Dynamic sources
A dynamic source stores a typed data reference in a section or block setting instead of a fixed value. The storefront resolves that reference against the resource context of each render, so one product template can show different content for every product.
{{ product.metafields.custom.care_instructions.value }}The reference above is persisted as the setting value. It is not evaluated by the seller UI and it is not a copy of the current product’s content.
Connect or insert a source
In the visual editor, a compatible setting can show two actions:
- Connect dynamic source replaces the complete setting value with one source. Use it for images, URLs, colors, resource pickers, and text that should come entirely from data.
- Insert dynamic source adds a source inside a text-like value. Static text can appear before or after it.
For example, an inserted source can persist this value:
Made by {{ product.vendor }}On a product whose vendor is Sellerlane Studio, the setting resolves to:
Made by Sellerlane StudioThe theme still renders the resolved setting normally:
{% if section.settings.subtitle != blank %} <p>{{ section.settings.subtitle }}</p>{% endif %}Supported setting types
The editor offers dynamic sources only for the following setting types.
| Setting family | Supported setting types | Required source shape |
|---|---|---|
| Text | text, richtext, inline_richtext | Compatible scalar value, or a supported list of scalar text values. |
| URL and color | url, color | A URL or color value with the matching metafield type. |
| Media | image_picker, video | An image/video field or a matching file_reference. |
| One resource | product, collection, page, article, metaobject | A matching single resource or single-reference metafield. |
| Resource list | product_list, collection_list, metaobject_list | A matching list-reference metafield. |
| Sellerlane extension | blog | A direct blog context only; persisted blog_reference metafields are not supported. |
metaobject and metaobject_list settings also enforce the
metaobject_type declared in the setting schema. A reference to another
definition type is rejected rather than coerced.
Types that render references literally
textarea, html, checkbox, number, and range do not accept dynamic
sources. A value such as {{ product.title }} in one of those settings remains
literal text; it is not executed.
The liquid setting type is different: it evaluates merchant-authored Liquid
and is not a dynamic-source field. Do not use it merely to bypass type
compatibility.
Global values in config/settings_data.json are also not connectable in the
visual editor. Global settings do not have one stable page resource or
closest.* scope. Put page-dependent bindings in a section or block setting.
Source and setting compatibility
The picker hides incompatible fields, and both save-time and render-time validation check the type again.
text accepts single-line text, lists of single-line text, numbers, dates,
measurements, ratings, and money. inline_richtext accepts the same
inline-safe shapes plus links. richtext additionally accepts multi-line
text and rich-text metafields.
image_picker accepts only image media. video accepts only video media.
A generic file_reference whose media kind does not match is rejected.
A singular setting requires a singular reference of the same resource
type. A *_list setting requires a list reference. metaobject bindings
must also match the setting’s declared metaobject definition type.
Examples of invalid pairings include a collection reference connected to a
product setting, a list of products connected to a singular product
setting, and a PDF file connected to an image_picker.
Available contexts
The picker is context-sensitive. It offers only contexts that can exist at the selected section or block instance.
| Context | Example access path | When it is available |
|---|---|---|
| Template resource | product, collection, page, article, blog | On the matching JSON template. |
| Section resource setting | section.settings.featured_product | When another section setting declares that resource type. |
| Block resource setting | block.settings.card_product | When another setting on the selected block declares that resource type. |
| Closest resource | closest.product or closest.metaobject["material"] | In a theme block rendered inside that resource scope. |
| Brand | shop.brand | Storewide brand fields and brand metafields. |
| Metaobjects browser | metaobjects["material"]["cotton"] | For storefront-enabled definitions and entries compatible with the setting. |
The current setting is removed from its own candidate contexts, preventing a
self-reference. In repeated theme blocks, the canvas selection must identify
the exact rendered block instance before the editor uses its closest.*
context. The editor does not silently take the first loop instance.
Which backend owns the picker state?
The state is deliberately split by responsibility.
| Data | Authority | Why |
|---|---|---|
| Durable draft theme files and saved setting values | Seller backend and the pinned draft artifact | This is the editable source of truth and the publish boundary. |
| Metafield definitions, metaobject definitions, and metaobject entry choices | Seller backend, fetched through the buyer editor API | These are catalog definitions, not page-render state. |
Actual template, section, block, and closest.* scope for the visible preview | Buyer backend render | Only the renderer knows the exact runtime nesting and repeated block instance. |
| Picker UI selection and search text | Seller frontend | Ephemeral interaction state only; it is never authorization. |
Therefore, the runtime scope must come from the buyer preview, not be
reconstructed by the seller backend. During a render, the buyer emits a scope
graph pinned to themeId, versionId, draft revision, manifest hash, and a
render revision. The seller frontend accepts it only from the expected preview
window/origin and only for the active snapshot. It then sends the selected frame
back to the buyer’s editor-only datasource endpoints.
The buyer endpoint requires a valid editor token, checks that the snapshot still matches the active draft, reloads the selected section/block schema from the pinned theme artifact, and verifies that the selected context is still offered. The seller backend validates the expression and catalog compatibility again when the draft is saved. No client-supplied scope graph grants storefront data access on its own.
Exact expression grammar
Dynamic sources are a restricted data-path language, not arbitrary Liquid. The editor normally writes the expression, but theme tooling should follow the same rules.
Resource fields
These first-hop fields are supported:
| Root | Supported direct fields |
|---|---|
product | title, vendor, description, url, featured_image, collections, metafields |
collection | title, image, description, url, products, metafields |
page | title, url, content, metafields |
article | title, url, author, content, excerpt, comments_count, image, metafields |
blog | title, url, metafields |
A direct field cannot be followed by arbitrary descendants. For example,
product.title.anything is invalid.
Metafields must end in .value
Use this shape:
{{ product.metafields.custom.care_instructions.value }}Bracket notation is valid for supported persisted names:
{{ product.metafields["product.details"]["wash-care"].value }}The object-only form below is not a valid dynamic source:
{{ product.metafields.custom.care_instructions }}That object form remains useful in ordinary theme Liquid, but a dynamic setting needs the typed value.
Metaobjects
Select a specific entry and field:
{{ metaobjects["material"]["organic-cotton"]["display-name"].value }}Or use the closest metaobject supplied by a theme block:
{{ closest.metaobject["material"]["display-name"].value }}system is reserved and cannot be traversed as an authored dynamic-source
field. Type handles, entry handles, namespaces, keys, and field keys are
validated before lookup.
Lists of metaobjects
The picker can project a field from each entry in a list-reference metafield.
The persisted expression uses structured map pairs:
{{ product.metafields.custom.materials.value | map: "display-name" | map: "value" }}This is the only permitted filter-like syntax. Every traversed metaobject field
must be followed by map: "value". Arbitrary filters such as compact,
where, or join are rejected inside a dynamic source. Format the resolved
setting later in ordinary theme Liquid when needed.
Common examples
Product metafield in a text setting
Schema:
{% schema %}{ "name": "Care note", "settings": [ { "type": "text", "id": "care_note", "label": "Care note" } ], "presets": [{ "name": "Care note" }]}{% endschema %}Saved setting value:
{{ product.metafields.custom.care_instructions.value }}Render it defensively:
{% if section.settings.care_note != blank %} <p>{{ section.settings.care_note }}</p>{% endif %}Product-reference metafield
For a product setting, connect a singular product_reference metafield:
{{ product.metafields.custom.pairs_with.value }}Then use the resolved Drop:
{% assign paired_product = section.settings.paired_product %}{% if paired_product %} <a href="{{ paired_product.url }}">{{ paired_product.title }}</a>{% endif %}Image file metafield
For an image_picker, connect an image-valued file_reference:
{{ product.metafields.custom.size_chart.value }}{% if section.settings.size_chart %} {{ section.settings.size_chart | image_url: width: 1200 | image_tag }}{% endif %}Resource from another setting
A section can first select a fixed featured product and let another setting pull a field from it:
{{ section.settings.featured_product.title }}The current setting cannot point to itself, and only schema-declared resource settings are exposed as contexts.
Missing data and failures
At storefront render time, dynamic sources fail soft. A deleted resource, missing metafield, blank reference, incompatible legacy value, or unavailable template resource resolves to blank/empty rather than exposing an internal error. Themes must handle that state:
{% if block.settings.badge != blank %} <span class="badge">{{ block.settings.badge }}</span>{% endif %}Save-time behavior is stricter. The seller backend rejects malformed grammar, unsupported roots, unsafe keys, incompatible setting/metafield types, a wrong metaobject definition type, and limit violations. This keeps a bad binding out of a publishable draft even though runtime remains defensive for old/imported data.
Limits
| Scope | Limit |
|---|---|
| One setting | 50 dynamic-source references |
| One JSON template | 100 references across its sections and blocks |
| One section group | 100 references across its sections and blocks |
| Static-section editor preflight | 50 references |
| One datasource path | 12 path segments |
| Metaobject entry picker page | 1–100 entries; 50 by default, with cursor pagination |
Limits count references, not characters. A text value containing three
{{ ... }} references consumes three slots.
Preview refresh and stale-scope errors
The editor invalidates its scope graph as soon as a preview update starts. It accepts the replacement graph only after the buyer finishes that exact render revision. This prevents a picker opened mid-refresh from binding to the previous section tree.
If the datasource endpoint returns 409 EDITOR_SCOPE_CONFLICT, the preview and
editor no longer refer to the same pinned draft snapshot. Finish or retry the
preview render, then reopen/reload the picker. The client should not reuse the
old runtime_scope frame. Authentication failures and catalog-unavailable
errors are shown separately and are retryable only when the response marks them
as retryable.
Test a dynamic source
-
Create the metafield or metaobject definition and fill a value on a real test resource.
-
Open the matching preview page, select the exact section or repeated block instance, and connect the source.
-
Confirm the canvas refresh shows the value, save, reload the editor, and confirm the binding remains connected.
-
Preview a second resource with another value to prove the setting is dynamic, not copied.
-
Clear the field and delete a referenced test resource. Confirm the section’s blank-state branch renders without a page error.
-
Run the theme validation/release suite before publishing so the buyer parser, seller save validator, picker compatibility, persistence, and runtime render all exercise the same expression.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The braces appear on the page | The setting type is not dynamic-source compatible. | Use a supported section/block setting type. |
| A metafield is absent from the picker | Its owner, value type, list shape, media kind, or metaobject type is incompatible. | Check the setting schema and metafield definition. |
The picker shows the wrong closest.* resource | The sidebar selection does not identify the rendered repeated-block instance, or the preview graph is stale. | Select the block on the canvas and let the current preview finish. |
| The value is blank only on some templates | That template has no matching primary/closest resource or the resource field is empty. | Add a blank-state branch or choose a context available on every target template. |
| Save rejects a value that an old theme rendered | Runtime soft-failure is preserving imported data, while the current save contract is stricter. | Reconnect using the picker so it writes the canonical expression. |
| The picker asks to reload | The draft revision/manifest changed or catalog loading failed. | Reload after the current save/render completes; do not reuse a stale frame. |