author-composition-via-api
When to use
Author Studio compositions by writing composition JSON via the CMA, the manual contract Studio's Data Picker hides. For headless / scripted / seed-pipeline projects, not interactive UI authors.
Use when authoring compositions programmatically via CMA: seed pipelines, scripted provisioning, migration scripts, debugging via diff. Phrases: "compositions from JSON", "API author composition", "seed compositions", "headless author", "bulk import". Do NOT use for UI authoring. The Data Picker handles every shape this skill describes. For UI use build-section / build-connected-template.
Mandatory auth preflight: settle the credential before the first API call. Resolve it OAuth-first per
authenticate-cma:CS_OAUTH_ACCESS_TOKEN, else the Contentstack MCP's stored session: Read that skill for where it is, even with no MCP connected. Never ask the user for a sessionauthtoken. If nothing resolves, or a refresh fails with400 invalid_refresh_token, hand them! CONTENTSTACK_REGION=<code> npx @contentstack/mcp --auth(it needs a TTY and a browser, so it cannot be run for them) and wait.403 error_code 316is a valid credential aimed at another org: fix the org or theapi_key, do not re-authenticate.
Author a composition by API (the manual contract)
Approved plan required: this path has no other gate. This skill writes composition JSON. It does not decide what to build. If there is no plan the user has explicitly approved (Sections with their linked schemas, Section Slots, per-Section components, bindings, and the template kind), stop and run
plan-studio-architecturefirst. The canvas build skills carry this guard. Scripted CMA authoring is the fastest way to reach a finished project without ever passing one, anddecompose-designnames this skill its default handoff. A template composition whose root slot holds a single component node is a monolith (no Sections, no slots, no reuse) and it renders perfectly, so nothing downstream will catch it. Exception: the user is iterating on a piece already in an approved plan.
Mandatory structural check before reporting success. Read the saved template back and count
section-compositionnodes inuiand entries inlinked_sections. Both zero means a monolith. The runnable check isbuild-connected-template§ Post-build structural check. It applies identically here, and it is the only acceptance check a monolith fails. When the page body is one modular-blocks field,decompose-blocks-pageis the recipe for splitting it.
When to use this: and when NOT to
Studio's Data Picker writes every binding shape this skill describes automatically. UI authors never need to know about data_sources.resolvedReferences, the repeaterUID discriminator, or any of the binding types. Studio's runtime resolves them under the hood.
Use this skill ONLY when bypassing the Data Picker:
- A seed pipeline that creates compositions programmatically (a docs / demo project's bootstrap, an automated stack provisioner)
- Migrating compositions between stacks
- Bulk-authoring compositions from another source (an existing CMS, a generator)
- Debugging: capture a working UI-authored composition, decode it, diff against your broken one
For interactive authoring, use Studio's UI. The Data Picker is the source of truth for these shapes. If your manually-written shape disagrees with what the Data Picker emits, the picker is right. Your manual shape is wrong.
Prerequisite: the project, the compositions CT, the credentials
This skill assumes:
- A Studio project exists, bound to a stack
- That stack has a compositions CT (the CT the project's
contentTypeUidpoints at) with the required field schema (seeprovision-studio-project) - You have a CMA auth token + the stack API key with permission to create entries on that CT
- For headless verification later: a Delivery Token + paired Preview Token (
enable-visual-experienceStep 4)
If any of these is missing, this skill is the wrong starting point.
Auth: OAuth first. Snippets here write
-H "$CS_AUTH", resolved once per run. Assume there is no sessionauthtoken. The user authenticates with OAuth (MCP--auth/csdx auth:login --oauth) and never pastes one. Resolution order, scope headers (organization_uidvsapi_key), the expiry/refresh rule and the 401/422 signature table:authenticate-cma.
MCP-first execution. Discover tools and verify the target with a read call per
install-contentstack-mcp. When supported tools are callable, the entry write + publish run as MCP tool calls (create_an_entry/update_an_entry/publish_an_entry) with no pastedauthtoken. The composition shape is unchanged: you still build theui(zlib:<base64>),data_sources,static_value, andcomposable_uid(= entry uid) exactly as below. The MCP only replaces the raw CMA transport. Shell-corruption caveat on the MCP path:create_an_entrytakesentry_dataas an inline JSON object: there is no file-path param, so the temp-file /curl -d @fileguard does NOT apply. Instead build the entireentry_dataobject programmatically and hand it to the tool, never hand-transcribe theuiblob into the arguments, and assert the"children"count in the tree before the call (see the shell-corruption pitfall). If hand-assembly is unavoidable, keep the rawcurl -d @filepath for the write. A missing readiness flag requires discovery; use HTTP only for unavailable operations or a recorded connection limitation.
The API-authoring set: read the one you need
This skill is the entry point. It carries the decision to author by API at all, the prerequisites, and the acceptance gate. Everything else lives in a focused sibling, so a single task loads what it needs instead of the whole contract.
| Read this | For |
|---|---|
author-composition-entry | The CMA body, url + url_metadata. Read first. |
author-composition-static-values | Where a static_value binding's literal lives |
author-composition-bindings | Every binding type, node anatomy, Repeater binding shapes. Read second. |
author-composition-encoding | Compressing the ui tree, and preflight-validating it before publish |
author-composition-repeater-recipes | Repeat a card over a multi-reference or GROUP-MULTIPLE field |
author-composition-nested-repeaters | A list inside each iterated item |
author-composition-sections | Section scoping (P22-P25), selectedField, section-slot |
author-composition-section-slot-fill | Pattern B: fill a wrapper's slots with another Section |
author-composition-exposed-props | Per-instance overrides on a placed Section |
author-composition-thumbnails | Why the API never sets a Section thumbnail |
author-composition-templates | linked_sections (P27), plus a complete Freeform body |
author-composition-pitfalls | Verifying the write, and the full pitfall table. Read before reporting done. |
author-composition-practice | Skeleton-first authoring, reading component source, free-form primitives |
author-composition-migration | Moving compositions between stacks |
Minimum path for any composition: author-composition-entry, then
author-composition-bindings, then author-composition-pitfalls before you report done.
Acceptance
This skill succeeds only when ALL are true.
- The composition entry was created with a unique
composable_uid(a readable name or the entry uid), published. - For a cross-stack migration: the template's
uicompositionUIDs +linked_sectionswere remapped to the destination entry uids (not carried over from the source). - Every
place_composition_as: "page"entry has BOTHurlandurl_metadata,url_sourcematching how the pattern is derived (content_type_url_patternfor Connected,default_url_pattern/user_specified_patternfor Freeform),url_queriesas a JSON string. Read the entry back after the write and confirmurl_metadatais present: a compositions CT missing the group makes the CMA drop it silently, leaving the composition onlegacy_url. -
data_sources.resolvedReferencesis set per reference field the composition iterates. - Every Repeater node has
metadata.mode: "preview"(for canvas iteration). - Every Repeater iterating a reference or modular block has a Condition Block as its immediate child.
- Every Condition Block's
metadata.conditionhasoperatorandconditionBinding, and the composition was checked in preview mode. Without the binding the block renders nothing, but design mode still shows it. - Card / iteration child bindings use
type: "repeater"withrepeaterUID, NOTtype: "template"with.0.<field>fixed-index paths. - Every Section composition created in this run has
ui_previewset, verified by re-reading each entry. A section with a blank tile is an unfinished section. Do not report the work as done. Also true of sections created as a side effect (clones, rescopes, batch builds). - Every Section composition has
ui_previewset, an uploaded, published asset uid, verified by re-reading the entry (.entry.ui_preview.urlresolves). Blank tiles in Studio's Sections accordion otherwise. Fastest fix is often opening each section in Studio and pressing Save. See § Section thumbnails - Envelope complete: every node in the written tree has all seven keys (
uid,type,props,metadata,slots,attrs,styles), verified by re-reading the entry. A missingslotsrenders fine and white-screens the Data tab later. - Preflight passed: every
slots[<uid>]is a non-empty array (exception:section-slotnodes carryslots: { "<own-uid>": [] }by design). No orphan descendant references in the tree. - Pattern B (if used): wrapper section has
section-slotnode withslots:{"<own-uid>":[]}. Fill section's leaf bindings usetype:"template"(NOTrepeater). Template'ssection-compositionnode hasslots.<slot-uid>: [<fill section-composition node>]. Template'slinked_sectionsincludes BOTH wrapper and fill entries. - Every
section-compositionnode'smetadata.compositionUIDis the placed section's entry uid (blt…), never itscomposable_uid. Acomposable_uidthere fails in the canvas withComponent with type '<it>' is not registered. - A section whose
selected_fieldis a multiple group / global field / file / link hascardinalityfrom the user's answer (asked, never assumed):"collection"with a root Repeater, or"single"without one. Reference and modular-block sections carry nocardinality(understand-linked-schemas). - Exposed section props (if used):
sectionExposedPropsarray on section'suiROOTmetadata. Template'ssection-composition.propsmap keyed by each exposure'suid. Per-instance override values use distinct static_value keys. - The composition resolves at runtime, verified by either curl against the CDA preview API (returns 200 + entries body, see
troubleshoot-canvas§ CORS error on the CDA preview host) OR a SSR cold-loadawait sdk.fetchCompositionData({ url })returningspec.data.section_scoped_data[<instance>]with the expectedtemplatearray (P26-style verification).
Sanity-check the output
After authoring, verify by ONE of:
- CDA preview API curl: confirm the composition is returned. See
troubleshoot-canvas§ CORS error on the CDA preview host for the curl shape. Status 200 + JSON entries body = the contract resolves. - SSR cold-load:
await sdk.fetchCompositionData({ url })from a throwaway server route. It rejects if the URL resolves to nothing, so a thrownNO_COMPOSITION_FOR_URL/URL_MATCHED_NO_ENTRYhere is itself the verification result: the composition isn't resolving. In a throwaway script that's fine. In app code wrap it (configure-csr-vs-ssr). Dumpspec.data.section_scoped_data[<instance>]to verify each section'sselectedField+ scopedtemplatearrives correctly. The most reliable check. - Decode the stored
ui: the read-onlyzlib-inflate snippet introubleshoot-canvas§ Diagnostic tooling. Diff your composition's node tree against a known-working UI-authored one to spot any shape mismatch. Useful for debugging.
See also
troubleshoot-canvas§ Diagnostic tooling (inspecting a stored composition's ui) the zlib decodertroubleshoot-canvas§ CORS error on the CDA preview host: preview-token verification curl shapeuse-repeater: UI authoring side. The Preview Mode toggle the API directly sets viametadata.modeuse-condition-block: the Condition Block schema requirement for reference / modular-block iterationbuild-section: section'sselectedFieldscoping (the parent of most API-authored sections)understand-auto-binding: the UI mechanism that writes these shapes for UI authorsprovision-studio-project(planned): compositions CT field schema, includinglinked_schemasas group-multipletroubleshoot-data-binding: the "registered as lazy but not loaded yet" error class