Studio Docs

When to Use

Symptom-mapped diagnostic for bound fields rendering incorrectly: covers binding paths, type mismatches, repeater item context, and condition-block parent dependency.

Use when a binding is wired but renders the wrong value, an empty value, the literal path, or breaks at runtime. Phrases: “binding shows nothing”, “wrong value rendering”, “literal {{...}}”. If the symptom is ambiguous, route via troubleshoot first. Do NOT use for canvas iframe failures (use troubleshoot-canvas), SSR errors (troubleshoot-ssr-rendering), or URL resolution (troubleshoot-composition-resolution).

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.

Troubleshoot Studio Data Binding

Context

Data binding in Studio flows through three layers, each with distinct failure modes:

  1. Authoring (Studio editor): the user picks a field via the Data Picker. The picker enforces type / shape rules at selection time. Violations surface as inline modal errors.
  2. Spec validation (registry): when a component is registered, the SDK checks each prop’s declared type against incoming bindings. Mismatches log in editor mode.
  3. Runtime resolution (SDK data-binder): at render time, the SDK walks the binding path against actual data, falls back to static value or type-default if missing, and console.warns on type mismatches in production only (editor mode suppresses them).

Almost every “field renders wrong” report maps to one of these three layers. This skill walks each symptom to its failing layer, then to the UI control that fixes it.

Diagnostic tooling: inspecting a stored composition’s ui

Binding-shape debugging often needs the actual stored shape, what the Data Picker emitted into the composition. Fetch via the Studio API. The ui field comes back as zlib:<base64>, a literal zlib: prefix + base64-encoded zlib-deflated JSON. Decode for read-only inspection:

// Node 18+. Save as inspect-ui.js
import zlib from "node:zlib";

function decodeUi(ui) {
  if (!ui.startsWith("zlib:")) return JSON.parse(ui);  // legacy plain-JSON fallback
  const bytes = Buffer.from(ui.slice("zlib:".length), "base64");
  return JSON.parse(zlib.inflateSync(bytes).toString("utf8"));
}

const composition = JSON.parse(await new Response(process.stdin).text());
console.log(JSON.stringify(decodeUi(composition.ui), null, 2));

The reliable debugging move when bindings misbehave is: capture a working composition that was hand-authored through the UI, decode its ui, and diff your broken composition’s tree against it. Bindings, data-source resolution, repeater metadata: they’re all visible in the decoded tree.

Read-only, for debugging. Don’t modify bindings by writing back to the API. Use the Data Picker. Hand-edited bindings drift from the shape Studio expects and reproduce exactly the failure mode you’re trying to debug.

Task

  1. Note the surface the user is on. Some symptoms only appear in production (the type-mismatch console warning is silenced in editor mode). Live Preview also has different fallback behavior than a raw production page.
  2. Match the symptom to the table below: exact wording matters less than category.
  3. Run the “Check first” step. If it confirms the cause, propose the fix. If not, run the secondary check.
  4. Report: the symptom, the cause, the check you ran, and the fix. Cap at two checks. Escalate to engineering if both come back negative, sharing the page URL, the binding path from Properties, and the data payload (in DevTools, open the Network tab and read the composition or entry fetch responses).

Symptom-to-cause matrix

Symptom (verbatim or paraphrased)Likely causeCheck firstFix
”Invalid binding” marker in the Layers panelCondition Block’s condition binding is null/undefined, or the binding resolves to no data.In Properties, open Conditions: is the picker actually populated?Re-select the field via the picker. If parent (section/repeater/template) is itself unbound, bind it FIRST (Condition Block depends on parent context).
”No source configured” in LayersBinding was set, then the source field was deleted from the schema or the data source was disconnected. Static value is still rendering as fallback.Open Properties, then Data: does the binding chip look empty or stale?Click the picker, re-bind the field. Static value persists as a fallback when the binding goes stale. Both can coexist.
"Direct binding isn't supported because this field has no data—use a repeater to bind it to the schema"The chosen data path resolved to an empty array. The picker blocks expanding empty arrays at selection time.Confirm the upstream data has items (Live Preview / production payload).If iterating, wrap with a Repeater bound to the array field, then bind nested props inside the iteration context.
”Primitive fields cannot be selected when binding nested repeaters. Only array fields are allowed.”Tried to bind a nested Repeater’s items to a primitive (string / number / boolean) field.Check the picked field’s type in the schema.The field must be type: "array". Pick a different field, or restructure upstream data so the iteration source is an array.
”Non-array object fields (like JSON editor) cannot be selected when binding nested repeaters.”Same picker, tried to bind a nested Repeater’s items to an object/JSON-editor field.Check the picked field’s type.The binding must point to an array. Transform the object into an array of entries upstream if you genuinely need to iterate object keys.
Browser console warning: Data binding type mismatch in component "...": Expected prop "..." to be ... but received ...At runtime, the SDK saw a type that doesn’t match the registered prop’s declared type. Only logged in production-style render. Editor mode suppresses it.Open DevTools console on the published page. Filter Data binding type mismatch.Reconcile the schema. Either change the prop’s declared type, or change the bound field, or insert a transformation in code. Don’t silence the warning: it always indicates a real type drift.
Field renders a hardcoded default (or empty string / 0 / false) instead of CMS dataBinding path didn’t resolve in the actual data, so the SDK used the static value / default value (then a type-default if both are absent).In DevTools, open the Network tab, find the composition fetch, and look for the binding’s path in the response. Does the path exist?(a) If path is wrong: re-bind via picker. (b) If path is correct but value is null/missing: fix upstream content. (c) Set a non-empty static value as visible fallback if the field is legitimately optional.
A list/grid component shows generic placeholder cards (ARTICLE ONE / TWO / THREE, “short teaser”, gray image icons) even in Preview ModeA whole-array component prop (a Card Grid / Article Grid that .map()s internally) is bound to a field that doesn’t exist on the CT (or isn’t bound at all), so the array resolves to nothing and the component renders its registration defaultValue. Classic cause: the page CT (e.g. Article List) has no multi-Reference / Modular Block / Pinned Query feeding the list. The items live in a separate CT the page never references.Open the Data Picker on the array prop: is there actually a multi-valued field to bind to? Then in DevTools, open the Network tab and check the composition fetch: is the array field present and populated in the payload?Add the missing multi-valued source: multi-Reference on the page CT (migrate-ct-schema), or a Pinned Query (pin-query-to-freeform), then bind. If you want per-item binding visible in Studio instead of one array blob, decompose to Repeater + atomic child Section (build-repeating-section). This is the build-section Step 5e / register-component whole-array trap, caught after the fact.
Console: [Composable Studio SDK] useData() was called outside a DataCtxProvider (dev only, client only)A basic component (Repeater / ConditionBlock / SectionComposition) rendered without its parent DataCtxProvider. Hook returns empty data and warns.Confirm the component tree wraps the basic component with <StudioComponent>.Always render compositions inside <StudioComponent> (it provides DataCtxProvider with the spec). If you’re using a basic component directly outside that wrapper, wrap it explicitly. This is a render-tree shape issue, not a binding-path issue.
Repeater renders one iteration on the Studio canvas, production renders the correct NRepeater’s authoring default is to render a single iteration with placeholder/default values so authors can lay out the structure without depending on bound data. Production iterates the real array.Repeater Preview Mode toggle in Properties.Select the Repeater node in Layers, toggle Preview Mode in Properties: canvas re-renders with real iteration data. (If production also renders one or zero iterations, the binding itself is wrong. Re-check the bound array field in the data payload.)
Repeater renders no iterations in production / SSRDuring SSR, useData() returns empty data (no items), so the Repeater iterates 0× until hydration attaches real context.View page source (no JS): Repeater HTML is minimal.This is expected for SSR. If the items don’t appear AFTER hydration either, the issue is upstream: specOptions.data.dataSources doesn’t contain the items, or the binding path is wrong. Check the Network payload. See troubleshoot-ssr-rendering for further SSR-specific failure modes.
Condition Block’s field picker shows no optionsParent node (section / repeater / template) has no binding set, or its binding hasn’t resolved. Condition is a child of the parent’s data context.Select the parent in Layers. Check its binding in Properties, under Data.Select the parent in Layers FIRST. In Properties, under Data, verify a binding is set. Then return to the Condition Block: its picker now lists fields from the parent context.
Component prop’s declared type doesn’t match what the binding supplies (editor-side warning)Component registry validates declared types against incoming bindings. Mismatch surfaces in the editor (not as a thrown error, as a warning).Look for the warning in the editor’s console alongside the component name.Open the component’s registration (where registerComponent was called). Reconcile the type declaration with what the binding actually returns. If the mismatch is intentional (e.g. a number rendered as a string), clear the binding and use a static value, or transform upstream.
Empty render that worked seconds ago, no code changes (grid suddenly empty, list blank, etc.)A transient 5xx from the Studio API: /composable-studio-api/v1/projects/... returns 503 / 502 / 504 intermittently and Studio renders nothing. Not your binding.In DevTools, open the Network tab and filter composable-studio-api: any 503 / 502 / 504 around the time it went empty? If yes, that’s the cause.Reload. Don’t chase a transient 5xx through the binding-debug path above: empty renders from upstream blips look identical to real binding failures and will cost an hour. Only if the 5xx reproduces consistently is it no longer transient. Escalate with the API path + project ID + timestamp. See troubleshoot-canvas for the canonical row on this symptom.
Section renders blank in production AND in the editor canvas, the section is dropped on a template but shows no content / repeats nothing / shows one empty placeholderThe section’s scope (selectedField) didn’t resolve. Sections are data-less standalone (author-composition-via-api § How a Section gets data). They only fill out when placed on a template. If the resolved selectedField is wrong / missing, the section’s dataSources.template is undefined or empty, so repeaters render nothing and the section comes out blank. Common causes: linked_schemas dropped by the compositions CT (P23 trap: modeled as reference instead of group-multiple), selectedField points at a field that doesn’t exist on the page CT, the page entry’s field is genuinely empty, missing data_sources.resolvedReferences for a reference path.Dump section_scoped_data via the SSR cold-load. See verify-setup § Layer 7. For every section instance, that map shows the resolved selectedField + the actual scoped template. If template is undefined / empty / wrong-shape, the section’s scope is broken.Fix per the failure class. For the linked_schemas trap, re-model the compositions CT field as group-multiple AND populate sectionBindingOverride on each section-composition node (see author-composition-via-api § The linked_schemas-as-reference trap). For a wrong selectedField, fix the section’s linked_schemas to point at a field that exists on the page CT. For missing resolvedReferences, see author-composition-via-api § Repeat a card over a multi-reference field.

| Modular Block renders, but the reference inside each block is empty, blocks appear, their own fields render, only the referenced entry’s fields are blank. Binding shows the right path in the picker | The resolvedReferences path for a reference nested in a Modular Block omitted the block uid. A MB item is { <block_uid>: { …fields } }, so the path must be blocks.<block_uid>.<ref_field>. blocks.<ref_field> (the shape that works for a group-multiple) is not a fetchable CDA include, so Studio filters it out to avoid a 422, silently. The reference stays a stub and the binding resolves against the stub. | Dump the template composition’s data_sources, find the "template" entry’s resolvedReferences, and check each path has three segments: the MB field, the block uid, then the reference field. Also confirm it’s on the parent template composition, not the Section. | Rewrite the path with the block uid: "blocks.feature_block.related_products". One entry per block uid that holds a reference. Numeric prefixes (2_feature_block) are stripped by both Studio and the SDK, so write the plain uid. See author-composition-via-api § Modular Block to reference: the block-uid segment. Same silent failure applies to any mis-terminated reference path: a reference inside a reference (drop the target CT uid), or a nested asset (list the reference AND the nested path). All four shapes: author-composition-via-api § Where a reference path must terminate. | | A whole section renders as nothing, though the field it binds is populated | The block declares two collection fields for the same idea and leaves one empty, <collection_a>: [] beside a populated <collection_b>. Component code written as body.<collection_a> ?? body.<collection_b> takes the empty array, because [] is neither null nor undefined, so the section renders zero items with no error. | Dump the block object and look for sibling array fields where one is []. | Select the first array that actually has items, not the first that is non-nullish. ?? and || are both wrong: ?? keeps [], and || would also discard a legitimate 0 or "" if the same helper is reused for scalars. | | A boolean-looking flag never turns a section off (or on) | Entry booleans can arrive over the CDA as the strings "True" / "False" rather than booleans. "False" is a non-empty string and therefore truthy, so {flag && <Section/>} renders unconditionally and a gate meant to hide a section silently stops working. Affects any authored toggle: visibility flags, “open in new tab”, feature-present markers. | Log typeof on the flag, not just its value. | Compare explicitly: v === true \|\| v === 'true' \|\| v === 'True'. Put it in one shared helper rather than at each call site. | | Cards render but their titles/copy are blank, and the array length is right | The collection nests deeper than it looks. Each item is a single-key wrapper naming its block type ({ <block_type>: { <field>: [ {…} ] } }), so reading item.<prop> finds nothing while item still counts as an object and the length looks correct. | Print Object.keys(items[0]). A single key that is not _metadata means you are holding a wrapper, not the record. | Unwrap iteratively: while the object has exactly one non-_ key whose value is an object or a single-element array, descend into it. One level is not always enough: a wrapper around a reference array needs two. | | A reference path is declared correctly and still resolves to nothing | Two different failures look identical at the render. An unresolved reference is {uid} and nothing else. An empty one is []. Declaring more resolvedReferences paths fixes only the first. Seen where part of a card collection resolved and the rest held [] in that environment, capping what the page could ever render. | Fetch the entry with the path in include[] and inspect each item: a bare {uid} is a declaration gap, [] is a content gap. | Only the declaration gap is yours to fix. For the content gap the data is absent in that environment, report it as such rather than adding paths that change nothing. Re-check in an environment where the content is published before concluding the build is wrong. |

When the symptom maps to none of the above

These failure modes have no diagnostic surface in the codebase as of the current SDK alpha:

  • Manually typed binding path with a typo: silently falls through to fallback. Picker-only authoring avoids this. If you find a hand-edited binding string, re-set it through the picker.
  • Repeater item context lost inside a deeply nested Section Slot: propagation is implicit via DataCtxProvider. No warning if the context doesn’t reach the slot’s subtree.
  • Schema mismatch between Studio’s composition and runtime ComponentData you pass in code: the SDK uses fallback values, no validation error.
  • CSLP-append behavior when appendTags: false: content renders un-tagged with no UI indication.
  • Data source fetch timeout / network failure: picker may stall without a banner unless the adapter explicitly surfaces it.

If the symptom fits none of the rows above and is not in this silent-failure list, escalate to engineering with: the page URL, the bound field’s path from Properties, the actual data payload from DevTools Network, and the SDK version (grep '"@contentstack/studio-client"' package.json).

What This Skill is NOT

  • Not a guide to authoring bindings from scratch. Use build-connected-template / build-section.
  • Not a guide to wiring external data. Use wire-external-data / wire-component-default-data.
  • Not for live-site URL resolution (use troubleshoot-composition-resolution).
  • Not for canvas iframe / Studio editor failures (use troubleshoot-canvas).
  • Not for SSR / RSC boundary errors (use troubleshoot-ssr-rendering).