vtex-io-storefront-theme-app

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.

Skill file

Preview skill file
---
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."
---

# 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:
  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**

```json
{
  "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**

```json
{
  "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**

```json
// manifest.json
{
  "dependencies": {
    "vtex.store-theme": "2.x",
    "acme.product-widgets": "0.x"
  }
}
```

```json
// store/blocks.json
{
  "store.product": {
    "children": [
      "flex-layout.row#product-main",
      "acme-related-products"
    ]
  },
  "acme-related-products": {
    "props": { "limit": 8 }
  }
}
```

```json
// acme.product-widgets@0.x ships store/interfaces.json with:
{
  "acme-related-products": {
    "component": "RelatedProducts"
  }
}
```

**Wrong**

```json
// 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**

```json
// store/routes.json
{
  "store.custom#about": {
    "path": "/institucional/sobre",
    "context": "vtex.store-resources/InstitutionalPageContext"
  }
}
```

```json
// store/blocks.json
{
  "store.custom#about": {
    "blocks": ["rich-text#about-body"]
  }
}
```

**Wrong**

```json
// 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:

```text
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:

```json
{
  "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:

```json
// store/routes.json
{
  "store.custom#about": {
    "path": "/institucional/sobre",
    "context": "vtex.store-resources/InstitutionalPageContext"
  }
}
```

```json
// 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:

```json
// 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-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

- [ ] Does `manifest.json` declare the `store` builder?
- [ ] 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.json` resolve to a real `interfaces.json` entry in an installed app at a matching major?
- [ ] Does every entry in `store/routes.json` have a valid `path`, `context`, and matching template in `store/blocks.json`?
- [ ] Are merchant-editable copy and image fields exposed through `contentSchemas.json` rather than hardcoded in `props`?
- [ ] 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`](../vtex-io-storefront-theme-versioning/SKILL.md) — 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`](../vtex-io-render-runtime-and-blocks/SKILL.md) — 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`](../vtex-io-storefront-react/SKILL.md) — Use when the question is the React implementation of a block, not its composition into pages.
- [`vtex-io-app-contract`](../vtex-io-app-contract/SKILL.md) — 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](https://developers.vtex.com/docs/guides/vtex-io-documentation-store-framework) — How theme apps assemble pages from blocks and templates.
- [Using Components](https://developers.vtex.com/docs/guides/store-framework-using-components) — How to reference blocks from base themes and component apps.
- [Themes](https://developers.vtex.com/docs/guides/vtex-io-documentation-themes) — Theme app structure and the relationship to `vtex.store-theme`.
- [Routes](https://developers.vtex.com/docs/guides/vtex-io-documentation-routes) — Declaring custom storefront routes and binding them to page contexts.
- [Making a Custom Component Available in Site Editor](https://developers.vtex.com/docs/guides/vtex-io-documentation-making-a-custom-component-available-in-site-editor) — `contentSchemas.json` and the Site Editor surface.

Source

Creator's repository · vtex/skills

View on GitHub

Security

Security checks in progress
Results will appear here once audits complete
Checked by 3 independent security firms
Does it try to trick the AI?Not yet checkedPending · Gen Agent Trust Hub
Does it sneak in hidden code?Not yet checkedPending · Socket
Does it have known bugs?Not yet checkedPending · Snyk