Skip to content

@storyblok/vue (Version 10.x)

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

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

See the @storyblok/richtext reference for further details.

Use asTag to render a RouterLink component for internal story links:

<script setup>
import { Mark } from '@tiptap/core';
import { asTag } from '@storyblok/vue';
import { RouterLink } from 'vue-router';
const CustomLink = Mark.create({
name: 'link',
renderHTML({ HTMLAttributes }) {
if (HTMLAttributes.linktype === 'story') {
return [asTag(RouterLink), { to: HTMLAttributes.href }, 0];
}
return ['a', { href: HTMLAttributes.href, target: HTMLAttributes.target }, 0];
},
});
</script>
<template>
<StoryblokRichText :doc="blok.richtext_field" :tiptap-extensions="{ link: CustomLink }" />
</template>

Use this composable to programmatically render a rich text field.

<script setup>
import { useStoryblokRichText } from '@storyblok/vue';
const { render } = useStoryblokRichText({});
const root = () => render(blok.articleContent);
</script>
<template>
<root />
</template>

See the @storyblok/richtext reference for further details.

<script setup>
import Heading from '@tiptap/extension-heading';
import { useStoryblokRichText } from '@storyblok/vue';
const CustomHeading = Heading.extend({
renderHTML({ node, HTMLAttributes }) {
const level = node.attrs.level;
return [`h${level}`, { class: `heading-${level}`, ...HTMLAttributes }, 0];
},
});
const { render } = useStoryblokRichText({
tiptapExtensions: { heading: CustomHeading },
});
const root = () => render(blok.articleContent);
</script>
<template>
<root />
</template>

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.