Skip to main content
This page describes how existing shops are migrated to the new Strapi data structure. Because the previous content sync is being switched to the new sync middleware, the format of the JSON files that Strapi passes on to the shop changes. Shop templates that are based on the old data structure have to be adjusted for this.
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): Part B – Migrating templates: the eight steps:

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”, fields title and content) looks like this in the two formats: mapping format – each input field is directly a JSON key:
schema format – each input field is an entry in a list, always with the same three keys:
Overview of the differences:
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 _sync key is not a content field but a technical addition of the sync middleware. _sync.contentType names 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 in componentno longer in __component as 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/attributes wrapper of the old structure is removed.
The structure therefore largely corresponds to the previous structure, but without the 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 _sync in 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:
An input mask itself can have content fields with names such as url or robots. These then appear as regular entries in fields and are something different from meta.url or meta.robots (same name, but different meaning and in some cases a different form). SEO values from the Meta-Info plugin always sit in meta and never in fields. Do not confuse the two when processing them.

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 fields list: it follows the schema definition and is irrelevant for rendering. Therefore, always access fields via name and 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.
The switch of file names to documentId affects every place in the template that loads a JSON file via a descriptive name. The documentId is the central, stable identity of a document. The numeric id is not suitable for that because it changes per language version.

Migrating templates: the eight steps

Three notes up front:
  1. 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.
  2. 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.
  3. Work behind the test-mode switch from the start (step 2). Otherwise you change the live output before you have verified it.
The examples use the schema format throughout because it involves the larger rework. Where the mapping format differs, this is noted in the respective step.
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 option source: "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 attributes and __component.
The result is a list of the templates that will be adjusted in the following steps. For each hit, note which Strapi document is loaded there – you will need the “file ↔ document” mapping in step 3.

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):
After (with a switch – scaffolding that you fill in steps 3 to 7):
All following steps work exclusively in the upper branch. How to invoke test mode in the shop is described under Switching shop test modes on/off.
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 option source: "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 $cCMSFile in 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.
The most commonly overlooked point: mapping files and self-built naming conventions. If your template assembles the file name only at runtime (for example from the called URL via its own mapping file), this mapping must be switched to documentId file names.

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 the attributes 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):
After (schema format):
The check {{ 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 component instead of __component – that applies to both formats.
  • In the schema format, the field values of a block sit in its own fields list.
Replace the existing render code according to this pattern: Before (old structure):
After (mapping format):
After (schema format, $cFields from step 4):
Replace the component names and fields in the example with those of your shop. The order of the blocks matches the editor’s arrangement and is output unchanged.
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. The data/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):
After (schema format, $cFields from step 4):
After (mapping format):
Newly available directly are also 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):
After (schema format):
Two points stand out here in particular:
  • robots is now an array and must be joined into a string for output in the meta tag. join is called as a function with a separator for that; the filter notation | join in the path examples on this page joins without a separator.
  • The SEO fields sit in meta, not in fields. The name/value object $cFields from step 4 does not contain them.
Newly available is also 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.
It is best to compare test-mode and live output of the same page directly side by side. Only when all pages have been checked, remove the switch from step 2: the test-mode branch becomes the regular code, and the {{ 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.