Migrate to @storyblok/astro 11.x
@storyblok/astro 11.x is a ground-up redesign of the SDK. Version 11.x:
- Removes the
storyblokAstro integration and its config-based component registry. - Moves all data fetching to the framework-agnostic
@storyblok/api-client. - Replaces
StoryblokComponentanduseStoryblokApiwith a block registry built arounddefineStoryblokBlocks, and live preview setup built aroundstoryblokPreviewMiddleware.
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.
Requirements
Section titled “Requirements”- 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)
Migration steps
Section titled “Migration steps”Follow these steps to migrate an application:
- Upgrade
@storyblok/astroand add@storyblok/api-client. - Create one
apiClientinstance and move theapiOptionsof thestoryblokintegration tocreateApiClient(). ReplaceuseStoryblokApi()andstoryblokApicalls withapiClient.stories.get(). - Replace the
componentsoption withdefineStoryblokBlocks(() => ({ components })). Export itsStoryblokBlockandStoryblokRichTextfrom one module. - Remove the
storyblokintegration fromastro.config.mjs. - Rename
bloktoblockin every block component. Replace manualstoryblokEditable(blok)calls with the injectededitableprop, and render nested blocks withStoryblokBlockinstead ofStoryblokComponent. - Fetch and render content without live editing using
apiClientandStoryblokBlock. - Add
storyblokPreviewMiddleware, and replace thelivePreviewandbridgeoptions withgetPayload()andStoryblokPreview. - Update rich text, including the
StoryblokRichTextimport and type names.
Summary of changes
Section titled “Summary of changes”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
Section titled “Removed”| 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. |
Changed
Section titled “Changed”| 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. |
Update the packages
Section titled “Update the packages”Upgrade @storyblok/astro and add @storyblok/api-client to the project:
npm install @storyblok/astro@latest @storyblok/api-client@latestCreate the API client
Section titled “Create the API client”Replace useStoryblokApi() and the storyblok integration’s apiOptions with @storyblok/api-client. Create one client instance and export it next to the component map.
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 },}));import { defineConfig } from "astro/config";import { storyblok } from "@storyblok/astro";
export default defineConfig({ integrations: [ storyblok({ accessToken: "YOUR_ACCESS_TOKEN", apiOptions: { region: "eu" }, components: { page: "storyblok/Page", feature: "storyblok/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" } });---import { useStoryblokApi } from "@storyblok/astro";const storyblokApi = useStoryblokApi();const { data } = await storyblokApi.get("cdn/stories/home", { version: "draft" });---apiClient.stories.get() takes the slug without the cdn/stories/ prefix, and API parameters such as version go in the query object.
Register blocks
Section titled “Register blocks”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.
Remove the integration configuration
Section titled “Remove the integration configuration”Version 11.x has no Astro integration. Remove the storyblok() entry, and the @storyblok/astro import, from astro.config.mjs.
Update block components
Section titled “Update block components”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:
---const { block, editable } = Astro.props;---
<div {...editable}> <h2>{block.headline}</h2></div>---import { storyblokEditable } from "@storyblok/astro";const { blok } = Astro.props;---
<div {...storyblokEditable(blok)}> <h2>{blok.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():
---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>---import { storyblokEditable } from "@storyblok/astro";import StoryblokComponent from "@storyblok/astro/StoryblokComponent.astro";const { blok } = Astro.props;---
<main {...storyblokEditable(blok)}>{blok.body?.map((blok) => <StoryblokComponent blok={blok} />)}</main>Fetch and render without live editing
Section titled “Fetch and render without live editing”To fetch and render content, use apiClient and StoryblokBlock:
---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} />---import { useStoryblokApi } from "@storyblok/astro";import StoryblokComponent from "@storyblok/astro/StoryblokComponent.astro";
const storyblokApi = useStoryblokApi();const { data } = await storyblokApi.get("cdn/stories/home", { version: "draft" });---
<StoryblokComponent blok={data.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.
Set up live preview
Section titled “Set up live preview”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:
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:
---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 />---import { getPayload, useStoryblokApi } from "@storyblok/astro";import StoryblokComponent from "@storyblok/astro/StoryblokComponent.astro";
const storyblokApi = useStoryblokApi();const payload = await getPayload({ locals: Astro.locals });const story = payload?.story ?? (await storyblokApi.get("cdn/stories/home", { version: "draft" })).data.story;---
<StoryblokComponent blok={story.content} />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.
Update rich text
Section titled “Update rich text”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} />---import StoryblokRichText from "@storyblok/astro/StoryblokRichText.astro";---
<StoryblokRichText document={blok.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:
export const { StoryblokBlock, StoryblokRichText } = defineStoryblokBlocks(() => ({ components: { page: Page, feature: Feature },}));---import { StoryblokRichText } from "../storyblok";const { block } = Astro.props;---
<StoryblokRichText document={block.richtext_field} />Further resources
Section titled “Further resources”Was this page helpful?
This site uses reCAPTCHA and Google's Privacy Policy (opens in a new window).Terms of Service (opens in a new window) apply.
Get in touch with the Storyblok community