Studio Docs

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. Never ask the user for a session authtoken. If nothing resolves, or a refresh fails with 400 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 316 is a valid credential aimed at another org: fix the org or the api_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-architecture first. The canvas build skills carry this guard. Scripted CMA authoring is the fastest way to reach a finished project without ever passing one, and decompose-design names 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-composition nodes in ui and entries in linked_sections. Both zero means a monolith. The runnable check is build-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-page is 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 contentTypeUid points at) with the required field schema (see provision-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-experience Step 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 session authtoken. The user authenticates with OAuth (MCP --auth / csdx auth:login --oauth) and never pastes one. Resolution order, scope headers (organization_uid vs api_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 pasted authtoken. The composition shape is unchanged: you still build the ui (zlib:<base64>), data_sources, static_value, and composable_uid (= entry uid) exactly as below. The MCP only replaces the raw CMA transport. Shell-corruption caveat on the MCP path: create_an_entry takes entry_data as an inline JSON object: there is no file-path param, so the temp-file / curl -d @file guard does NOT apply. Instead build the entire entry_data object programmatically and hand it to the tool, never hand-transcribe the ui blob 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 raw curl -d @file path 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 thisFor
author-composition-entryThe CMA body, url + url_metadata, static_value resolution. Read first.
author-composition-bindingsEvery binding type, node anatomy, Repeater binding shapes. Read second.
author-composition-encodingCompressing the ui tree, and preflight-validating it before publish
author-composition-repeater-recipesRepeat a card over a multi-reference or GROUP-MULTIPLE field
author-composition-nested-repeatersA list inside each iterated item
author-composition-sectionsSection scoping (P22-P25), selectedField, section-slot
author-composition-exposed-propsPer-instance overrides on a placed Section
author-composition-thumbnailsWhy the API never sets a Section thumbnail
author-composition-templateslinked_sections (P27), plus a complete Freeform body
author-composition-pitfallsVerifying the write, and the full pitfall table. Read before reporting done.
author-composition-practiceSkeleton-first authoring, reading component source, free-form primitives
author-composition-migrationMoving 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 composable_uid = the entry uid, published.
  • For a cross-stack migration: composable_uid was re-derived to each destination entry’s new uid, and the template’s ui compositionUIDs + linked_sections were remapped to the destination uids (not carried over from the source).
  • Every place_composition_as: "page" entry has BOTH url and url_metadata, url_source matching how the pattern is derived (content_type_url_pattern for Connected, default_url_pattern / user_specified_pattern for Freeform), url_queries as a JSON string. Read the entry back after the write and confirm url_metadata is present: a compositions CT missing the group makes the CMA drop it silently, leaving the composition on legacy_url.
  • data_sources.resolvedReferences is 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.
  • Card / iteration child bindings use type: "repeater" with repeaterUID, NOT type: "template" with .0.<field> fixed-index paths.
  • Every Section composition created in this run has ui_preview set, 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_preview set, an uploaded, published asset uid, verified by re-reading the entry (.entry.ui_preview.url resolves). 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 missing slots renders fine and white-screens the Data tab later.
  • Preflight passed: every slots[<uid>] is a non-empty array (exception: section-slot nodes carry slots: { "<own-uid>": [] } by design). No orphan descendant references in the tree.
  • Pattern B (if used): wrapper section has section-slot node with slots:{"<own-uid>":[]}. Fill section’s leaf bindings use type:"template" (NOT repeater). Template’s section-composition node has metadata.sectionSlotCompositions.<slot-uid>.compositionUID. Template’s linked_sections includes BOTH wrapper and fill entries.
  • Exposed section props (if used): sectionExposedProps array on section’s ui ROOT metadata. Template’s section-composition.props map keyed by each exposure’s uid. 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-load await sdk.fetchCompositionData({ url }) returning spec.data.section_scoped_data[<instance>] with the expected template array (P26-style verification).

Sanity-Check the Output

After authoring, verify by ONE of:

  1. 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.
  2. SSR cold-load: await sdk.fetchCompositionData({ url }) from a throwaway server route. It rejects if the URL resolves to nothing, so a thrown COMPOSITION_NOT_FOUND_BY_URL / PREVIEW_ENTRY_NOT_FOUND here 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). Dump spec.data.section_scoped_data[<instance>] to verify each section’s selectedField + scoped template arrives correctly. The most reliable check.
  3. Decode the stored ui: the read-only zlib-inflate snippet in troubleshoot-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 decoder
  • troubleshoot-canvas § CORS error on the CDA preview host: preview-token verification curl shape
  • use-repeater: UI authoring side. The Preview Mode toggle the API directly sets via metadata.mode
  • use-condition-block: the Condition Block schema requirement for reference / modular-block iteration
  • build-section: section’s selectedField scoping (the parent of most API-authored sections)
  • understand-auto-binding: the UI mechanism that writes these shapes for UI authors
  • provision-studio-project (planned): compositions CT field schema, including linked_schemas as group-multiple
  • troubleshoot-data-binding: the “registered as lazy but not loaded yet” error class