The new sync middleware is currently under development. This page describes the data structure that has already been confirmed.
Does the new data structure already apply to my shop?
The new sync middleware is introduced with the update to Strapi version 5 and is the standard from then on. It does not exist before that. This results in three cases:What this page covers and what it does not
This page covers the new output format of the JSON files, the differences from the old structure, and the template adjustments required for it. Not covered:- Creating or extending content in Strapi. That is independent of the migration. The basics are described under Basics & architecture of Strapi.
- Rendering new content building blocks. A template only ever renders the input masks, fields, and components that are implemented in it. If an editor creates a new input mask or a new component, the template has to be extended for it. Both before and after the migration. The procedure is described under Adapting templates for Strapi content.
Structure of this guide
The guide consists of two parts. Part A explains the basics, part B is the actual working instruction. Part A – Basics (reference part):- Terminology: Strapi in five sentences
- Why the data format is changing
- The two new formats compared
- The mapping format in detail and The schema format in detail
- Changes compared to the old structure
Terminology: Strapi in five sentences
These five terms are used throughout the page and are essential for understanding the guide.
In the following, field access means: the place in the template that reads the value of an input field from the loaded JSON file, for example the page title.
These terms are described in more detail under Basics & architecture of Strapi.
Why the data format is changing
The shop templates read content from the JSON files via the technical field names. Until now, the format of these files was closely coupled to the internal REST structure of Strapi. With every major Strapi version jump (most recently from version 4 to 5), this structure could change, which required adjustments in the shop and templates. The new sync middleware decouples the output format from the internal Strapi structure. WEBSALE thus specifies its own, fixed data structure that remains stable even after future Strapi updates. Existing shops must be migrated once to this new structure. After that, they are update-ready without the templates needing to be adjusted for each Strapi update. A second reason lies in Strapi itself: through the Content-Type Builder, authorized users can not only maintain content but also change the input masks. Fields can be created, renamed, or deleted.The two new formats compared
The new sync middleware writes every document into two files, in two different formats. Both contain the same content, only arranged differently. By default, both are generated in parallel so that a gradual switch is possible. The same document (“Terms and conditions”, fieldstitle and content) looks like this in the two formats:
mapping format – each input field is directly a JSON key:
Important: In both formats, the template location must be adjusted when an input field is renamed. No format protects against that. The only difference is whether the structure of the file changes (mapping) or just a value within it (schema).
The mapping format in detail
Structure
A content page with the input fields “title” and “content” looks like this:- Each input field is a JSON key at the top level. The value sits directly below it.
- The
_synckey is not a content field but a technical addition of the sync middleware._sync.contentTypenames the input mask the document comes from. - Content blocks appear as an array under their field name (here
content). Each block carries its component name incomponent– no longer in__componentas in the old structure – and its fields directly next to it. - Media are provided as a flat, normalized media object, identical to the schema format (see Media). The
data/attributeswrapper of the old structure is removed.
attributes wrapper and with the other changes from the comparison below.
What to watch out for with the mapping format
- Field names come through unfiltered. If an input field is renamed in Strapi, the JSON key changes. The affected template location must then be adjusted. This corresponds to the behavior you know from custom product data fields in the shop.
- No field type in the file. Unlike the schema format, there is no indication whether a value is text, a number, or a media object. The template has to know that.
- Reserved names. A field named
_syncin Strapi would collide with the technical key. Avoid leading underscores in field names.
Where the SEO data of the Meta-Info plugin sits in the mapping format has not yet been finally decided. Clarify this for your shop with your WEBSALE contact before you implement step 7 based on the mapping format. In the schema format, they sit in
meta.The schema format in detail
Every file of the schema format describes exactly one document, for example the page “Terms and conditions”. Each file consists of three parts:
These three parts together are called the “envelope”. This is the outer frame of every file, which always looks the same. Even when input masks are changed in Strapi.
For the migration itself, it is enough to know this base structure. The following subsections are the reference part for the concrete template adjustment.
The envelope
This envelope is identical for all input masks and documents. That also holds when fields are renamed, added, or deleted. Only the entries inside
fields change as a result.Administrative data and SEO fields in meta
meta contains the administrative fields of a document. They are deliberately kept separate from the content fields in fields:
If the Meta-Info plugin is active for a document (see SEO meta data for CMS pages),
meta additionally contains the SEO fields of the page:
The content fields in fields
fields is a list of all input fields of the document. Each entry has exactly three keys:
Two orderings need to be distinguished:
- Order of the
fieldslist: it follows the schema definition and is irrelevant for rendering. Therefore, always access fields vianameand never via position. - The order within an array value (content blocks, repeatable components, relation lists) is layout-critical. It exactly matches the editor’s arrangement and must be preserved when rendering.
The field types
Scalar types:value is the value directly.
Whether
richtext and blocks values arrive as raw data (Markdown or Blocks JSON) or as finished HTML is a deployment setting of the sync middleware. Clarify with WEBSALE which variant is active for your shop before you adjust the template.Media in the schema format
media: value is a normalized media object with fixed keys (url, alt, caption, name, mime, ext, width, height, size, formats). Strapi internals such as IDs, hashes, provider data, and timestamps are deliberately removed so that Strapi-internal changes do not reach the shop. If a value is missing, it is null. For multi-media, value is an array of such objects.
Components and relations
component: value contains its own fields list. For repeatable components, value is an array of such objects.
dynamiczone (content blocks): value is an array of blocks. Each block carries its component name in component, its id, and its own fields list.
relation: value contains the linked documents, recursively with their own fields list. A single relation returns an object, a multi-relation an array. The resolution depth is limited. Deeper relations that are no longer loaded appear as value: null. Self-referencing relations (for example nested navigations) are additionally cut off by a cycle protection. The same type is only resolved once per path.
Keep the load depth in mind in the template as well:
$wsExternalData.load truncates deeply nested structures via the maxDepth option. In the examples on this page, maxDepth: 20 is used. See $wsExternalData.Stability guarantee and its limit
The structure is stable: No matter what happens to the input masks, every file is{ contentType, meta, fields[] } and every field is { name, type, value }. Templates can rely on that 100%.
The meaning is not stable: If an editor renames a field title to titel, that comes through cleanly. But in name, it now reads titel. A template that specifically searches for title no longer finds anything and outputs nothing at that place. The mapping of field names to their meaning is an agreement between editors and template owners. The format cannot enforce it. The only exception are the SEO fields: through the Meta-Info plugin, they always sit at the same place in meta in the schema format, regardless of how the input masks are structured.
Changes compared to the old structure
The following overview compares the new structure with the old data structure of the previous content sync. It is the basis for the template adjustment in the next section. The “New structure” column shows the schema format. The changes to the input-mask identifier, the component identifier, the media,localizations, and the file names apply to both formats. For the SEO fields, only the storage in the schema format (meta) is confirmed; for the mapping format, it is still open.
Migrating templates: the eight steps
Three notes up front:- The code examples are patterns to transfer, not code to be copied unchanged. The “after” code replaces the corresponding existing location in your template – but inside the test-mode switch from step 2, so that the previous code initially remains in place for the live output.
- The “before” examples show a typical pattern of the old structure. Because the old structure was set up differently per shop, the actual locations in your shop may differ.
- Work behind the test-mode switch from the start (step 2). Otherwise you change the live output before you have verified it.
Prerequisite: The new files must already be generated for your shop, otherwise the test mode has nothing to work with. Check in the JSON directory whether there are files with
documentId names (<documentId>.json and <documentId>.schema.json). The old files with descriptive names remain alongside them during the transition phase. Activation and regeneration are initiated by WEBSALE.Step 1: Find the affected locations
When needed: Always. Search the template repository (GitLab) for all places where Strapi JSON is loaded or read:$wsExternalData.load(– loads a single file. Relevant are the calls with the optionsource: "system"and a path that points to the JSON directory.$wsExternalData.read(– reads a directory and returns a list of file names.- the subsequent field accesses on the loaded data, typically recognizable by
attributesand__component.
Step 2: Add a test-mode switch
When needed: Always, and specifically before the first content change. No second Strapi instance is needed for the check. Because the old files remain in place during the transition phase, old and new files sit side by side in the JSON directory. You can therefore switch between old and new in the same template: in test mode, the template loads and renders the new files, while the live output continues to work unchanged with the old ones. This keeps the migration safe at all times. The live page remains untouched until step 8.The switch covers not just the load call but the entire output block**.** The field accesses of the new structure (steps 4 to 7) do not fit the old files. If you only switched the file name, the live output would immediately be empty. Before (old structure, without a switch):
In larger templates, a switch per output location becomes hard to read. In that case, it is more practical to leave the old output template unchanged and maintain the new version as a dedicated file that is only included in test mode. The procedure is described under Adapting templates for Strapi content.
Step 3: Adjust the file names in the load calls
When needed: Always. The path stays, only the file name changes. The JSON files remain in the same directory as before: in the JSON directory of your shop, which the templates reach via the optionsource: "system" – for example json/Deutsch/ for the German-language content. Both new formats sit there side by side, in the same directory as the old file did before.
Instead of a descriptive name, each file carries the documentId of the document by default:
How to find the
documentId: Open the document in Strapi in the Content Manager. The documentId then appears in the browser’s address bar. In addition, it is present in every generated file of the schema format under meta.documentId.
Screenshot: Content Manager with an opened document, documentId highlighted in the address bar.
Where to adjust:
- In every
$wsExternalData.load(...)call that loads a Strapi file by its name – in the scaffolding from step 2, that is the value of$cCMSFilein the test-mode branch. - Not in
$wsExternalData.read(...)calls. These read a directory and remain unchanged. If your template further evaluates the read file names by content – for example, to derive the appropriate page from the name –, that logic must also be adjusted, because the names are no longer descriptive.
Step 4: Update field accesses
When needed: Always. The scope differs depending on the format. This refers to the place that reads the value of an input field from the loaded data – for example the page title. In the mapping format, only theattributes wrapper is removed. $cCMSData.attributes.title becomes $cCMSData.title. Nothing more needs to be done on the field accesses.
In the schema format, the values are no longer directly under their field name but as entries in the fields list. Convert this list once into a name/value object. Afterwards you access it via the field name as usual:
The load call from step 2 remains unchanged. Only the output part below it is replaced:
Before (old structure):
{{ if $cCMSData }} is not decorative: $wsExternalData.load returns null if the file is missing or invalid. Without this check, the page stays empty without any indication.
This pattern works identically at every level: for the document fields, inside components (value.fields), and for the blocks of the content blocks (see next step).
Step 5: Adjust content blocks (dynamic zone)
When needed: If your template renders the content blocks of a page. That applies to practically every content page with freely combinable components. Two things change:- The component identifier is now called
componentinstead of__component– that applies to both formats. - In the schema format, the field values of a block sit in its own
fieldslist.
$cFields from step 4):
Keep a
{{ else }} branch for unknown components, or add one. Otherwise a newly created block disappears silently from the page instead of standing out.Step 6: Adjust media accesses
When needed: Everywhere images or files from Strapi are output. Applies to both formats. Thedata/attributes wrapper around media is removed. Image URL, alt text, and responsive formats sit directly in the normalized media object. Additionally, the alt text is now called alt instead of alternativeText. Replace the existing image outputs according to this pattern:
Before (old structure):
$cFields from step 4):
width, height, and the responsive variants under formats, for example $cFields.logo.formats.thumbnail.url. Only output these values if they are populated: missing entries are null, and not the same formats are generated for every shop.
Step 7: Read SEO fields from meta
When needed: If your template outputs meta title, meta description, robots, or the SEO URL from the Strapi data. The examples show the schema format (for the mapping format, see the note under What to watch out for with the mapping format).
These values previously came from a content component (for example “Meta Information”) and in the future sit in the meta section of the file, with new field names (see comparison). Replace the existing accesses according to this pattern:
Before (old structure):
robotsis now an array and must be joined into a string for output in the meta tag.joinis called as a function with a separator for that; the filter notation| joinin the path examples on this page joins without a separator.- The SEO fields sit in
meta, not infields. The name/value object$cFieldsfrom step 4 does not contain them.
meta.hreflang with one entry per subshop that serves this language. If you want to output hreflang tags, that is the data source for it.
Step 8: Verify in test mode and go live
When needed: Always, as the last step. Open the shop in test mode (see Switching shop test modes on/off) and check every migrated page. Screenshot: shop in test mode showing a migrated content page. Checklist:- Are all content blocks output – and in the order the editor set in Strapi?
- Do all images appear including the responsive formats, with alt texts?
- Is the SEO output in the HTML head correct: meta title, meta description, robots?
- Are the pages reachable via their SEO URLs?
- Are there empty places where content used to be? That is the typical hint at a field name that no longer matches.
{{ else }} branch with the old load call and old output is removed.
When something does not work
For the switch date of your shop as well as the activation of the formats and the regeneration of the files for existing content (backfill), contact your WEBSALE contact.
