Skip to content

@storyblok/angular

@storyblok/angular is Storyblok’s official SDK for Angular applications.

  • Angular version 19 or later
  • Node.js LTS (version 22.x recommended)
  • Modern web browser (for example, Chrome, Firefox, Safari, Edge — latest versions)

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

Terminal window
npm install @storyblok/angular@latest

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

src/app/app.config.ts
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()
),
],
};

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.

src/app/components/feature/feature.component.ts
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).

src/app/components/page/page.component.ts
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 ?? []);
}

Use the client to fetch a story and render the content using StoryblokComponent.

src/app/routes/home/home.component.ts
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);
}
}
}

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:

KeyDescriptionType
configAll options listed in the @storyblok/api-client package reference are available in the first parameter.StoryblokClientConfig
withStoryblokComponentsAn object that maps Angular components to Storyblok blocks. Each component receives a blok input containing the content of the block.object
withLivePreviewEnable or disable live preview for the Angular application.BridgeParams | undefined
withStoryblokRichtextComponentsAn object that maps Angular components to Storyblok richtext mark and nodes. Each component receives a data input containing the content of the richtext.object

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));

withLivePreview enables and disables live preview for the Angular application. Pass BridgeParams to configure the Storyblok Bridge globally.

withLivePreview({
resolveRelations: ['feature_posts.posts'],
}),

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.

src/app/components/richtext/link.component.ts
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">>();
}
src/app/components/richtext/heading.component.ts
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 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 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> renders Storyblok blocks dynamically. The sbBlok input accepts either a single Storyblok block or an array of blocks.

<sb-component [sbBlok]="blok.body" />

SbBlokDirective renders a single Storyblok block dynamically inside an Angular template. It accepts a sbBlok input.

<ng-container [sbBlok]="blok" />

<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" />

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.