Documentation
Feedback
Guides
API Reference

Guides
Guides
CMS

Understanding CMS architecture and schema declarations

Learn how the CMS separates read and write operations with CQRS, defines component schemas using modular files, and organizes project folders differently from the Headless CMS (legacy).

The CMS is built on the CQRS (Command Query Responsibility Segregation) architectural pattern, which separates read operations (queries) from write operations (commands) into different models or services.

With CQRS, content editors get a responsive editing experience while your store gets fast content delivery, without either affecting the other, covering the following aspects:

  • Performance under load: Read and write operations scale independently.
  • Optimization trade-offs: Each plane is optimized for its specific purpose.
  • Reliability: Failures in one plane don't affect the other.
  • Technology choices: Best technology for each plane's requirements.

Data Plane (Content Delivery)

The Data Plane is optimized for high-performance content delivery to storefronts and has the following key characteristics:

  • Read-only: Only serves published content, no write operations.
  • ETag caching: Supports efficient cache validation.
  • Slug-based lookups: Retrieve content by URL path.
  • Context filtering: Filter content by locale or other contexts.
ComponentPurpose
Data Plane APIRead-only RESTful API optimized for fast content retrieval.
DatabaseStores published content in a format optimized for queries.

Schema Registry

The Schema Registry manages component schemas and Content Type definitions and has the following key operations:

  • Upload and store schema bundles.
  • Provide schema definitions to CMS interface for form rendering.
ComponentPurpose
CLI PluginCommand-line tool for generating and uploading schemas.
Registry APIStores and serves schema definitions.
Schema StorePersists schema versions and metadata.

Content data management through the architecture

  1. Schema upload (development time):

    • Developer runs vtex content upload-schema.
    • CLI sends schema to Schema Registry API.
    • Schema is stored and made available to Control Plane and Data Plane.
  2. Content editing (authoring time):

    • Editor creates/edits content in the CMS Admin interface.
    • CMS interface (Admin UI) calls Control Plane API.
    • Content is saved to Control Plane database.
    • Changes are isolated on a branch until merged.
  3. Content publishing (publish time):

    • Editor merges branch to main.
    • Event triggers sync to Data Plane and build webhook.
  4. Content delivery (runtime):

    • Content is fetched from the Data Plane API.
    • Data Plane returns published content with ETag headers.

Schema declarations

Schemas define the structure of content in the CMS. They specify which fields a section exposes, the data types those fields accept, and the validation rules that apply.

When a content editor creates or edits content in the CMS interface in the Admin, schemas are used to:

  • Render the editing form: Each field in the schema becomes an input field in the interface.
  • Validate content: Ensure required fields are filled, and data types are correct.
  • Define relationships: Schemas determine which sections can be used within specific Content Types.

Both the Headless CMS (legacy) and the CMS rely on JSON Schema to define schemas. However, they differ in how schemas are organized, authored, and deployed.

Schema declarations between CMS and Headless CMS (legacy)

The table below summarizes the key differences between schema declarations in the Headless CMS (legacy) and the CMS:

AspectHeadless CMS (legacy)CMS
File organizationSingle sections.json file.Individual .jsonc files per component.
Section identifiername field.$componentKey field.
Display nameschema.title$componentTitle field.
InheritanceNot supported.$extends for schema inheritance.
Base componentN/AExtends #/$defs/base-component.
Image widget"ui:widget": "image-uploader""ui:widget": "media-gallery"
File extension.json.json or .jsonc
DeploymentSynced via vtex cms sync.Uploaded via vtex content upload-schema.

Schema format comparison

To understand the practical differences, let's compare how the same Banner component is defined in the CMS and the Headless CMS (legacy).

Headless CMS (legacy) format

In the Headless CMS (legacy), all sections are defined in a single sections.json file. Each section has a name and a nested schema object.

  • All components are defined in a single array within one file.
  • Component identified by the name field.
  • Display name comes from schema.title.
  • No inheritance between components.

_36
// sections.json (single file with all sections)
_36
[
_36
{
_36
"name": "Banner",
_36
"schema": {
_36
"title": "Banner",
_36
"description": "A banner component for the storefront",
_36
"type": "object",
_36
"required": ["title"],
_36
"properties": {
_36
"title": {
_36
"title": "Title",
_36
"type": "string"
_36
},
_36
"image": {
_36
"type": "object",
_36
"title": "Image",
_36
"properties": {
_36
"src": {
_36
"type": "string",
_36
"title": "Image",
_36
"widget": {
_36
"ui:widget": "image-uploader"
_36
}
_36
},
_36
"alt": {
_36
"type": "string",
_36
"title": "Alternative Label"
_36
}
_36
}
_36
}
_36
}
_36
}
_36
}
_36
// ... all other sections in the same file
_36
]

CMS schema format

In the CMS, each component is defined in its own .jsonc file. The schema is flatter and includes additional metadata fields for identification, display, and inheritance.

  • Each component in its own file (cms_component__ComponentName.jsonc).
  • Component identified by $componentKey field.
  • Display name specified by $componentTitle.
  • Inherits from base schema via $extends.

_31
// cms/components/cms_component__Banner.jsonc
_31
{
_31
"$extends": ["#/$defs/base-component"],
_31
"$componentKey": "Banner",
_31
"$componentTitle": "Banner",
_31
"type": "object",
_31
"required": ["title"],
_31
"properties": {
_31
"title": {
_31
"title": "Title",
_31
"type": "string"
_31
},
_31
"image": {
_31
"type": "object",
_31
"title": "Image",
_31
"properties": {
_31
"src": {
_31
"type": "string",
_31
"title": "Image",
_31
"widget": {
_31
"ui:widget": "media-gallery"
_31
}
_31
},
_31
"alt": {
_31
"type": "string",
_31
"title": "Alternative Label"
_31
}
_31
}
_31
}
_31
}
_31
}

CMS schema keywords

The CMS introduces additional schema keywords for a more modular, reusable, and maintainable schema definition.

KeywordPurposeExample
$extendsInherit properties from base schemas."$extends": ["#/$defs/base-component"]
$componentKeyUnique identifier for the component."$componentKey": "Banner"
$componentTitleDisplay name shown in CMS interface."$componentTitle": "Promotional Banner"
$refReference another schema definition."$ref": "#/components/SEO"

For standard JSON Schema terms, see the JSON Schema keywords reference.

Schema inheritance with $extends

The $extends property allows sections to inherit properties from one or more base schemas, promoting code reuse and consistency. Schema inheritance provides benefits for content modeling, including:

  • Consistency through a shared base structure across components.
  • Maintainability by propagating changes from base schemas.
  • Reusability via domain-specific base schemas (for example, promotional or navigational components).
  • Type safety through schema-compatibility validation.
Example: Inheriting from multiple schemas

_16
{
_16
"$extends": [
_16
"#/$defs/base-component",
_16
"#/components/PromoBase"
_16
],
_16
"$componentKey": "PromoBanner",
_16
"$componentTitle": "Promotional Banner",
_16
"properties": {
_16
// Inherits: id, name, description, data (from base-component)
_16
// Inherits: startDate, endDate (from PromoBase)
_16
"discountPercentage": {
_16
"title": "Discount Percentage",
_16
"type": "number"
_16
}
_16
}
_16
}

Folder structure

The CMS introduces a modular folder structure that aligns with modern development practices. Instead of consolidating all schemas into a single file, the new structure separates components and Content Types into individual files, making it easier to manage, review, and version-control your content definitions.

The shift from monolithic files, as in the Headless CMS (legacy), to individual files addresses the following issues:

  • Version control conflicts: With a single sections.json file, multiple developers editing different components often caused merge conflicts. Individual files mean each component can be edited independently.
  • Code review clarity: Reviewing changes to a single component is straightforward when each component has its own file, rather than searching through a large JSON array.
  • Maintainability: Finding and updating a specific component is faster when you can navigate directly to cms_component__Banner.jsonc instead of scrolling through hundreds of lines in sections.json.
  • Comments and documentation: The .jsonc format allows inline comments, enabling developers to document schema decisions directly in the file.

Headless CMS (legacy) folder structure

In the Headless CMS (legacy), all schema definitions are consolidated into two main files within a subdirectory, for example, faststore:


_12
your-faststore-project/
_12
├── cms/
_12
│ └── faststore/
_12
│ ├── sections.json # All component schemas in one file
_12
│ └── content-types.json # All Content Type definitions
_12
├── src/
_12
│ └── components/
_12
│ ├── Banner/
_12
│ │ └── Banner.tsx
_12
│ └── ProductShelf/
_12
│ └── ProductShelf.tsx
_12
└── ...

CMS folder structure

The CMS organizes schemas into individual files. Below are two common approaches:

Option 1: Centralized schemas

Keep component schemas in a dedicated cms/components/ directory:


_18
your-store/
_18
├── cms/
_18
│ ├── components/ # Individual component schemas
_18
│ │ ├── cms_component__Banner.jsonc
_18
│ │ ├── cms_component__MegaMenu.jsonc
_18
│ │ ├── cms_component__ProductShelf.jsonc
_18
│ │ └── cms_component__RecommendationShelf.jsonc
_18
│ └── pages/ # Content type definitions
_18
│ ├── cms_content_type__landingPage.jsonc
_18
│ ├── cms_content_type__home.jsonc
_18
│ └── cms_content_type__pdp.jsonc
_18
├── src/
_18
│ └── components/
_18
│ ├── Banner/
_18
│ │ └── Banner.tsx
_18
│ └── ProductShelf/
_18
│ └── ProductShelf.tsx
_18
└── ...

Option 2: Co-located schemas

Place component schemas alongside their implementation files:


_15
your-store/
_15
├── cms/
_15
│ └── pages/ # Content type definitions
_15
│ ├── cms_content_type__landingPage.jsonc
_15
│ ├── cms_content_type__home.jsonc
_15
│ └── cms_content_type__pdp.jsonc
_15
├── src/
_15
│ └── components/
_15
│ ├── Banner/
_15
│ │ ├── Banner.tsx
_15
│ │ └── cms_component__Banner.jsonc # Schema co-located with component
_15
│ └── ProductShelf/
_15
│ ├── ProductShelf.tsx
_15
│ └── cms_component__ProductShelf.jsonc # Schema co-located with component
_15
└── ...

File naming conventions

The CMS uses a consistent naming pattern that makes file purposes immediately clear:

ItemHeadless CMS (legacy)CMS
Component schemasAll in sections.jsoncms_component__ComponentName.jsonc
Content typesAll in content-types.jsoncms_content_type__typeName.jsonc
File extension.json.jsonc (allows comments)
Locationcms/faststore/cms/components/ and cms/pages/, or co-located with component code
Contributors
1
Photo of the contributor
Was this helpful?
Yes
No
Suggest Edits (GitHub)
Contributors
1
Photo of the contributor
Was this helpful?
Suggest edits (GitHub)
On this page