Skip to content

Migrate to @storyblok/astro 11.x

@storyblok/astro 11.x is a ground-up redesign of the SDK. Version 11.x:

  • Removes the storyblok Astro integration and its config-based component registry.
  • Moves all data fetching to the framework-agnostic @storyblok/api-client.
  • Replaces StoryblokComponent and useStoryblokApi with a block registry built around defineStoryblokBlocks, and live preview setup built around storyblokPreviewMiddleware.

This guide explains how to update the integration configuration, data fetching, and component registration code of apps that import @storyblok/astro. Block components need smaller updates: the renamed block prop, the injected editable prop, and StoryblokBlock for nested blocks.

  • Astro version 5.0 or later (version 11.x drops support for Astro 3.x and 4.x)
  • Node.js LTS (version 22.x recommended)

Follow these steps to migrate an application:

  1. Upgrade @storyblok/astro and add @storyblok/api-client.
  2. Create one apiClient instance and move the apiOptions of the storyblok integration to createApiClient(). Replace useStoryblokApi() and storyblokApi calls with apiClient.stories.get().
  3. Replace the components option with defineStoryblokBlocks(() => ({ components })). Export its StoryblokBlock and StoryblokRichText from one module.
  4. Remove the storyblok integration from astro.config.mjs.
  5. Rename blok to block in every block component. Replace manual storyblokEditable(blok) calls with the injected editable prop, and render nested blocks with StoryblokBlock instead of StoryblokComponent.
  6. Fetch and render content without live editing using apiClient and StoryblokBlock.
  7. Add storyblokPreviewMiddleware, and replace the livePreview and bridge options with getPayload() and StoryblokPreview.
  8. Update rich text, including the StoryblokRichText import and type names.

Version 11.x has no Astro integration and no global component registry. The following tables list the APIs that version 11.x removes, adds, and changes.

Removed in version 11.x Replacement
The storyblok Astro integration, its apiOptions, components, componentsDir, enableFallbackComponent, customFallbackComponent, useCustomApi options defineStoryblokBlocks(), and createApiClient() from @storyblok/api-client
useStoryblokApi(), the storyblokApi client export apiClient.stories.get(), from the @storyblok/api-client instance
StoryblokComponent, its blok prop StoryblokBlock, with block and injected editable props
renderRichText, buildStoryblokImage, and splitTableRows from @storyblok/astro The same utilities from @storyblok/richtext
The livePreview option, the bridge option, the storyblok-live-preview meta tag storyblokPreviewMiddleware, getPayload(), and StoryblokPreview
getLiveStory (deprecated since 8.2.0) getPayload()
SbAstroRichTextProps, SbAstroRichTextComponentMap, SbAstroRichTextRenderContext StoryblokAstroRichTextProps, StoryblokAstroRichTextComponentMap, StoryblokAstroRichTextRenderContext
isEditorRequest() isInEditor()
SbBlokData and the other @storyblok/js types (ISbStoryData, ISbStoriesParams, …) StoryblokBlockData from @storyblok/astro, and the Story type of the API client
The @storyblok/richtext types and helpers re-exported from @storyblok/astro (StoryblokRichTextNode, processAttrs, …), buildAstroAttrs, isValidAstroComponent The same types and helpers from @storyblok/richtext. buildAstroAttrs and isValidAstroComponent have no replacement.
storyblokInit, apiPlugin, loadStoryblokBridge, handleStoryblokMessage, toCamelCase, the IntegrationOptions type No replacement. StoryblokPreview loads the Storyblok Bridge.
The @storyblok/astro/client, @storyblok/astro/middleware.ts, and @storyblok/astro/toolbarApp.ts entry points, and the Astro development toolbar app @storyblok/api-client for the client, and storyblokPreviewMiddleware from @storyblok/astro. The development toolbar app has no replacement.
The @storyblok/astro/FallbackComponent.astro and @storyblok/astro/StoryblokServerData.astro entry points The fallback option of defineStoryblokBlocks(), and the StoryblokServerData named export of @storyblok/astro
Added in version 11.x Description
defineStoryblokBlocks() Replaces the components option of the storyblok integration. Returns StoryblokBlock and StoryblokRichText, bound to one component map.
storyblokPreviewMiddleware Astro middleware that captures the Visual Editor’s preview payload on Astro.locals, so getPayload() can read it back.
StoryblokPreview Component that reloads on save or publish, and (with liveUpdate) morphs the DOM live. Replaces the bridge and livePreview options.
warnings Option of defineStoryblokBlocks() and prop of StoryblokRichText. Set to false to silence warnings for content that renders nothing.
isInEditor() Checks whether a request came from the Visual Editor.
@storyblok/api-client The recommended way to fetch stories. The Astro SDK no longer bundles an API client.
Change What to update
The block prop is block instead of blok Rename the prop in every block component. StoryblokBlock also injects the result of storyblokEditable() as an editable prop.
getPayload() requires storyblokPreviewMiddleware Add the middleware in src/middleware.ts, instead of enabling the livePreview option.
StoryblokRichText is a named export of @storyblok/astro instead of a default export of a separate entry point Import StoryblokRichText from @storyblok/astro instead of @storyblok/astro/StoryblokRichText.astro.
Rich text type names use the Storyblok prefix instead of Sb Rename SbAstroRichTextProps to StoryblokAstroRichTextProps, SbAstroRichTextComponentMap to StoryblokAstroRichTextComponentMap, and SbAstroRichTextRenderContext to StoryblokAstroRichTextRenderContext.
The bridge loads only on pages that render StoryblokPreview, instead of on every page Render StoryblokPreview in the layout. Add liveUpdate to keep the behavior of the livePreview option.

Upgrade @storyblok/astro and add @storyblok/api-client to the project:

Terminal window
npm install @storyblok/astro@latest @storyblok/api-client@latest

Replace useStoryblokApi() and the storyblok integration’s apiOptions with @storyblok/api-client. Create one client instance and export it next to the component map.

src/storyblok.ts
import { createApiClient } from "@storyblok/api-client";
import { defineStoryblokBlocks } from "@storyblok/astro";
import Page from "./components/Page.astro";
import Feature from "./components/Feature.astro";
export const apiClient = createApiClient({
accessToken: "YOUR_ACCESS_TOKEN",
region: "eu",
});
export const { StoryblokBlock } = defineStoryblokBlocks(() => ({
components: { page: Page, feature: Feature },
}));

Fetch stories directly from apiClient wherever the application used to call useStoryblokApi() or storyblokApi:

const { data } = await apiClient.stories.get("home", { query: { version: "draft" } });

apiClient.stories.get() takes the slug without the cdn/stories/ prefix, and API parameters such as version go in the query object.

defineStoryblokBlocks() replaces the components option of the storyblok integration. Unlike the integration’s string paths relative to src, components takes the Astro component references directly:

export const { StoryblokBlock } = defineStoryblokBlocks(() => ({
components: {
page: Page,
feature: Feature,
},
// Replaces enableFallbackComponent and customFallbackComponent
fallback: FallbackBlock,
}));

Throughout the app, import StoryblokBlock from src/storyblok.ts. In version 8.x-10.x, @storyblok/astro exported StoryblokComponent from a separate entry point. StoryblokBlock replaces StoryblokComponent.

Version 8.x-10.x automatically registered the Astro components in the Storyblok folder of componentsDir. Version 11.x has no implicit folder scanning. To replicate automatic discovery, use Vite’s import.meta.glob.

The registry accepts only Astro (.astro) components. To render a block with a framework component, such as React or Vue, wrap it in an Astro component and apply the client:* directive there.

Version 11.x has no Astro integration. Remove the storyblok() entry, and the @storyblok/astro import, from astro.config.mjs.

Every component registered in the map receives a block prop and an editable prop. StoryblokBlock injects editable automatically, so components no longer need to import storyblokEditable. Rename blok to block and spread editable onto the root element:

src/components/Feature.astro
---
const { block, editable } = Astro.props;
---
<div {...editable}>
<h2>{block.headline}</h2>
</div>

@storyblok/astro still exports storyblokEditable() for components that aren’t rendered through StoryblokBlock.

In version 8.x-10.x, block components rendered nested blocks by importing StoryblokComponent from @storyblok/astro/StoryblokComponent.astro and mapping over the array. Version 11.x removes it. Render nested blocks with StoryblokBlock, imported from the module that calls defineStoryblokBlocks():

src/components/Page.astro
---
import type { StoryblokBlockData } from "@storyblok/astro";
import { StoryblokBlock } from "../storyblok";
const { block, editable } = Astro.props;
---
<main {...editable}>{block.body?.map((child: StoryblokBlockData) => <StoryblokBlock block={child} />)}</main>

To fetch and render content, use apiClient and StoryblokBlock:

src/pages/index.astro
---
import { apiClient, StoryblokBlock } from "../storyblok";
const { data } = await apiClient.stories.get("home", { query: { version: "draft" } });
const story = data?.story;
if (!story) {
return new Response(null, { status: 404 });
}
---
<StoryblokBlock block={story.content} />

For fetching and rendering without live editing, apiClient and StoryblokBlock replace useStoryblokApi() and StoryblokComponent. Render StoryblokPreview alongside the content to enable live editing, as described in the next section.

Version 11.x replaces the livePreview and bridge integration options, getLiveStory, and the storyblok-live-preview meta tag with storyblokPreviewMiddleware, getPayload(), and StoryblokPreview.

Add the middleware so preview edits reach the page:

src/middleware.ts
import { sequence } from "astro:middleware";
import { storyblokPreviewMiddleware } from "@storyblok/astro";
export const onRequest = sequence(storyblokPreviewMiddleware);

Read the preview payload with getPayload(), and render the story with StoryblokBlock and StoryblokPreview:

src/pages/index.astro
---
import { getPayload, StoryblokPreview } from "@storyblok/astro";
import { apiClient, StoryblokBlock } from "../storyblok";
const payload = await getPayload({ locals: Astro.locals });
const story = payload.story ?? (await apiClient.stories.get("home", { query: { version: "draft" } })).data?.story;
if (!story) {
return new Response(null, { status: 404 });
}
---
<StoryblokBlock block={story.content} />
<StoryblokPreview liveUpdate />

The bridge option’s resolveRelations, resolveLinks, and customParent move to the bridgeOptions prop of StoryblokPreview. preventClicks has no replacement in version 11.x.

Version 8.x-10.x injected the bridge into every page. In version 11.x, the bridge loads only on pages that render StoryblokPreview, so render it once in the layout. Add liveUpdate to keep the behavior of the livePreview option. Like livePreview, liveUpdate requires routes that render on demand.

There’s no per-route opt-out flag, such as the storyblok-live-preview meta tag. Not rendering StoryblokPreview on a route is the opt-out.

StoryblokRichText is a named export of @storyblok/astro in version 11.x, instead of a default export of a separate entry point:

---
import { StoryblokRichText } from "@storyblok/astro";
---
<StoryblokRichText document={block.richtext_field} />

For TypeScript projects, version 11.x also renames the following types:

Version 8.x-10.x Version 11.x
SbAstroRichTextProps StoryblokAstroRichTextProps
SbAstroRichTextComponentMap StoryblokAstroRichTextComponentMap
SbAstroRichTextRenderContext StoryblokAstroRichTextRenderContext

In version 8.x-10.x, StoryblokRichText rendered embedded blocks with StoryblokComponent. In version 11.x, the StoryblokRichText exported by @storyblok/astro has no block registry: it skips embedded blocks and logs a warning, unless components.blok provides a renderer.

To keep the version 8.x-10.x behavior, use the StoryblokRichText returned by defineStoryblokBlocks(). It renders embedded blocks through the same registry as StoryblokBlock:

src/storyblok.ts
export const { StoryblokBlock, StoryblokRichText } = defineStoryblokBlocks(() => ({
components: { page: Page, feature: Feature },
}));
---
import { StoryblokRichText } from "../storyblok";
const { block } = Astro.props;
---
<StoryblokRichText document={block.richtext_field} />

Last updated:

Was this page helpful?

What went wrong?

This site uses reCAPTCHA and Google's Privacy Policy (opens in a new window).Terms of Service (opens in a new window) apply.