Subchapter 10.97
references/seo/manage-seo-tags.mdMarkdown17 KBView on GitHub
Use the public SEO APIs to read and write the titles, descriptions, social share tags, canonical links, structured data, and indexing directives of the authenticated Wix site. The API selects the site from the caller’s authorization context; never ask for or send a site ID.
Writing tags requires the permission. Reading tags or listing pattern variables does not prove that the caller can write.
Every write replaces the target’s tags in full, so a Get always immediately precedes a Set. There is no partial update: sending only the tag the user asked about deletes every other tag that item, pattern, or site had. So before any Set, call the matching Get for that exact target, merge the requested change into the tags it returns, and send the complete set back.
Never write from the user’s request alone, and never skip the Get because the
change looks small, because a list call already returned something, or because
the target looks empty. The Set response returns the updated tags and
resolvedTags, so report the outcome from that response instead of issuing
another read.
Wix combines tags from several sources, where a more specific source wins. Match the user’s request to the level that owns the change:
| User intent | Level | API |
|---|---|---|
| “Change my site’s default social image”, site verification tags, site-wide indexing | Site | Site SEO Tags |
| “All my product/blog/event pages should be titled like X” — a convention for every item of a page type | Pattern | SEO Patterns |
| “Change the title of this page/product/post” — one specific item | Item | Item SEO Tags |
Site SEO Tags accepts meta tags only (including site-level social image
and robots settings). Never send title, link, or script tags to
PATCH /site-seo-tags. Use Item SEO Tags for a specific page’s title, or
SEO Patterns for a page-type title convention. See the
Site SEO Tags object (opens in a new tab).
If the user says “change my site’s SEO title” without identifying a page or
page type, ask whether they mean the homepage, another specific page, or a
page-type title convention. Ask immediately after reading this recipe; do not make discovery or schema
queries until scope is clear. For an explicit
homepage request, discover its actual ID; never assume an ID such as home.
Use Item SEO Tags with STATIC_PAGE, read its current tags, and merge the title
into the complete set. Follow the static-page publication rules below.
Do not substitute og:title for the requested SEO title.
Work at the level that matches the change. Writing the same title onto many items is the same outcome as one pattern and much harder to undo. If the user’s words fit more than one level, ask one short question before writing. An explicit request to change a specific tag at a clear level is already confirmation to make that write.
All three APIs live under the SEO category of the Wix REST API reference. The request shapes are in the section below — construct requests from them. The reference article URLs follow this pattern (for edge cases not covered here):
https://dev.wix.com/docs/api-reference/business-management/seo/{api}/ + method slug
| Level | API slug | Methods (slug) |
|---|---|---|
| Site | site-seo-tags-v1 | Get Site SEO Tags (get-site-seo-tags), Set Site SEO Tags (set-site-seo-tags) |
| Pattern | seo-pattern-v1 | Get SEO Pattern (get-seo-pattern), List SEO Patterns (list-seo-patterns), List SEO Pattern Variables (list-seo-pattern-variables), Create SEO Pattern (create-seo-pattern), Set SEO Pattern (set-seo-pattern), Reset SEO Pattern To Default (reset-seo-pattern-to-default) |
| Item | item-seo-tags-v1 | Get Item SEO Tags (get-item-seo-tags), List Item SEO Tags (list-item-seo-tags), Set Item SEO Tags (set-item-seo-tags), Bulk Set Item SEO Tags (bulk-set-item-seo-tags), Reset Item SEO Tags To Default (reset-item-seo-tags-to-default) |
Use these shapes directly — do not go looking for them in the docs first.
Build requests from the shapes below. Do not search the API schemas or read the reference article to construct them.
{
"itemSeoTags": {
"tags": [
{ "type": "title", "children": "Page title here" },
{ "type": "meta", "props": { "name": "description", "content": "Description here" } }
]
},
"fieldMask": "tags"
}No request body. Returns itemSeoTags with tags, resolvedTags,
hasOverride, publishStatus, and hostPageId.
No request body. Returns itemSeoTags[] and pagingMetadata with cursors.
itemSeoTagsList is not a response field. Each array entry is an Item SEO Tags
object directly, not a wrapper: read entry.itemId, entry.tags, and
entry.resolvedTags, not entry.itemSeoTags. Use itemId as the path ID;
id is a composite identifier and hostPageId is not the item identifier.
For static-page discovery, call this with STATIC_PAGE. Example response:
{
"itemSeoTags": [
{
"itemType": "STATIC_PAGE",
"itemId": "<page-id>",
"tags": [],
"resolvedTags": [
{ "tag": { "type": "title", "children": "Home | Studio Shop" },
"source": "TAG_SOURCE_DEFAULT_PATTERN" }
]
}
],
"pagingMetadata": { "hasNext": false }
}Inspect the returned page identities and resolved tags to identify the requested
page, then Get that exact itemId before writing. If the response does not
unambiguously identify it, ask the user to identify the page. Do not interpret
a missing response field as an empty page list, assume the first entry is the
homepage, or create/update a pattern as a fallback for a missing page. A
single-page request never authorizes changing all pages of its type.
See List Item SEO Tags (opens in a new tab).
{
"siteSeoTags": {
"tags": [
{ "type": "meta", "props": { "name": "google-site-verification", "content": "token" } }
]
},
"fieldMask": "tags"
}{
"seoPattern": {
"pattern": {
"tags": [
{ "type": "title", "children": "{{item.name}} | {{site.name}}" }
]
}
},
"fieldMask": "pattern"
}A tag is {type, props, children}: title and script carry their text in
children; meta and link carry theirs in props (name/content for a
meta tag, rel/href for a link).
fieldMask over REST is a comma-separated string ("fieldMask": "tags");
the SDK takes an array (fieldMask: ["tags"]). Sending an array to REST
returns INVALID_FIELD_MASK. publish is a boolean.
resolvedTags in any Get or Set response is an array of
{tag, source, inheritedTag}. source is one of TAG_SOURCE_SITE,
TAG_SOURCE_DEFAULT_PATTERN, TAG_SOURCE_USER_PATTERN, TAG_SOURCE_HOST_PAGE,
TAG_SOURCE_ITEM, or TAG_SOURCE_UNSPECIFIED. inheritedTag appears only when
this source replaced a value a lower source had set.
All paths are relative to https://www.wixapis.com/promote/seo/v1.
STATIC_PAGE, STORES_PRODUCT, STORES_CATEGORY,
BLOG_POST, BLOG_CATEGORY, BOOKINGS_SERVICE, EVENTS_PAGE,
PORTFOLIO_PROJECTS, PORTFOLIO_COLLECTIONS, RESTAURANTS_MENU_PAGE.POST /stores/v3/products/search
to get the product ID, then use that ID as the itemId with item type
STORES_PRODUCT. Do not call List Item SEO Tags and scan through all items
to match by name — use the vertical API.UNSUPPORTED_ITEM_TYPE error message; do not hardcode one beyond the common
values listed above.pageId; addressing a pattern by
collection name is not supported.Exactly three calls, in this order, with no extra probing:
resolvedTags. Do not issue a verification read: the write response is the
confirmation. Read again only to check a static page’s published revision
after publish: true, or when the user asks about a target you have not read
in this session.To remove an item’s own tags so it inherits again, call Reset Item SEO Tags To Default (or Reset SEO Pattern To Default) — never set an empty list to mean “reset”.
The user asks: “Set the SEO title of my product ‘Handmade Mug’ to ‘Handmade Ceramic Mug | Studio Shop’ and its description to ‘A stoneware mug.’”
Step 1 — find the product ID. Search Products v3 by name:
POST https://www.wixapis.com/stores/v3/products/search
{ "search": { "search": { "expression": "Handmade Mug" } } }{ "products": [{ "id": "a1b2c3d4-...", "name": "Handmade Mug", ... }] }Take products[0].id. The item type is STORES_PRODUCT. Do not use Query
Products with a name filter — name is not filterable and returns 400.
Step 2 — read the current tags.
GET https://www.wixapis.com/promote/seo/v1/item-seo-tags/STORES_PRODUCT/{productId}{
"itemSeoTags": {
"tags": [],
"hasOverride": false,
"resolvedTags": [
{ "tag": { "type": "title", "children": "Handmade Mug | My Site" },
"source": "TAG_SOURCE_DEFAULT_PATTERN" }
]
}
}tags is empty because the item has no overrides yet. resolvedTags shows
what the page currently renders with and where each tag comes from.
Step 3 — merge and write. Take the full tags array from step 2, replace
or add the title and description, and send the complete set back:
PATCH /item-seo-tags/STORES_PRODUCT/{productId}
{
"itemSeoTags": {
"tags": [
{ "type": "title", "children": "Handmade Ceramic Mug | Studio Shop" },
{ "type": "meta", "props": { "name": "description", "content": "A stoneware mug." } }
]
},
"fieldMask": "tags"
}Step 4 — report from the response. The Set response returns the updated
tags and resolvedTags. Report each resolved tag with its source. Do not
issue another Get — the write response is the confirmation.
Searching the API schemas or reading the reference article before the first call. The request shapes are in this recipe. If you get a 400, compare your request against the shapes in this recipe — do not go to the docs.
Searching for the SEO endpoints through API spec tools. The shapes are above; searching wastes a turn.
Sending fieldMask as an array over REST. Over REST it is a
comma-separated string: "fieldMask": "tags". The SDK’s ["tags"] array
returns INVALID_FIELD_MASK.
Guessing the item type. Use the values in the Discover section. A store
product is STORES_PRODUCT, not StoresProduct, product, or Product.
Issuing a verification Get after a successful Set. The Set response
already carries resolvedTags. A second read is redundant.
Using Products v1 or filtering by name on Query Products. Both return
400. Use Search Products v3: POST /stores/v3/products/search with
{"search":{"search":{"expression":"..."}}}.
Calling List Item SEO Tags expecting product names. List returns IDs and tags, not names. Use Search Products to find the ID by name first.
Retrying a 400 with a different request shape without checking why. A 400 can mean the target level is wrong, not just the shape. Read the validation message and check the level rules first. Compare against the shapes in this recipe, fix the mismatch, send once. Three retries with guessed shapes is three wasted calls.
Tags can currently be written only for the site’s primary language: leave
language unset on every write. There is no revision checking — the last write
wins and dashboard edits write to the same data, so read immediately before
writing.
Static pages keep a draft and a published revision: a write updates the
draft unless publish is true, and publish: true updates only the
published revision. Say clearly which revision the change reached. Item types
that are always live, such as store products, need no publish step.
To set tags on many items, call Bulk Set Item SEO Tags once, not Set in a
loop. Expected per-item failures (invalid tags, item not found) fail only that
entry and are reported on that entry’s itemMetadata.error; map results back
to the request with originalIndex and retry only the failed entries. Report
partial failures truthfully — never present a partially failed bulk write as a
success.
resolvedTags already
returned by the last Get or Set — not a fresh call — and name the source of
each tag (site, default pattern, user pattern, host page, or the item
itself). Naming the source of each reported tag is required, not optional.
hasOverride: false does not mean built-in defaults — the item may inherit
from a customized pattern or the site.resolvedTags excludes tags added by site code or apps at render time, so
present it as what Wix manages, not a literal copy of the rendered page head.403 or PERMISSION_DENIED. Do
not retry with another item, level, request shape, or site. Explain that the
current Wix identity lacks Manage SEO Settings and must be reconnected or
authorized. Never imply that a successful read means the write ran.UNSUPPORTED_ITEM_TYPE: the error message lists the item types the API
supports; use it to redirect the request rather than retrying blindly.ITEM_NOT_FOUND: re-discover the item ID; do not guess a new one.FIELD_NOT_ALLOWED: a rejected title,
link, or script is a wrong-level error, not a nesting or field-mask error.
Do not retry it at site level or silently drop the requested tag. Clarify the
target if needed, then use the appropriate item or pattern API. Get that
target’s current tags before merging and writing; never reuse the site’s
tags as the new target’s complete set. A rejected write saved no changes.400 on a request you built from this recipe: compare the request you
sent against the shapes in this recipe’s “REST request and response shapes”
section and the wrong-level recovery rule above. Fix the mismatch — a wrong
nesting level, a missing wrapper key, or fieldMask sent as an array instead of a string — and send again. Never
resend the same shape, and never walk through variations hoping one is
accepted.EVENTS_PAGE items: not supported yet, although reading
them is; say so instead of retrying.This recipe is self-contained for the common flows: request shapes, field
masks, tag structure, resolvedTags format, discovery, and error handling are
all above. Do not read the method’s reference article and do not search the
API schemas — build every request from this recipe alone.
Source