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.
| Component | Purpose |
|---|---|
| Data Plane API | Read-only RESTful API optimized for fast content retrieval. |
| Database | Stores 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.
| Component | Purpose |
|---|---|
| CLI Plugin | Command-line tool for generating and uploading schemas. |
| Registry API | Stores and serves schema definitions. |
| Schema Store | Persists schema versions and metadata. |
Content data management through the architecture
-
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.
- Developer runs
-
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.
-
Content publishing (publish time):
- Editor merges branch to main.
- Event triggers sync to Data Plane and build webhook.
-
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:
| Aspect | Headless CMS (legacy) | CMS |
|---|---|---|
| File organization | Single sections.json file. | Individual .jsonc files per component. |
| Section identifier | name field. | $componentKey field. |
| Display name | schema.title | $componentTitle field. |
| Inheritance | Not supported. | $extends for schema inheritance. |
| Base component | N/A | Extends #/$defs/base-component. |
| Image widget | "ui:widget": "image-uploader" | "ui:widget": "media-gallery" |
| File extension | .json | .json or .jsonc |
| Deployment | Synced 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
namefield. - 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
$componentKeyfield. - 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.
| Keyword | Purpose | Example |
|---|---|---|
$extends | Inherit properties from base schemas. | "$extends": ["#/$defs/base-component"] |
$componentKey | Unique identifier for the component. | "$componentKey": "Banner" |
$componentTitle | Display name shown in CMS interface. | "$componentTitle": "Promotional Banner" |
$ref | Reference 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.jsonfile, 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.jsoncinstead of scrolling through hundreds of lines insections.json. - Comments and documentation: The
.jsoncformat 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:
_12your-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:
_18your-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:
_15your-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:
| Item | Headless CMS (legacy) | CMS |
|---|---|---|
| Component schemas | All in sections.json | cms_component__ComponentName.jsonc |
| Content types | All in content-types.json | cms_content_type__typeName.jsonc |
| File extension | .json | .jsonc (allows comments) |
| Location | cms/faststore/ | cms/components/ and cms/pages/, or co-located with component code |