Skip to content

@storyblok/vue

@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>
Introduced in 9.3.0

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

<StoryblokRichText :doc="blok.richtext_field" />

See the @storyblok/richtext reference for further details.

<script setup>
import { type VNode, h } from "vue";
import { StoryblokRichText, BlockTypes, MarkTypes, type StoryblokRichTextNode } from "@storyblok/vue";
import { RouterLink } from "vue-router";
import CodeBlok from "./components/CodeBlok.vue";
const resolvers = {
// RouterLink example:
[MarkTypes.LINK]: (node: StoryblokRichTextNode<VNode>) => {
return node.attrs?.linktype === 'STORY'
? h(RouterLink, {
to: node.attrs?.href,
target: node.attrs?.target,
}, node.text)
: h('a', {
href: node.attrs?.href,
target: node.attrs?.target,
}, node.text)
},
// Custom code block component example:
[BlockTypes.CODE_BLOCK]: (node: Node) => {
return h(CodeBlock, {
class: node?.attrs?.class,
}, node.children)
},
}
</script>
<template>
<StoryblokRichText :doc="blok.articleContent" :resolvers="resolvers" />
</template>

Use this composable to programmatically render a rich text field.

<StoryblokRichText :doc="blok.richtext_field" />

See the @storyblok/richtext reference for further details.

<script setup>
import { type VNode, h } from "vue";
import { StoryblokRichText, BlockTypes, MarkTypes, type StoryblokRichTextNode } from "@storyblok/vue";
import { RouterLink } from "vue-router";
import CodeBlok from "./components/CodeBlok.vue";
const resolvers = {
// RouterLink example:
[MarkTypes.LINK]: (node: StoryblokRichTextNode<VNode>) => {
return node.attrs?.linktype === 'STORY'
? h(RouterLink, {
to: node.attrs?.href,
target: node.attrs?.target,
}, node.text)
: h('a', {
href: node.attrs?.href,
target: node.attrs?.target,
}, node.text)
},
// Custom code block component example:
[BlockTypes.CODE_BLOCK]: (node: Node) => {
return h(CodeBlock, {
class: node?.attrs?.class,
}, node.children)
},
}
</script>
<template>
<StoryblokRichText :doc="blok.articleContent" :resolvers="resolvers" />
</template>