| name | vtex-io-storefront-theme-app |
| description | Apply when designing or modifying a VTEX IO storefront theme app — the app that owns `store/blocks.json`, `store/routes.json`, `store/templates/`, `store/contentSchemas.json`, and the storefront page tree assembled from `store.home`, `store.product`, `store.search`, `store.custom`, and other native page templates. Covers how a theme app extends a base theme, declares routes, composes blocks across pages, and how its `store/` files relate to merchant Site Editor content. Use for theme scaffolding, custom page routes, theme-level overrides, or reviewing whether a change belongs in the theme app, in a component app, or in app settings.
|
| metadata | {"track":"vtex-io","tags":["vtex-io","storefront","store-theme","store-framework","blocks","routes","site-editor","templates"],"globs":["store/blocks.json","store/routes.json","store/templates/**/*.json","store/contentSchemas.json","store/blocks/**/*.json"],"version":"1.0","purpose":"Decide what belongs in a storefront theme app and how its `store/` files compose Store Framework pages","applies_to":["scaffolding a new storefront theme app","adding a custom storefront route","composing blocks for `store.home`, `store.product`, `store.search`, or `store.custom`","extending a base theme such as `vtex.store-theme`","deciding whether a change belongs in the theme, in a component app, or in app settings"],"excludes":["block registration in component apps (see `vtex-io-render-runtime-and-blocks`)","storefront component implementation (see `vtex-io-storefront-react`)","theme version lifecycle and rollback (see `vtex-io-storefront-theme-versioning`)","i18n and message content (see `vtex-io-messages-and-i18n`)"],"decision_scope":["what should ship in the theme app vs in a component app","how page templates compose blocks","how custom routes map to page contexts","when to override a base theme block vs when to add a new block"],"vtex_docs_verified":"2026-04-24"} |
Storefront Theme App
When this skill applies
Use this skill when working on the app that assembles the storefront — the theme app that owns the page tree, custom routes, and Site Editor surface area for a store.
- Scaffolding
vendor.store-theme or any app with the store builder that ships pages
- Adding or changing
store/blocks.json or per-template files under store/blocks/
- Adding or modifying a custom route in
store/routes.json
- Composing the block tree for
store.home, store.product, store.search, store.custom, or any native page template
- Extending or overriding a base theme such as
vtex.store-theme
- Reviewing whether a change belongs in the theme app, in a component app, or in app settings
Do not use this skill for:
- registering a new block in a component app (
store/interfaces.json) — use vtex-io-render-runtime-and-blocks
- implementing the React component behind a block — use
vtex-io-storefront-react
- changing the theme app's installed version on
master — use vtex-io-storefront-theme-versioning
- localized strings — use
vtex-io-messages-and-i18n
Decision rules
- A theme app is a regular VTEX IO app with the
store builder declared in manifest.json. It almost never ships React code; it composes blocks declared by component apps and by base themes.
- The theme app declares its base theme in
manifest.json#dependencies, typically vtex.store-theme, and inherits every page template, block declaration, and default content the base ships.
store/blocks.json (or per-template files under store/blocks/) defines the block tree per page template by referencing block IDs. Block IDs come from the component apps' store/interfaces.json and are scoped by the declaring app's MAJOR version.
store/routes.json defines custom storefront routes and binds them to a page context (store.custom, store.product, store.search, etc.). Native routes (/, /{slug}/p, search) come from the base theme and rarely need to be redeclared.
store/contentSchemas.json declares Site Editor-editable props for blocks. Merchant edits to those props are stored by vtex.pages-graphql under a key that includes the theme app's MAJOR version.
- Three change locations exist for storefront behavior. Pick consciously:
- Theme app
store/ JSON — composition, routes, default content, allowed children. Affects all shoppers immediately on promote.
- Component app code — the React behavior of a block. Released on the component app's own version cadence.
- Site Editor — merchant-managed content overrides on top of the theme's defaults. Stored by
vtex.pages-graphql and scoped by the declaring app's installed major.
- Prefer extending a base theme over forking it. Forking a base theme moves the responsibility for every block, route, and template to your app forever, including upstream bug fixes.
- A storefront page is a tree of blocks. The leaves are component blocks; the branches are container blocks (
flex-layout.row, flex-layout.col, etc.). Keep the tree as shallow as the design allows; deep trees inflate render and content footprint.
- The theme app is a content-holding app in the sense of
vtex-io-storefront-theme-versioning. Its installed major version is part of the key the platform uses for every Site Editor change a merchant has ever saved against blocks declared by this app. Treat its version contract as merchant-facing, not developer-facing.
Hard constraints
Constraint: Theme apps must declare the store builder and a base theme
A storefront theme app MUST declare "store" in manifest.json#builders and MUST depend on a base theme (typically vtex.store-theme) unless it explicitly takes ownership of every native page template, block, and route.
Why this matters
Without the store builder, none of the files under store/ are processed and the theme contributes nothing to the storefront. Without a base theme, the app is responsible for declaring every native page template (store.home, store.product, store.search, etc.) from scratch — including upstream maintenance forever.
Detection
If a theme app ships store/blocks.json or store/routes.json but its manifest does not declare "store" in builders, STOP and add the builder. If the manifest also omits vtex.store-theme (or another base theme) from dependencies or peerDependencies without an explicit reason, STOP and confirm the app intends to own every native template.
Correct
{
"vendor": "acme",
"name": "store-theme",
"version": "1.0.0",
"title": "ACME Store Theme",
"builders": {
"store": "0.x",
"messages": "1.x"
},
"dependencies": {
"vtex.store-theme": "2.x",
"acme.product-widgets": "0.x"
}
}
Wrong
{
"vendor": "acme",
"name": "store-theme",
"version": "1.0.0",
"builders": {
"messages": "1.x"
},
"dependencies": {}
}
The store/ files exist on disk but the platform ignores them, and the theme inherits nothing.
Constraint: Block IDs in store/blocks.json must resolve to a registered block in an installed app
Every block ID referenced in store/blocks.json (or per-template files) MUST be declared in an interfaces.json of an installed app whose MAJOR version matches what the platform resolves at render time. Unresolved block IDs cause Missing block errors at the GraphQL layer and break the page.
Why this matters
vtex.pages-graphql resolves a block ID by looking up vendor.app@MAJOR.x:block-id against the installed apps. If the declaring app is not installed, or is installed at a different major than the merchant content was authored against, the block does not exist from the resolver's point of view and the page fails to render.
Detection
If store/blocks.json references a block ID, verify that some app in manifest.json#dependencies declares it in store/interfaces.json at the major version range listed in the dependency. If the dependency is acme.product-widgets@0.x, the block must exist in the 0.x line of that app, not the 5.x line.
Correct
{
"dependencies": {
"vtex.store-theme": "2.x",
"acme.product-widgets": "0.x"
}
}
{
"store.product": {
"children": [
"flex-layout.row#product-main",
"acme-related-products"
]
},
"acme-related-products": {
"props": { "limit": 8 }
}
}
{
"acme-related-products": {
"component": "RelatedProducts"
}
}
Wrong
{
"store.product": {
"children": ["acme-new-related-products"]
}
}
The render-time resolver returns Missing block acme.product-widgets@0.x:acme-new-related-products and the page fails.
Constraint: Custom routes in store/routes.json must bind to a real page context and template
Every entry in store/routes.json MUST set a valid path, a context (or rely on the built-in context for native page IDs such as store.product), and a template that exists in the block tree (the page ID itself, or a store.custom#<id> declared in store/blocks.json).
Why this matters
A route entry without a resolvable template renders nothing. A route entry with the wrong context (e.g., a custom institutional page typed as store.product) executes the wrong data resolver and crashes or returns empty product data.
Detection
For each route in store/routes.json, confirm: (a) the path does not collide with native VTEX paths, (b) the bound page ID exists as a key in store/blocks.json, and (c) the context matches the data the page actually needs.
Correct
{
"store.custom#about": {
"path": "/institucional/sobre",
"context": "vtex.store-resources/InstitutionalPageContext"
}
}
{
"store.custom#about": {
"blocks": ["rich-text#about-body"]
}
}
Wrong
{
"store.custom#about": {
"path": "/p/sobre"
}
}
Path /p/sobre collides with the native PDP route shape, no context is declared, and there is no matching store.custom#about template in store/blocks.json.
Preferred pattern
Recommended theme app structure:
acme.store-theme/
├── manifest.json # store builder, base theme dependency
├── messages/ # localized strings (separate skill)
└── store/
├── blocks.json # global block declarations
├── routes.json # custom routes
├── contentSchemas.json # Site Editor-editable props
└── blocks/ # per-template block trees
├── home.jsonc
├── product.jsonc
├── search.jsonc
├── category.jsonc
└── custom/
├── about.jsonc
└── stores.jsonc
Recommended store.home composition:
{
"store.home": {
"blocks": [
"flex-layout.row#home-hero",
"shelf#home-best-sellers",
"rich-text#home-newsletter"
]
},
"flex-layout.row#home-hero": {
"children": ["image#hero-banner"]
},
"image#hero-banner": {
"props": {
"src": "/arquivos/home-hero.png",
"alt": "Promotional banner"
}
}
}
Recommended custom institutional route:
{
"store.custom#about": {
"path": "/institucional/sobre",
"context": "vtex.store-resources/InstitutionalPageContext"
}
}
{
"store.custom#about": {
"blocks": ["flex-layout.row#about-body"]
},
"flex-layout.row#about-body": {
"children": ["rich-text#about-copy"]
},
"rich-text#about-copy": {
"props": {
"text": "About ACME — see Site Editor for the live copy."
}
}
}
Recommended Site Editor-editable surface:
{
"rich-text#about-copy": {
"type": "object",
"properties": {
"text": {
"type": "string",
"title": "Body copy",
"widget": { "ui:widget": "textarea" }
}
}
}
}
Merchant edits to text are persisted by vtex.pages-graphql under a key that includes the theme app's MAJOR version. See vtex-io-storefront-theme-versioning for what happens to that content on a major bump and how to migrate it with the updateThemeIds mutation.
Common failure modes
- Forking
vtex.store-theme instead of depending on it, then losing every upstream block fix and route addition forever.
- Referencing a block ID in
store/blocks.json that exists in a different major of the declaring app than the dependency range allows.
- Declaring the same block ID in two different apps and getting non-deterministic resolution at render time.
- Putting Site Editor-editable copy directly in
store/blocks.json props without contentSchemas.json, so merchants cannot change it.
- Adding a custom route to
store/routes.json without adding the matching store.custom#<id> template in store/blocks.json.
- Treating the theme app's version as developer-facing and bumping the major to "tidy up", which leaves the new major with no merchant content and forces an
updateThemeIds migration in vtex.pages-graphql@2.x before promote (see vtex-io-storefront-theme-versioning).
- Putting React component code in the theme app instead of in a dedicated component app, which mixes block declaration with block consumption and complicates reuse.
- Storing operational or shopper-specific data in theme
store/ files. The theme is global; per-shopper or per-segment data belongs elsewhere.
Review checklist
Related skills
vtex-io-storefront-theme-versioning — Use when the question is how to safely change which version of this theme app is installed on master.
vtex-io-render-runtime-and-blocks — Use when the question is how a block ID becomes a React component, or how a component app should declare blocks for themes to consume.
vtex-io-storefront-react — Use when the question is the React implementation of a block, not its composition into pages.
vtex-io-app-contract — Use when the question is what the theme app's manifest contract should declare and how it interacts with base themes and component apps.
Reference