@storyblok/angular
@storyblok/angular is Storyblok’s official SDK for Angular applications.
Requirements
Section titled “Requirements”- Angular version 19 or later
- Node.js LTS (version 22.x recommended)
- Modern web browser (for example, Chrome, Firefox, Safari, Edge — latest versions)
Installation
Section titled “Installation”Add the package to a project by running this command in the terminal:
npm install @storyblok/angular@latestConfiguration
Section titled “Configuration”Import and initialize the SDK using the access token of a Storyblok space.
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from "@angular/core";import { provideRouter, withComponentInputBinding } from "@angular/router";import { routes } from "./app.routes";import { provideClientHydration, withEventReplay } from "@angular/platform-browser";import { provideStoryblok, withLivePreview, withStoryblokComponents, type StoryblokClientConfig } from "@storyblok/angular";
const sbConfig: StoryblokClientConfig = { accessToken: "YOUR_ACCESS_TOKEN", region: "eu", inlineRelations: true,};export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), provideRouter(routes, withComponentInputBinding()), provideClientHydration(withEventReplay()), provideStoryblok( sbConfig, withStoryblokComponents({ page: () => import("./components/page/page.component").then((m) => m.PageComponent), }), withLivePreview() ), ],};Components
Section titled “Components”Create an Angular 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.
import { Component, ChangeDetectionStrategy, input } from "@angular/core";export interface FeatureBlok { headline?: string;}@Component({ selector: "app-feature", changeDetection: ChangeDetectionStrategy.OnPush, template: `<h3>{{ blok().headline }}</h3>`,})export class FeatureComponent { readonly blok = input.required<FeatureBlok>();}Use <sb-component> to automatically render nested components (provided they are registered globally).
import { Component, ChangeDetectionStrategy, input, computed } from "@angular/core";import { type SbBlokData, StoryblokComponent } from "@storyblok/angular";
export interface PageBlok { body?: SbBlokData[];}
@Component({ selector: "app-page", standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [StoryblokComponent], template: ` <div> <sb-component [sbBlok]="bloks()" /> </div> `,})export class PageComponent { readonly blok = input.required<PageBlok>(); readonly bloks = computed(() => this.blok().body ?? []);}Fetching and rendering
Section titled “Fetching and rendering”Use the client to fetch a story and render the content using StoryblokComponent.
import { Component, ChangeDetectionStrategy, inject, signal, computed, OnInit } from "@angular/core";import { StoryblokComponent, StoryblokService, Story } from "@storyblok/angular";
@Component({ selector: "app-home", changeDetection: ChangeDetectionStrategy.OnPush, imports: [StoryblokComponent], template: ` <div> <!-- Pass content directly - componnent handles null internally --> <sb-component [sbBlok]="storyContent()" /> </div> `,})export class HomeComponent implements OnInit { private readonly storyblok = inject(StoryblokService); private client = this.storyblok.getClient(); readonly story = signal<Story | null>(null); readonly loading = signal(true); readonly storyContent = computed(() => this.story()?.content);
async ngOnInit(): Promise<void> { try { const { data } = await this.client.stories.get("home", { query: { version: "draft", }, }); this.story.set((data?.story as Story) || null); } catch (error) { throw error; } finally { this.loading.set(false); } }}provideStoryblok
Section titled “provideStoryblok”Import and initialize the SDK to access and configure all features.
import { provideStoryblok } from "@storyblok/angular";
provideStoryblok(OPTIONS);provideStoryblok() creates an instance of the Storyblok Content Delivery API (CAPI) client and loads the Storyblok Bridge.
All The following options are available:
| Key | Description | Type |
|---|---|---|
config | All options listed in the @storyblok/api-client package reference are available in the first parameter. | StoryblokClientConfig |
withStoryblokComponents | An object that maps Angular components to Storyblok blocks. Each component receives a blok input containing the content of the block. | object |
withLivePreview | Enable or disable live preview for the Angular application. | BridgeParams | undefined |
withStoryblokRichtextComponents | An object that maps Angular components to Storyblok richtext mark and nodes. Each component receives a data input containing the content of the richtext. | object |
Example: withStoryblokComponents
Section titled “Example: withStoryblokComponents”Register Angular components as follows. The key represents the technical name of the Storyblok block, the value represents the Angular component. It supports both lazy and eager loading.
import { type StoryblokComponentsMap } from "@storyblok/angular";import { PageComponent } from "./components/page/page.component";const storyblokComponents: StoryblokComponentsMap = { page: PageComponent, teaser: () => import("./components/teaser/teaser.component").then((m) => m.TeaserComponent), grid: () => import("./components/grid/grid.component").then((m) => m.GridComponent), feature: () => import("./components/feature/feature.component").then((m) => m.FeatureComponent), "featured-articles": () => import("./components/feature-posts/feature-posts.component").then((m) => m.FeaturePostsComponent), "article-overview": () => import("./components/article-overview/article-overview.component").then((m) => m.ArticleOverviewComponent), article: () => import("./components/article/article.component").then((m) => m.ArticleComponent),};provideStoryblok(withStoryblokComponents(storyblokComponents));Example: withLivePreview
Section titled “Example: withLivePreview”withLivePreview enables and disables live preview for the Angular application. Pass BridgeParams to configure the Storyblok Bridge globally.
withLivePreview({ resolveRelations: ['feature_posts.posts'],}),Example: withStoryblokRichtextComponents
Section titled “Example: withStoryblokRichtextComponents”Register custom Angular components for richtext nodes and marks.
The key is the technical name of the Storyblok node or mark, and the value is the Angular component used to render it. Both eager and lazy loaded components are supported, with full type safety for keys and component props.
withStoryblokRichtextComponents({ link: () => import("./components/richtext/link.component").then((m) => m.LinkComponent), heading: () => import("./components/richtext/heading.component").then((m) => m.HeadingComponent),});Each component receives a required data input with the richtext node or mark data.
Example: Custom link component
Section titled “Example: Custom link component”import { Component, ChangeDetectionStrategy, input } from "@angular/core";import { type RichTextComponentProps } from "@storyblok/angular";
@Component({ selector: "app-link", changeDetection: ChangeDetectionStrategy.OnPush, template: ` <a [href]="data().attrs?.href" target="_blank" rel="noopener"> <ng-content /> </a> `, host: { style: "display: inline-block" },})export class LinkComponent { readonly data = input.required<RichTextComponentProps<"link">>();}Example: Custom heading component
Section titled “Example: Custom heading component”import { Component, ChangeDetectionStrategy, input } from "@angular/core";import { type RichTextComponentProps, SbRichTextComponent } from "@storyblok/angular";
@Component({ selector: "app-heading", changeDetection: ChangeDetectionStrategy.OnPush, template: ` <p> <sb-rich-text [sbDocument]="data().content" /> </p> `, host: { style: "display: contents" }, imports: [SbRichTextComponent],})export class HeadingComponent { readonly data = input.required<RichTextComponentProps<"heading">>();}StoryblokService
Section titled “StoryblokService”StoryblokService is the main entry point to access the underlying Storyblok @storyblok/api-client. It provides a typed, framework-aware wrapper with full access to the official Storyblok API client.
Use getClient() when you need direct access to the Storyblok SDK methods (for example fetching stories, datasources, or links).
const client = inject(StoryblokService).getClient();
const { data } = await client.stories.get(path, { query: { version: "draft", resolve_relations: "featured-articles.articles", },});See the @storyblok/api-client reference.
LivePreviewService
Section titled “LivePreviewService”LivePreviewService enables real-time updates from the Storyblok Visual Editor. It listens for story changes and automatically updates your local state when editing in preview mode.
Use it alongside the Storyblok client to fetch the initial story, then subscribe to live updates.
export class ExampleComponent { private readonly livePreview = inject(LivePreviewService);
private readonly story = signal<Story | null>(null); readonly bridgeConfig: BridgeParams = { resolveRelations: ["featured-articles.articles"], };
ngOnInit(): void { this.livePreview.listen((updatedStory) => { this.story.set((updatedStory as Story) || null); }, this.bridgeConfig); }}This keeps responsibilities clean: StoryblokService handles data fetching, while LivePreviewService manages real-time synchronization.
<sb-component>
Section titled “<sb-component>”<sb-component> renders Storyblok blocks dynamically. The sbBlok input accepts either a single Storyblok block or an array of blocks.
<sb-component [sbBlok]="blok.body" />SbBlokDirective
Section titled “SbBlokDirective”SbBlokDirective renders a single Storyblok block dynamically inside an Angular template. It accepts a sbBlok input.
<ng-container [sbBlok]="blok" /><sb-rich-text>
Section titled “<sb-rich-text>”<sb-rich-text> renders Storyblok rich text documents. It accepts a sbDocument input, which represents the rich text JSON structure.
<sb-rich-text [sbDocument]="blok.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