
Vtex Io Storefront Theme App
- 4 installs
- 39 repo stars
- Updated June 16, 2026
- vtex/ai-skills
Designs and modifies VTEX IO storefront theme apps that own blocks.json, routes.json, templates, and the storefront page tree across native page templates.
About
This skill covers the VTEX IO theme app that assembles the storefront page tree, custom routes, and Site Editor surface. A developer uses it when scaffolding a theme, editing blocks.json or routes.json, composing page templates, or extending a base theme.
- Composes block trees for store.home, store.product, store.search, and custom pages
- Decides whether a change belongs in the theme app, a component app, or settings
Vtex Io Storefront Theme App by the numbers
- 4 all-time installs (skills.sh)
- +1 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #1,817 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/vtex/ai-skills --skill vtex-io-storefront-theme-appAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 39 |
| Last updated | June 16, 2026 |
| Repository | vtex/ai-skills ↗ |
What it does
Designs and modifies VTEX IO storefront theme apps that own blocks.json, routes.json, templates, and the storefront page tree across native page templates.
Files
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-themeor any app with thestorebuilder that ships pages - Adding or changing
store/blocks.jsonor per-template files understore/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) — usevtex-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— usevtex-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
storebuilder declared inmanifest.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, typicallyvtex.store-theme, and inherits every page template, block declaration, and default content the base ships. store/blocks.json(or per-template files understore/blocks/) defines the block tree per page template by referencing block IDs. Block IDs come from the component apps'store/interfaces.jsonand are scoped by the declaring app's MAJOR version.store/routes.jsondefines 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.jsondeclares Site Editor-editable props for blocks. Merchant edits to those props are stored byvtex.pages-graphqlunder a key that includes the theme app's MAJOR version.- Three change locations exist for storefront behavior. Pick consciously:
1. Theme app store/ JSON — composition, routes, default content, allowed children. Affects all shoppers immediately on promote. 2. Component app code — the React behavior of a block. Released on the component app's own version cadence. 3. 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
// manifest.json
{
"dependencies": {
"vtex.store-theme": "2.x",
"acme.product-widgets": "0.x"
}
}// store/blocks.json
{
"store.product": {
"children": [
"flex-layout.row#product-main",
"acme-related-products"
]
},
"acme-related-products": {
"props": { "limit": 8 }
}
}// acme.product-widgets@0.x ships store/interfaces.json with:
{
"acme-related-products": {
"component": "RelatedProducts"
}
}Wrong
// manifest.json depends on acme.product-widgets@0.x
// but store/blocks.json references a block that only exists in @5.x
{
"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/routes.json
{
"store.custom#about": {
"path": "/institucional/sobre",
"context": "vtex.store-resources/InstitutionalPageContext"
}
}// store/blocks.json
{
"store.custom#about": {
"blocks": ["rich-text#about-body"]
}
}Wrong
// store/routes.json
{
"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.jsoncRecommended 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/routes.json
{
"store.custom#about": {
"path": "/institucional/sobre",
"context": "vtex.store-resources/InstitutionalPageContext"
}
}// store/blocks/custom/about.jsonc
{
"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:
// store/contentSchemas.json
{
"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-themeinstead of depending on it, then losing every upstream block fix and route addition forever. - Referencing a block ID in
store/blocks.jsonthat 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.jsonpropswithoutcontentSchemas.json, so merchants cannot change it. - Adding a custom route to
store/routes.jsonwithout adding the matchingstore.custom#<id>template instore/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
updateThemeIdsmigration invtex.pages-graphql@2.xbefore promote (seevtex-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
- [ ] Does
manifest.jsondeclare thestorebuilder? - [ ] Does the theme depend on a base theme, or is full ownership of native templates explicit and intentional?
- [ ] Does every block ID in
store/blocks.jsonresolve to a realinterfaces.jsonentry in an installed app at a matching major? - [ ] Does every entry in
store/routes.jsonhave a validpath,context, and matching template instore/blocks.json? - [ ] Are merchant-editable copy and image fields exposed through
contentSchemas.jsonrather than hardcoded inprops? - [ ] Are React components kept out of the theme app and in dedicated component apps?
- [ ] Has the team considered whether a planned change would force a major version bump on this content-holding app?
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
- Store Framework — How theme apps assemble pages from blocks and templates.
- Using Components — How to reference blocks from base themes and component apps.
- Themes — Theme app structure and the relationship to
vtex.store-theme. - Routes — Declaring custom storefront routes and binding them to page contexts.
- Making a Custom Component Available in Site Editor —
contentSchemas.jsonand the Site Editor surface.