Skip to content

@storyblok/vue is Storyblok’s official development for Vue applications.

  • Vue version 3.4 or later
  • Node.js LTS (version 22.x recommended)
  • Modern web browser (e.g., Chrome, Firefox, Safari, Edge – latest versions)

Add the package to a project by running this command in the terminal:

Terminal window
npm install @storyblok/vue@latest

Import and initialize the SDK using the access token of a Storyblok space.

src/main.js
import { createApp } from "vue";
import { StoryblokVue, apiPlugin } from "@storyblok/vue";
import App from "./App.vue";
const app = createApp(App);
app.use(StoryblokVue, {
accessToken: "YOUR_ACCESS_TOKEN",
use: [apiPlugin],
apiOptions: {
region: "eu",
},
});
app.component("Page", Page);
app.component("Feature", Feature);
app.mount("#app");

Create a Vue component for each block defined in Storyblok and registered in the configuration. Each component will receive a blok prop, containing the content of the block.

src/components/Feature.vue
<script setup>
defineProps({ blok: Object });
</script>
<template>
<div v-editable="blok">
<h2>{blok.headline}</h2>
</div>
</template>

Use <StoryblokComponent> to automatically render nested components (provided they are registered globally).

src/components/Page.vue
<script setup>
defineProps({ blok: Object });
</script>
<template>
<main>
<StoryblokComponent v-for="currentBlok in blok.body" :key="currentBlok._uid" :blok="currentBlok" />
</main>
</template>

You can use slots to insert content into the dynamic component:

<template>
<StoryblokComponent v-if="story" :blok="story.content">
<MyCustomComponent />
</StoryblokComponent>
</template>

Then, in the dynamic component that StoryblokComponent uses, you can render the slot content as you would with regular Vue slots:

<template>
<div>
<!-- Some content -->
<!-- The slot content MyCustomComponent will be rendered here -->
<slot></slot>
<!-- Some more content -->
</div>
</template>

In a Vue component, use the client to fetch a story and render the content using StoryblokComponent.

src/App.vue
<script setup>
import { useStoryblokApi } from '@storyblok/vue';
const storyblokApi = useStoryblokApi();
const { data } = await storyblokApi.get('cdn/stories/home', {
version: 'draft',
});
const { story } = data;
</script>
<template>
<StoryblokComponent v-if="story" :blok="story.content" />
</template>

Import and initialize the SDK to access and configure all features.

import { createApp } from "vue";
import { StoryblokVue } from "@storyblok/vue";
import App from "./App.vue";
import MyCustomFallback from "./components/MyCustomFallback.vue";
const app = createApp(App);
app.use(StoryblokVue, {
// ...
enableFallbackComponent: true,
customFallbackComponent: "MyCustomFallback",
});
app.component("MyCustomFallback", MyCustomFallback);

All options listed in the @storyblok/js package reference are available. The following additional options are available:

KeyDescriptionType
enableFallbackComponentEnable or disable a fallback component to be rendered if no Vue component has been defined for a Storyblok block. Disabled by default.boolean
customFallbackComponentRegister a custom fallback component. Requires enableFallbackComponent to be enabled. See example below.string
import MyCustomFallback from "./components/MyCustomFallback.vue";
app.use(StoryblokVue, {
// ...
enableFallbackComponent: true,
customFallbackComponent: "MyCustomFallback",
});
app.component("MyCustomFallback", MyCustomFallback);

apiPlugin configures the implementation of the Storyblok API. It is imported from @storyblok/js.

import { StoryblokVue, apiPlugin } from "@storyblok/vue";
app.use(StoryblokVue, {
use: [apiPlugin],
});

See the @storyblok/js reference for further details.

Enable both data fetching and bridge capabilities using this composable.

<script setup>
import { useStoryblok } from "@storyblok/vue";
const { story, fetchState } = useStoryblok(URL, API_OPTIONS, BRIDGE_OPTIONS);
</script>

For the API_OPTIONS, see the storyblok-js-client reference. For the BRIDGE_OPTIONS, see the @storyblok/preview-bridge reference.

useStoryblokApi() returns the client instantiated in the application.

<script setup>
import {useStoryblokApi} from '@storyblok/vue';
const storyblokApi = useStoryblokApi();
const {data} = await storyblokApi.get(URL, API_OPTIONS)
</script>

For the API_OPTIONS, see the storyblok-js-client reference.

useStoryblokBridge() activates the Storyblok Bridge.

<script setup>
import {useStoryblokApi, useStoryblokBridge} from '@storyblok/vue';
const storyblokApi = useStoryblokApi();
const {data} = await storyblokApi.get(URL, API_OPTIONS);
useStoryblokBridge(STORY_ID, CALLBACK, BRIDGE_OPTIONS);
</script>

For the BRIDGE_OPTIONS, see the @storyblok/preview-bridge reference.

This component automatically renders Storyblok blocks for corresponding Vue components registered in the application. It requires a blok property. Any additional passed properties are forwarded to the Vue component.

Use it to iterate over blocks fields as follows:

<StoryblokComponent
v-for="currentBlok in blok.nested_bloks"
:key="currentBlok._uid"
:blok="currentBlok"
/>

Use the v-editable directive in components to connect them to the Storyblok Bridge.

<script setup>
defineProps({ blok: Object });
</script>
<template>
<section v-editable="blok">
<h3>{{ blok.name }}</h3>
</section>
</template>

Used to render a rich text field from a Storyblok story.

<template>
<StoryblokRichText :document="blok.richtext_field" />
</template>

The component accepts the following optional props:

  • components: Allows custom Vue components to be provided for supported rich text nodes and marks.
  • optimizeImage: Allows image optimization options to be configured for image nodes.

See the @storyblok/richtext reference for a complete list of supported nodes, marks, and customization options.

Custom components can be registered using the components prop. The key must match a supported rich text node or mark name.

import type { SbVueRichTextComponentMap } from "@storyblok/vue";
import CustomHeading from "./CustomHeading.vue";
import CustomLink from "./CustomLink.vue";
import CustomTable from "./CustomTable.vue";
import { h } from "vue";
const components: SbVueRichTextComponentMap = {
heading: CustomHeading,
link: CustomLink,
table: CustomTable,
paragraph: ({ content }, { slots }) =>
h(
"p",
{
class: "custom-paragraph",
},
slots.default?.()
),
};

The components can then be passed to StoryblokRichText:

<template>
<StoryblokRichText :document="blok.richtext_field" :components="components" />
</template>

Marks receive their rendered child content through Vue’s <slot />.

<script setup lang="ts">
import type { SbVueRichTextProps } from "@storyblok/vue";
const props = defineProps<SbVueRichTextProps["link"]>();
</script>
<template>
<a :href="props.attrs?.href ?? ''" :target="props.attrs?.target ?? '_self'">
<slot />
</a>
</template>

Nodes can use Vue’s <slot /> to render child content.

<script setup lang="ts">
import type { SbVueRichTextProps } from "@storyblok/vue";
const props = defineProps<SbVueRichTextProps["heading"]>();
</script>
<template>
<component :is="`h${props.attrs?.level ?? 1}`" class="custom-heading">
<slot />
</component>
</template>

Example: Custom table component (advanced node)

Section titled “Example: Custom table component (advanced node)”

For advanced use cases, nodes can access content and context to recursively render child nodes using StoryblokRichText.

The splitTableRows utility can be used to separate table header rows from body rows when creating custom table components.

<script setup lang="ts">
import { StoryblokRichText, splitTableRows, type SbVueRichTextProps } from "@storyblok/vue";
const props = defineProps<SbVueRichTextProps["table"]>();
const { headerRows, bodyRows } = splitTableRows(props.content);
</script>
<template>
<table class="custom-table">
<thead v-if="headerRows">
<StoryblokRichText :document="headerRows" v-bind="props.context" />
</thead>
<tbody>
<StoryblokRichText :document="bodyRows" v-bind="props.context" />
</tbody>
</table>
</template>

useStoryblokRichText can be used to programmatically render rich text content.

<script setup lang="ts">
import { useStoryblokRichText, type SbVueRichTextComponentMap } from "@storyblok/vue";
import CustomLink from "../components/richtext/CustomLink.vue";
const components: SbVueRichTextComponentMap = {
link: CustomLink,
};
const render = useStoryblokRichText({ components });
const RichText = () => render(story.value?.content.richText);
</script>
<template>
<RichText />
</template>

Accepts the same configuration options as StoryblokRichText, including components and optimizeImage.

The Vue SDK also exports the core rich text utilities from @storyblok/richtext. These can be useful when implementing advanced custom components, such as custom image or table rendering.

Available utilities include:

  • renderRichText: The core rich text rendering function.
  • buildStoryblokImage: Generates optimized Storyblok Image Service URLs.
  • splitTableRows: Splits table rows into headerRows and bodyRows.

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.