curl --request POST \
--url https://studio.weaverse.io/api/v1/content/projects/{projectId}/pages \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"type": "COLLECTION",
"handle": "summer-sale"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({type: 'COLLECTION', handle: 'summer-sale'})
};
fetch('https://studio.weaverse.io/api/v1/content/projects/{projectId}/pages', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://studio.weaverse.io/api/v1/content/projects/{projectId}/pages"
payload = {
"type": "COLLECTION",
"handle": "summer-sale"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"object": "page_create",
"page": {
"object": "page",
"id": "clz2r8u6x0003mnop3456uvwx",
"type": "CUSTOM",
"handle": "pages/gift-shop",
"locale": "",
"items": [
{
"id": "019a3c2e-5b7d-7f10-8a2b-3c4d5e6f7a81",
"type": "main",
"data": {},
"children": null
}
],
"updatedAt": "2026-10-20T10:30:00.000Z",
"meta": {
"inherited": false,
"sourceProjectId": "clz1k8m2n0000abcd1234efgh",
"depth": 0
}
}
}{
"object": "error",
"code": "UNAUTHORIZED",
"message": "<string>",
"status": 123,
"details": "<unknown>"
}{
"object": "error",
"code": "UNAUTHORIZED",
"message": "<string>",
"status": 123,
"details": "<unknown>"
}{
"object": "error",
"code": "UNAUTHORIZED",
"message": "<string>",
"status": 123,
"details": "<unknown>"
}{
"object": "error",
"code": "UNAUTHORIZED",
"message": "<string>",
"status": 123,
"details": "<unknown>"
}{
"object": "error",
"code": "CONFLICT",
"message": "Page already exists: COLLECTION/summer-sale",
"status": 409
}{
"object": "error",
"code": "UNAUTHORIZED",
"message": "<string>",
"status": 123,
"details": "<unknown>"
}Create a page
Creates one new page in the project. Two kinds of page are creatable:
CUSTOM: a bespoke merchant page addressed by a caller-chosen handle/path such aspages/gift-shop. Leading slashes are stripped, repeated slashes collapsed and trailing slashes removed. Starts with a blank root item unlessbasedOnis supplied.- Resource-backed types (
PRODUCT,COLLECTION,PAGE,BLOG,ARTICLE): a per-resource override (custom template) for one Shopify resource, wherehandleis the resource handle, kept verbatim. It clones the project’s shared default template for that type when one exists, otherwise it starts with a blank root item. PassshopifyResourceIdto bind the Shopify resource for Studio’s assignment UI.
Pass basedOn with a page id from the pages list to clone that page’s layout instead. The source page must be assigned to the same project; a cross-project or unknown id returns 404 PAGE_NOT_FOUND.
This creates one page copy per call. To point many resources at a single shared template page without copying content, use POST /projects/{projectId}/template-assignments/{pageId}.
If a live assignment already exists for the same type, handle and locale (or market), the request returns 409 CONFLICT and creates nothing; read or edit the existing page instead. Any other type, or a missing or empty handle, returns 400 INVALID_PARAMS.
Legacy behaviour: a body containing pageIds or handles is treated as the bulk-delete fallback and returns a page_delete result. Prefer DELETE /projects/{projectId}/pages for deletion.
Required scope: content:write (the bulk-delete fallback also requires content:delete)
curl --request POST \
--url https://studio.weaverse.io/api/v1/content/projects/{projectId}/pages \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"type": "COLLECTION",
"handle": "summer-sale"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({type: 'COLLECTION', handle: 'summer-sale'})
};
fetch('https://studio.weaverse.io/api/v1/content/projects/{projectId}/pages', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://studio.weaverse.io/api/v1/content/projects/{projectId}/pages"
payload = {
"type": "COLLECTION",
"handle": "summer-sale"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"object": "page_create",
"page": {
"object": "page",
"id": "clz2r8u6x0003mnop3456uvwx",
"type": "CUSTOM",
"handle": "pages/gift-shop",
"locale": "",
"items": [
{
"id": "019a3c2e-5b7d-7f10-8a2b-3c4d5e6f7a81",
"type": "main",
"data": {},
"children": null
}
],
"updatedAt": "2026-10-20T10:30:00.000Z",
"meta": {
"inherited": false,
"sourceProjectId": "clz1k8m2n0000abcd1234efgh",
"depth": 0
}
}
}{
"object": "error",
"code": "UNAUTHORIZED",
"message": "<string>",
"status": 123,
"details": "<unknown>"
}{
"object": "error",
"code": "UNAUTHORIZED",
"message": "<string>",
"status": 123,
"details": "<unknown>"
}{
"object": "error",
"code": "UNAUTHORIZED",
"message": "<string>",
"status": 123,
"details": "<unknown>"
}{
"object": "error",
"code": "UNAUTHORIZED",
"message": "<string>",
"status": 123,
"details": "<unknown>"
}{
"object": "error",
"code": "CONFLICT",
"message": "Page already exists: COLLECTION/summer-sale",
"status": 409
}{
"object": "error",
"code": "UNAUTHORIZED",
"message": "<string>",
"status": 123,
"details": "<unknown>"
}Authorizations
Bearer authentication: a shop API key (WEAVERSE_API_KEY), a scoped API key, a delegated MCP OAuth token, or (translation-unit routes only) a project-bound translation agent token. Scopes: content:read — Read projects, pages, theme settings, versions, drafts and translation units. content:write — Save drafts and make non-destructive edits (page items, metadata, SEO, assignments, languages, glossary). Never publishes or deletes. content:publish — Publish drafts, schedule/reschedule/cancel publications, restore versions, publish global sections. content:delete — Delete pages, global sections, languages, static keys, override reverts; required with project:manage for project deletion. project:manage — Create/copy/delete projects, settings, variants, hierarchy, backups, restore-as-new, previews, hostname policy. translations:read — Read translation units only (no general content access). translations:write — Apply translation units only; never mutates page content. A legacy unscoped API key keeps read/write/translation baseline access and every pre-scope endpoint, but never gains publish/delete/project-manage implicitly.
Path Parameters
Body
- Option 1
- Option 2
- Option 3
Page type to create. CUSTOM is a bespoke merchant page (blank root). The resource-backed template types (PRODUCT, COLLECTION, PAGE, BLOG, ARTICLE) create a per-resource override / custom template that clones the project's shared default template for that type (or basedOn). This creates one new page per call (a per-resource copy). To share one template page across many resources without copying, use POST /template-assignments/{pageId} instead.
CUSTOM, PRODUCT, COLLECTION, PAGE, BLOG, ARTICLE Page handle. For CUSTOM, leading slashes are stripped, repeated slashes are collapsed, and trailing slashes are removed before persistence. For resource-backed types this is the Shopify resource handle (trimmed, kept verbatim) — pass the exact handle used by the list/get/delete endpoints.
Optional display name. Defaults to the normalized handle.
Optional assignment locale. Defaults to the global/default assignment ("").
Optional source page id to clone layout items from. The source page must be assigned to the same project. When omitted for a resource-backed type, the project's shared default template for that type is cloned when one exists; otherwise the page starts with a blank root item.
Optional non-empty Shopify resource id (gid). Validated when present; persisted only for resource-backed types (PRODUCT/COLLECTION/PAGE/BLOG/ARTICLE); not stored for CUSTOM.
Was this page helpful?