Linked-Schema Matching Rules

View as Markdown
Last updated July 17, 2026

When a section is dropped onto a template, Studio looks for fields on the template's connected content type that structurally match the section's linked schema. This page is the reference for the matching algorithm: every rule, every edge case.

For the concept-level explanation, see Linked schema and Auto-binding.

The Match Question Studio Asks

For each candidate field on the template's content type schema, Studio asks:

Could a section configured for <section's linked field> bind to <this template field> and have its internal bindings resolve correctly?

The answer is yes when the structure matches. Field names don't have to be the same, and Studio builds a positional remap by data type when names differ.

Per-Data-Type Matching Rules

data_type === "group" or "global_field" (and multiple flag agrees)

A match requires:

  1. Same number of immediate children
  2. The sorted set of children's data_types is identical

The child names can differ; Studio builds a positional remap (e.g. section's heading ↔ page's title) when names don't match.

Click to enlarge

data_type === "blocks" (modular blocks)

Looser rule: a match is declared if the section's blocks union and the template field's blocks union share at least one block UID.

Click to enlarge

Why looser: sections typically wrap their Repeater in a ConditionBlock that narrows to one specific block UID, so they care that the targeted block exists on both sides, not whether the unions are equal. Strict set equality would refuse the common recursive-shape pattern where the template field's nested modular_blocks allows only the inner block type while the section's modular_blocks allows several at root.

data_type === "reference"

Strict rule: a match requires the section's reference_to union and the template field's reference_to union to be equal as sorted sets. Every CT in one must be present in the other, and vice versa. Order in the schema doesn't matter (the comparison sorts both sides first), but the membership must be identical.

Click to enlarge

Why strict (unlike blocks): references have no ConditionBlock narrowing mechanism; at runtime a reference field can resolve to any CT in its union, and the section's internal bindings assume the exact union it was authored against. Accepting a subset would let bindings reference CTs the template's field can't actually hold; accepting a superset would let the template surface CTs the section doesn't know how to render. Sorted-set equality is the only safe rule.

If the unions differ even by one CT, the section won't auto-bind. Fix it by aligning the reference_to lists on both content types, or by adding a second linked-schema entry to the section that targets the alternate shape.

Scalar types (text, number, file, etc.)

Same data_type plus same multiple flag. No child structure to check.

Click to enlarge

The multiple Flag

Across all types, the section's multiple flag must match the template field's. A single-value field never matches a multi-value field, even if the data types align.

Three Drop Outcomes

After applying the rules, Studio computes the match count and resolves to one of three outcomes:

Matches foundOutcome
Exactly 1Auto-picked. The section binds to that field without user input. The right-panel Settings → Properties still surfaces a DATA SOURCE dropdown showing the bound field as the only option (the control is the same on every section drop; the menu just has one entry in this case).
More than 1Studio binds to one (the first in schema order). The right-panel Settings → Properties shows the same DATA SOURCE dropdown with all matches; the author can switch. Per-instance: dropping the same section twice on the same template lets each instance bind to a different field.
ZeroSection drops unbound. A warning indicates no compatible field. Author can manually pick from compatible fields, or move the section to a different scope.

Click to enlarge

Scope-Aware Matching

Studio doesn't only check the template's top-level fields. It walks into the scope at the drop location:

Drop locationScope walked
Root of the templateTop-level fields of the template's connected content type
Inside a RepeaterSchema of the Repeater's iteration item
Inside a Modular Block (under ConditionBlock)Fields of that specific block
Nested combinationsInnermost scope; scopes chain

The matcher runs against the resolved scope's schema, not the template's top-level schema. So a section anchored on gf_card can match an items[] repeater of gf_card (root scope), match a card field inside a CTA block (block scope), or scope-root-match the iteration item itself when iterating over a list of gf_card.

Scope-root match (special case)

When the surrounding scope itself mirrors the section's shape (e.g. a Repeater iterating a list of gf_featured_card, or a Modular Block whose schema is the Global Field), the section binds directly to the scope root. No wrapper field needed. The section "becomes" the iteration item.

This is what makes the section-inside-Repeater-of-its-own-shape pattern work cleanly. See Card grid with slots recipe.

The Positional UID Remap

When a group or global_field match is declared and the child names differ, Studio builds a remap by position within data-type:

Click to enlarge

Bindings inside the section that reference template.heading now resolve through page.title at render time. The author doesn't see this; the section's UI says template.heading; Studio handles the translation.

The remap is shallow (one level deep)

The remap only translates top-level child names. Nested groups must use the same name on both sides.

Click to enlarge

For nested shapes, prefer Global Fields. Embed the same Global Field everywhere; the structure is literally identical and the remap question never comes up.

What the User Override Looks Like

If Studio's auto-pick is wrong (or if you want a non-default match), the section's right-panel Settings shows a Linked Field dropdown when multiple matches exist. Picking a different field:

  1. Updates the section's binding for this drop instance
  2. Builds a new positional remap against the chosen field
  3. Stores the override on the template (not on the section composition)

The override is per-instance; different drops of the same section can target different fields.

What's NOT Supported

Documented to avoid surprises:

  • Renaming a nested group's children with the remap. Only top-level remap is supported.
  • Reordering a section's binding to a different field of the same shape via prop. Use the right-panel Linked Field dropdown instead.
  • Cross-CT match without a linked schema entry. A section with linked_schemas: [blog_post] will not match product's fields. Add product to the section's linked schemas to enable matching there.

Algorithm Summary (for the Curious)

def match(section_field, page_scope):
    matches = []
    for page_field in page_scope:
        if not same_data_type(section_field, page_field): continue
        if not same_multiple_flag(section_field, page_field): continue
        if page_field.data_type == "blocks":
            if section_field.blocks ∩ page_field.blocks: matches.append(page_field)
        elif page_field.data_type == "reference":
            if sorted(section_field.refs) == sorted(page_field.refs): matches.append(page_field)
        elif page_field.data_type in ("group", "global_field"):
            section_children = sorted(child.data_type for child in section_field.schema)
            page_children    = sorted(child.data_type for child in page_field.schema)
            if section_children == page_children: matches.append(page_field)
        else:
            matches.append(page_field)
    return matches

This is illustrative; actual implementation handles edge cases like missing schemas and scope-root variations.

See Also