Skip to content
decodecodeveloper docs
Storefront → Blocks → Getting started

Migrating from v7

Keep v7 content working with aliases, replace v7 loaders, actions, telemetry and analytics, fold site settings into the CMS block, and migrate saved content safely.

Your v7 store's saved pages refer to blocks like site/sections/Product.tsx, and its product shelf calls a VTEX loader from the apps. You want the new code without rewriting all that content.

Saved content refers to your code by name: every block stores a type name in __resolveType, references store saved block names, and inputs are stored by field name. Treat these names like database columns: once content uses them, changing one breaks that content.

This page describes the planned migration, how to keep old names working with aliases, what to do with v7 loaders and actions, what replaces v7's telemetry and analytics, how secrets move over, and a checklist for moving content.

Migration availability

The migration tooling in the source proposal is not present at the pinned repository revision. Its suggested @decocms/blocks@^8.1 installation and deco-v7-to-v8-migration skill have not been verified as available, so this documentation does not offer them as runnable steps. Continue using the supported v7 migration guides until the preview release and migration tooling are confirmed.

The remaining sections describe the intended migration contract: preserve saved names with aliases, move data access into application code, update telemetry and secrets, and validate behavior before rollout. The planned next framework removes the v7 bindings and admin package; those dependencies must remain installed while a site still runs v7.

Seven v7 type names have no alias in the next major, so the migration rewrites them in your saved content to a name that resolves the same way, with the same props:

v7 nameSaved as
website/flags/multivariate/image.ts, website/flags/multivariate/message.ts, website/flags/multivariate/page.ts, $live/flags/multivariate.tswebsite/flags/multivariate.ts
$live/matchers/MatchAlways.tswebsite/matchers/always.ts
website/matchers/date.ts, $live/matchers/MatchDate.tsdate

The Next design removes v7's framework-specific deferred-rendering wrapper blocks. The proposed migration unwraps them in saved content, pages and saved blocks alike, putting each section in its wrapper's place with its props unchanged:

v7 wrapperBecomes
website/sections/Rendering/Lazy.tsx, website/sections/Rendering/SingleDeferred.tsx ({ "section": … })its section
website/sections/Rendering/Deferred.tsx ({ "sections": [ … ] })its sections, in place in the list that held it

The wrapper's own options (loading, display, behavior) are removed too; recreate any loading or display behavior in your application where needed. This does not remove asynchronous block functions or framework-native streaming. The rendering guides use per-block Suspense boundaries in Next.js and deferred promises in TanStack Start, so a section may still arrive after the initial response. A wrapper that held several sections where only one block fits is intended to be reported for you to unwrap by hand. The migration is intended to be idempotent.

Rename a block type

Aliases work for any rename, not only a v7 migration. An alias is another key in the block map pointing at the same function. Content that stores the old name keeps working, and routing is unaffected because matchRoute looks at each entry's path field (see Pages and routing), not its type name:

.deco/index.ts
import type { Blocks } from "@decocms/blocks";
import productCard from "../src/product-card";
 
export default {
  "product-card": productCard,
  "site/sections/Product.tsx": productCard,     // the name legacy content stores
} satisfies Blocks;

Legacy page names such as website/pages/Page.tsx need no entry: the CLI's alias table maps them to the built-in page (see Site editor compatibility). Legacy website/flags/multivariate.ts blocks map to the built-in multivariate, and the alias bridge wraps each variant, a plain value, in a lazy block on the way.

An alias only works while both names take the same inputs. If the old shape differs, register a function under the old name that converts the old inputs and calls the new function, or migrate the content first. Here the old content called the title name:

type CardInput = Parameters<typeof productCard>[0];
 
export default {
  "product-card": productCard,
  "site/sections/Product.tsx": ({ name, ...rest }: Omit<CardInput, "title"> & { name: string }) =>
    productCard({ ...rest, title: name }),      // converts the old inputs
} satisfies Blocks;

deco check rejects an alias that collides with a saved block's name.

Loaders, actions and invoke

On v7, saved content often calls loaders and actions that ship in Deco's apps, such as a VTEX product loader. In the next major the apps are thin upstream clients and ship no loaders, so the migration copies (vendors) the ones your site actually uses into your own code, rewritten over the clients, and registers each under its old type name as an alias. Content keeps resolving, and the copied code is yours to change from then on.

v7's /deco/invoke endpoint (which ran loaders and actions over HTTP for the site editor and the browser) and cachedLoader (see v7 caching) are gone. Call upstream clients from your framework's server functions or route handlers instead; upstream caching is a fetch your app passes to a client (see Upstream data).

Telemetry and analytics

v7 configured telemetry with environment variables and shipped its analytics as components. In the next major, they're two separate parts of Deco CMS: code says where each one sends, and the CMS block holds their switches and rates.

v7Next major
The OpenTelemetry export, switched on with DECO_OTEL and the DECO_OTEL_* endpoint and header variables (see v7 observability)The telemetry option of createCMS: { endpoint, headers? } for your own collector, or the standard OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS variables. Sampling moves to the telemetry section of the CMS block.
Metrics Deco collected for your site in its own backendtelemetry: { site, token }, set explicitly (see Hosted telemetry)
The Stats and OneDollarStats components, and the DECO_ANALYTICS_ENABLED and ONEDOLLAR_ENABLED variablesAnalyticsScript in your root layout, with the analytics section of cms.settings(), which speaks the One Dollar Stats format. Rendering it turns analytics on; the section's enabled field switches it off.
The Analytics section with Google Tag Manager and GA4 tagsA tag manager block from your platform template, owned by your site; the migration lists the tag IDs in its report. Built-in analytics only counts page views, so it doesn't take them.
Experiment results keyed on the random matcher's saved-block nameThe experiment ID of the A/B test; the migration copies the old name into it, so results carry over.

Site settings

The next major keeps site-wide settings in one saved block, CMS, of the built-in type cms-settings. The migration folds what it finds into .deco/blocks/CMS.json, keeping anything already there, and writes nothing when there's nothing to fold:

FromInto CMS.json
The v7 Site block's (Site or site) previewHostspreview.hosts, entry for entry, each trimmed and lowercased as v7 compared it. v7 compared hosts exactly, port included, and so does an entry with a port here (see Host patterns). An entry that isn't a host is left out and listed in the report. On TanStack Start, v7 also allowed <site>.deco.site and <site>.deco-cx.workers.dev for the site named by DECO_SITE_NAME; the migration adds both when it finds the name in wrangler.*, .env or the Vite config, and lists them in the report when it doesn't. The rest of the Site block stays where it is.
A literal collectorAddress on v7's OneDollarStats component in your codeanalytics.collector
A Telemetry saved block (type telemetry) from an earlier next-major prereleaseThe telemetry section, field for field; the old file is deleted
An Analytics saved block (type analytics) from an earlier next-major prereleaseThe analytics section, field for field; the old file is deleted

Variants come along unchanged: a saved block that was a multivariate becomes the same multivariate in its section. The telemetry and analytics built-ins are gone, with no alias, so a leftover block of either type fails deco check until it's folded. Running the migration again changes nothing.

Settings v7 read from your hosting environment never reach the repository, so the report lists them for you to move by hand:

  • DECO_ALLOWED_PREVIEW_HOSTS replaced the Site block's list. Put its hosts in CMS.json's preview.hosts, or, to make them the most content may allow, in preview.hosts of createCMS. Its none is an empty list.
  • DECO_OTEL_* sampling variables become the telemetry section's rates, within the limits in code.
  • DECO_ANALYTICS_ENABLED and ONEDOLLAR_ENABLED become the analytics section's enabled, and ONEDOLLAR_COLLECTOR its collector.

A v7 site with no allowed hosts had draft preview off. In the next major, previews work on every host unless you list some, so add preview.hosts if you relied on that.

Where you render analytics, replace the resolved Analytics block with the settings: const { analytics } = await cms.settings(), then <AnalyticsScript {...analytics} /> (see Analytics).

Secrets

v7 saved credentials as website/loaders/secret.ts blocks, encrypted with the DECO_CRYPTO_KEY environment variable. The next major uses a key pair instead (see Secrets), so the migration re-encrypts each secret once:

  1. Create the key pair and commit .deco/secrets.pub.
  2. Run the migration with DECO_CRYPTO_KEY set. It decrypts each v7 secret and saves it as a secret block encrypted with your public key, in the same commit as the rest of the content.
  3. Deploy with DECO_SECRETS_KEY set, then remove DECO_CRYPTO_KEY.

Checklist for migrating existing content

  1. Keep existing names. If the content stores file-path type names, register them as they are or alias them, including the app loaders and actions you vendored.
  2. Check inputs. Run deco check: it validates every saved block against the schema and lists each one that no longer fits. It can't see changed defaults, so review those yourself.
  3. Migrate the content in one commit. A script reads the JSON in .deco/blocks, rewrites it, and writes it back. The commit is a new revision you can revert in one step. Keep fields the script doesn't recognize instead of deleting them, and report references to missing entries (NOT_FOUND) instead of dropping them.
  4. Test the integration. Load real pages against the migrated content: routes resolve, titles and SEO tags are right, blocks that load later (if your framework streams them) appear, failing blocks show their fallback, and navigation works.
  5. Keep a rollback pair. Keep the previous build plus the content revision it was tested with. Rolling back content alone can't bring back code you removed.

Test behavior, not just schemas. A function can keep its input type and still change its output, defaults, or upstream calls.