Dynamic Content: Sources, Loops and Field Mappings

Dynamic Content connects records supplied by an enabled provider to ordinary CMS blocks. The provider owns the data and its translations; you control the page layout, repeated template and field mappings. This guide describes the panel workflow in CMS 1.104.0 with source standard 1.0. The running example is the published plugin catalogue.

You can build these connections in the panel without writing API requests or typing block IDs. API access and credentials belong to the installed provider integration; this workspace selects sources already registered by that integration.

Find a source and understand its uses

Open Dynamic Content → Sources. Source details show the provider, declared data origin, collection or single-record type, supported languages, stable record key and available fields. A language declaration describes a contract; it is not a successful live connection test.

The Loops, Field connections and Issues views show where sources are used. Filter by site, publication state or search. Counts describe stored blocks and connections, not the number of generated cards. Opening this inventory reads configuration without requesting remote content. Shared Slot usages link to their existing editor; the guided destination workflow below uses page-owned slots.

The example integration exposes three different sources:

  • Published plugin catalog (webblocks-extensions-live::plugins): a collection for repeated cards.
  • Published plugin (webblocks-extensions-live::plugin): one selected plugin, such as a fixed record.
  • Catalog interface (webblocks-extensions-live::ui): the labels record supplies shared translated interface text.

These provider labels may remain English even when the panel is translated. Source record content uses the requested language.

Connect a source to a page

In the source details, choose Use on a page. Select the site and page, then Load destination. For the catalogue collection, select the Grid as the block containing the loop. Load that destination and choose its direct child Card as the repeated template.

On a published page, you can inspect and preview connections immediately. Before saving changes, choose Prepare staged draft, then select the destination in that draft. Its cloned blocks have their own IDs; use the visible selectors and review the resulting mappings. The published page remains unchanged.

If a draft has no suitable template, Create a starter grid and card adds an unconnected template to a page-owned slot. It preserves existing blocks and does not choose field mappings for you. Provider installation, source access and any API credentials are configured in the provider integration, not in this screen. See Plugin System for installation.

Choose the loop and repeated template

The loop belongs to the Grid; the Card is the template repeated once for each matching record. Image, Header, Plain Text and Button Link blocks inside that Card receive the current record. A fixed Header beside the Card stays editorial and cannot use that loop's current record.

The block tree marks the loop owner, repeated template and connected fields. Collapsed ancestors indicate that they contain dynamic content. Follow an indicator to the relevant loop or mapping. One stored Card can therefore produce ten public cards without ten editable Card copies.

Choose a compatible direct child, not a grandchild or a block from another page. Core Section, Container, Stack, Cluster, Grid and Slide accept otherwise-valid child templates; Slider requires Slide, Columns requires Column Item, Feature Grid requires Feature Item or Column Item, and Link List requires Link List Item. Card and Split do not offer this collection-container capability.

Section
└─ Container
   └─ Grid  [loop: Published plugin catalog]
      ├─ Header  [fixed editorial block]
      └─ Card  [repeated template]
         ├─ Image
         ├─ Header
         ├─ Plain Text
         └─ Button Link

Set filtering, sorting and pagination

For the catalogue example, set Maximum records to 50, Sort field to title, direction to ascending, and Records per page to 12; enable Paginate records. Fifty is the resolution cap, not a promise that fifty records exist. With ten matches and twelve per page, the preview has one page. Slider does not support this pagination control.

Only filter by fields the source declares. Published plugin catalog already selects published plugins in the provider; there is no need to invent a status field or maintain a second publication filter in CMS. Choose separate behaviors for When no records match and When the source fails: hide the template or keep its editorial version. Review both choices before publication.

For small collections, CMS filters and sorts the returned records. Query-capable providers execute the query themselves. Source details show which model applies.

Map fields: current record, fixed record or editorial value

In Template field mappings, choose a value mode for each destination field:

  • Current loop record: values change per card. Select the collection and a compatible field; the record is the current item.
  • Fixed source record: all cards can share one record's value. Select the source, then its record, then a compatible field.
  • Editorial value / fallback: use the text or media entered in the block. When a connected field resolves to no usable value, its editorial value can also serve as a fallback.

For example, bind the Header title to title, Plain Text content to summary, and the Button Link URL to url from the current loop record. For the button label, use the fixed Catalog interface source, record labels, field explore. Source, record and field are separate selectors; use Search records and fields to narrow the choices.

Connected blocks also expose a dedicated Dynamic Content tab. Their ordinary Block Fields values are editorial fallbacks. A text field cannot select a URL-only field, and current-record choices are limited to the selected template branch. Changing or removing a loop requires resolving affected mappings first; validation rejects orphaned or incompatible connections.

Connect images, alt text, captions and links separately

The source supplies an accessible image URL or a media reference that belongs to this CMS installation. A remote application's numeric image ID is not automatically a CMS media ID. CMS renders the image through its ordinary Image block; you do not copy the artwork into each Card.

For the catalogue Image block, use four independent connections:

  • Image source → current record's image_url.
  • Alternative text → image_alt.
  • Optional caption → image_caption.
  • Link → url.

A file can be shared across languages while alt text and captions differ. Alt text describes relevant visual information; a caption provides optional visible context. Do not copy one into the other. In the API, Image subtitle is alt text and Image title is the caption. Check that the image loads and that both texts suit its role; a field typed as URL does not prove it points to an image.

Let the provider supply translations

Standard 1.0 requires every provider source to support all six CMS languages: en, tr, de, es, it and fr, even when one site enables fewer languages. CMS requests the current site's enabled language; the provider supplies the corresponding record text, image alt text, caption and shared labels. You do not translate each generated card in the CMS page editor.

For example, the same catalogue Card uses summary in English and its provider-owned Turkish translation when the requested locale is tr. Its button label comes from Catalog interface / labels / explore in that same locale. The image file may remain identical.

The static parts you wrote around the loop still use normal page and block translations. Source validation checks language markers, stable keys and field types; it cannot certify translation quality. Preview every enabled language and check the actual copy. A provider must not label unchanged English content as another language.

Check the configured preview before saving

Choose Preview language, then Check source and preview. A private new-tab preview renders the actual block template with your current unsaved mappings, filter, sort, limit and pagination settings. It does not save or publish the page. Opening source details alone does not perform this check.

Confirm the record count, titles, summaries, button labels and destinations. Verify loaded images, localized alt text and captions. Distinguish a valid empty result from denied access, a response that violates the source contract, and an unavailable provider. The preview reports those states separately. Check provider access or settings when appropriate; do not replace missing data with guessed translations.

If you alter settings, generate a fresh preview. With pagination, move between windows to review the configured result. A raw API sample is useful for diagnosis but is not the complete visual preview of your Card.

Save a draft, then review publication separately

Save draft connections updates only the draft and records a revision. If the page changed after you opened it, reload and review before saving. Invalid configuration leaves the previous settings intact. Recheck the rendered draft, including all enabled languages, and use the normal editorial workflow for publication or staged promotion.

A collection repeats blocks within an existing page. A card's detail destination must already exist or be supplied by a separately configured Automatic Pages rule. Connecting a loop does not create or activate that rule. The published-plugin provider determines which records are available; CMS should not maintain a second manual copy of that catalogue.

Developer reference: source standard and entity contract

An enabled plugin registers sources on PluginDefinition::contentSources(...). An entity resolver implements ContentSourceResolver: options() provides safe readable record choices keyed by stable identity; resolve() returns a record for the site, page, locale and preview context. A collection resolver implements ContentCollectionSourceResolver; large collections can implement QueryableContentCollectionSourceResolver using ContentCollectionQuery and ContentCollectionResult for query execution, totals and page windows.

The example declaration below belongs to a provider integration, not the page editor. Each resolved record must include _locale equal to the requested locale, a nonempty string key declared by recordKey, and every declared field. Use null for unavailable field values; collection keys must be unique. Field types are text, rich_text, url or media. A media value is a positive CMS media ID; other non-null values are strings. URL values must satisfy the source URL contract.

ContentSourceDefinition::collection('catalog::items')
  ->label('Catalog items')
  ->resolver(CatalogItems::class)
  ->standard(
    locales: ['en', 'de', 'tr', 'es', 'it', 'fr'],
    description: 'Localized catalog records.',
    origin: 'Catalog application',
    recordKey: 'id',
  )
  ->fields([
    'id' => ['type' => 'text', 'label' => 'Record ID'],
    'title' => ['type' => 'text', 'label' => 'Title'],
    'image_url' => ['type' => 'url', 'label' => 'Image'],
    'image_alt' => ['type' => 'text', 'label' => 'Alternative text'],
    'image_caption' => ['type' => 'text', 'label' => 'Caption'],
  ]);

Unversioned sources are marked Legacy — standard update needed. Existing selections can remain unchanged, but new connections require the multilingual standard. Providers retain ownership of their tables and records; CMS owns layout and rendering. Plugin containers opt in with contentCollectionTemplate() and render resolved children through ContentCollectionRenderer::children($block); manifest declarations use content_collection.enabled and optional content_collection.child_types. Plugins should reuse core Card, Grid and Image renderers rather than reimplement them.

Developer reference: access, caching and fallbacks

Attach a ContentSourceAccessPolicy with accessPolicy(...) for restricted records; CMS checks the site, page, locale, preview state and authenticated actor before resolution. Entity failures or empty values retain the editorial field fallback. Collection empty-result and resolver-error behavior is selected separately; denied or removed sources retain the stored tree. Issues and block controls identify unavailable connections without silently deleting them.

cacheFor($seconds) enables bounded result caching; zero, the default, disables it. Keys include source, site, page, locale, record/query arguments and source settings. Preview bypasses this cache. After a domain write, call app(ContentSourceRuntime::class)->invalidate('plugin-handle::source') to invalidate that source's cached variants. Bindings and collection configuration are preserved by page duplication, revisions and site export/import; executable provider results are not copied into those packages.

Developer reference: Internal Content API

GET /webadmin/api/content-sources discovers accessible enabled sources. Add block_id to inspect compatible targets and collection capability. POST /webadmin/api/content-sources/{source}/preview resolves one selected entity or at most five collection sample records, exposing declared fields only. This sample endpoint does not render the complete configured template.

PATCH /webadmin/api/blocks/{block} accepts content_bindings and content_collection at the top level or beneath settings. Each object replaces its corresponding configuration; null clears it. Validation covers access, record choices, field types, direct-child ownership and template capability. Invalid requests return 422 invalid_content_source_configuration. Use an owned draft or staged page and render the result afterwards; follow the Internal Content API authorization and publication rules. Image subtitle and title retain their distinct alt/caption meanings.