---
title: "Create a page"
method: POST
path: "/projects/{projectId}/pages"
tags: ["Pages"]
---

# Create a page

`POST /projects/{projectId}/pages`

## Request

- Base URL: `https://studio.weaverse.io/api/v1/content`
- URL: `https://studio.weaverse.io/api/v1/content/projects/{projectId}/pages`
- Auth: HTTP bearer

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 as `pages/gift-shop`. Leading slashes are stripped, repeated slashes collapsed and trailing slashes removed. Starts with a blank root item unless `basedOn` is supplied.
- Resource-backed types (`PRODUCT`, `COLLECTION`, `PAGE`, `BLOG`, `ARTICLE`): a per-resource override (custom template) for one Shopify resource, where `handle` is 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. Pass `shopifyResourceId` to 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`)

## Path parameters

- `projectId` string, required

## Request body

- union
  - PageCreateRequest
    - `type` 'CUSTOM' | 'PRODUCT' | 'COLLECTION' | 'PAGE' | 'BLOG' | 'ARTICLE', required: 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.
    - `handle` string, required: 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.
    - `name` string: Optional display name. Defaults to the normalized handle.
    - `locale` string: Optional assignment locale. Defaults to the global/default assignment (`""`).
    - `basedOn` string: 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.
    - `shopifyResourceId` string: 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`.
  - object: Legacy POST-as-delete fallback. Prefer DELETE for bulk deletion.
    - `pageIds` string[], required
  - object: Legacy POST-as-delete fallback by handles. Prefer DELETE for bulk deletion.
    - `handles` string[], required
    - `type` string, required
    - `locale` string: Optional. Defaults to the stored default locale.

## Response `200`

Created page, or delete result when a legacy delete selector body is posted.

- union
  - PageCreateResponse
    - `object` 'page_create'
    - `page` Page
      - `object` 'page'
      - `id` string, nullable
      - `type` string
      - `handle` string
      - `locale` string, nullable
      - `items` Item[]
        - `id` string
        - `type` string
        - `data` object: Component props/settings
        - `children` unknown
      - `updatedAt` string, date-time, nullable
      - `meta` object
        - `inherited` boolean
        - `sourceProjectId` string
        - `depth` integer
      - `seo` object, nullable: The page's SEO metadata (`title`, `description`, `keywords`, `canonicalUrl`, `openGraph`, `twitter`, ...), or null when the page has none.
  - object
    - `object` 'page_delete'
    - `requested` integer
    - `deleted` integer
    - `notFound` integer
    - `failed` integer
    - `deletedHandles` string[]
    - `notFoundHandles` string[]
    - `failedHandles` string[]

## Other responses

- `400`: Invalid params
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Project or source page not found
- `409`: A live page assignment already exists for this handle/type/locale
- `500`: Unexpected server error

---

[API](https://skmtc.dev/weaverse/apis/weaverse-content-api.md) - [All operations](https://skmtc.dev/weaverse/apis/weaverse-content-api/llms.txt) - [OpenAPI document](https://skmtc.dev/weaverse/apis/weaverse-content-api/revisions/d189794169bb?raw)
