---
title: Migrate to @storyblok/astro 11.x
description: Upgrade an Astro application from @storyblok/astro 8.x-10.x to 11.x.
url: https://www.storyblok.com/docs/libraries/js/astro-sdk/migration-from-v8
---

# 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`](/docs/libraries/js/content-delivery-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.

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

Follow these steps to migrate an application:

1.  [Upgrade](#update-the-packages) `@storyblok/astro` and add `@storyblok/api-client`.
2.  [Create one `apiClient` instance](#create-the-api-client) and move the `apiOptions` of the `storyblok` integration to `createApiClient()`. Replace `useStoryblokApi()` and `storyblokApi` calls with `apiClient.stories.get()`.
3.  [Replace the `components` option](#register-blocks) with `defineStoryblokBlocks(() => ({ components }))`. Export its `StoryblokBlock` and `StoryblokRichText` from one module.
4.  [Remove the `storyblok` integration](#remove-the-integration-configuration) from `astro.config.mjs`.
5.  [Rename `blok` to `block`](#update-block-components) 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](#fetch-and-render-without-live-editing) using `apiClient` and `StoryblokBlock`.
7.  [Add `storyblokPreviewMiddleware`](#set-up-live-preview), and replace the `livePreview` and `bridge` options with `getPayload()` and `StoryblokPreview`.
8.  [Update rich text](#update-rich-text), including the `StoryblokRichText` import and type names.

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

| 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

| 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

| 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

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

```bash
npm install @storyblok/astro@latest @storyblok/api-client@latest
```

## 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.

-   Version 11.x
    
    src/storyblok.ts
    
    ```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 },
    }));
    ```
    
-   Version 8.x-10.x
    
    astro.config.mjs
    
    ```js
    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`:

-   Version 11.x
    
    ```ts
    const { data } = await apiClient.stories.get("home", { query: { version: "draft" } });
    ```
    
-   Version 8.x-10.x
    
    ```astro
    ---
    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

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

```ts
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`](https://vite.dev/guide/features#glob-import).

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

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

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

-   Version 11.x
    
    src/components/Feature.astro
    
    ```astro
    ---
    const { block, editable } = Astro.props;
    ---
    
    <div {...editable}>
      <h2>{block.headline}</h2>
    </div>
    ```
    
-   Version 8.x-10.x
    
    src/storyblok/Feature.astro
    
    ```astro
    ---
    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()`:

-   Version 11.x
    
    src/components/Page.astro
    
    ```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>
    ```
    
-   Version 8.x-10.x
    
    src/storyblok/Page.astro
    
    ```astro
    ---
    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

To fetch and render content, use `apiClient` and `StoryblokBlock`:

-   Version 11.x
    
    src/pages/index.astro
    
    ```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} />
    ```
    
-   Version 8.x-10.x
    
    src/pages/index.astro
    
    ```astro
    ---
    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

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

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

-   Version 11.x
    
    src/pages/index.astro
    
    ```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 />
    ```
    
-   Version 8.x-10.x
    
    src/pages/index.astro
    
    ```astro
    ---
    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

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

-   Version 11.x
    
    ```astro
    ---
    import { StoryblokRichText } from "@storyblok/astro";
    ---
    
    <StoryblokRichText document={block.richtext_field} />
    ```
    
-   Version 8.x-10.x
    
    ```astro
    ---
    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`:

src/storyblok.ts

```ts
export const { StoryblokBlock, StoryblokRichText } = defineStoryblokBlocks(() => ({
  components: { page: Page, feature: Feature },
}));
```

```astro
---
import { StoryblokRichText } from "../storyblok";
const { block } = Astro.props;
---

<StoryblokRichText document={block.richtext_field} />
```

## Further resources

[Repository Playground](https://github.com/storyblok/monoblok/tree/main/packages/astro/playground)Find every scenario in this guide in the repository playground.

[@storyblok/astro (Version 11.x) Package Reference](/docs/libraries/js/astro-sdk)Learn about every component, middleware, and helper in the @storyblok/astro 11.x reference.

[@storyblok/api-client Package Reference](/docs/libraries/js/content-delivery-api-client)Learn how to fetch content from the Content Delivery API in the @storyblok/api-client reference.
