---
title: Integrate Angular with Storyblok
description: This guide walks you through integrating Storyblok as a headless CMS, fetching content, and building components to render it in your Angular project.
url: https://www.storyblok.com/docs/guides/angular
---

# Integrate Angular with Storyblok

Use Storyblok to manage the content of your Angular application.

## Quickstart

The fastest way to get started is using Storyblok’s Core Blueprint or CLI wizard:

-   Blueprint (UI)
    
    Use [Storyblok’s Core Blueprint](/docs/concepts/blueprints) to [launch an Angular project](https://app.storyblok.com/#/spaces/new/blueprint?blueprintReference=starter), complete with a space, GitHub repository, and deployment environment.
    
    Log in to [Storyblok](https://app.storyblok.com/#/me/spaces), select **\+ Add Space** → **Start from Blueprint**, and follow the steps.
    
-   CLI
    
    Use [Storyblok’s CLI](/docs/libraries/storyblok-cli) to set up a new space and scaffold an Angular project. In the terminal, run:
    
    ```bash
    storyblok create --template angular
    ```

## Manual setup

Alternatively, set up the project from scratch and configure your frontend code.

This guide has been tested with the following package versions:

-   `@angular/core@21.2.0`
-   `@storyblok/angular`
-   `Node.js v22.13.0`

[No Storyblok account?](https://app.storyblok.com/#/signup)Sign up and create a space for free

1.  **Create a [new space](https://app.storyblok.com/#/me/spaces/new?tab=select-plan)**
    
    Log in to [Storyblok](https://app.storyblok.com/#/me/spaces), select **\+ Add Space** → **New Space**, and follow the steps.
    
2.  **Create an Angular project**
    
    Install the Angular CLI, then create a new project with SSR support following the official [installation guide](https://angular.dev/installation#create-a-new-project).
    
    ```bash
    npm install -g @angular/cli
    ```
    
    ```bash
    ng new my-storyblok-app --ssr
    cd my-storyblok-app
    ```
    
3.  **Install `@storyblok/angular`**
    
    In the terminal, install the package.
    
    ```bash
    npm install @storyblok/angular
    ```
    

Next, to use environment variables in your Angular app, [generate environments](https://angular.dev/cli/generate/environments) files with the Angular CLI.

```bash
ng generate environments
```

Add the Storyblok access token to the environment files in the `src/environments` folder. Set `production` to `false` in `environment.ts` file and to `true` in `environment.production.ts` file.

src/environments/environment.ts

```typescript
export const environment = {
  production: true,
  accessToken: "YOUR-ACCESS-TOKEN",
};
```

src/environments/environment.development.ts

```typescript
export const environment = {
  production: false,
  accessToken: "YOUR-ACCESS-TOKEN",
};
```

> [!NOTE]
> Learn how to get an [access token](/docs/concepts/access-tokens) for your Storyblok project.

> [!NOTE]
> Angular automatically selects the appropriate environment file based on the build configuration. For more information on using environments, refer to the [official Angular documentation](https://angular.dev/tools/cli/environments#using-environment-specific-variables-in-your-app).

Update the `app.config.ts` file to configure the Storyblok Angular package.

src/app/app.config.ts

```typescript
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { environment } from '../environments/environment';
import { provideStoryblok } from '@storyblok/angular';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideRouter(routes),
    provideClientHydration(withEventReplay()),
    provideStoryblok({
      accessToken: environment.accessToken,
      region: 'eu',
    }),
  ],
};
```

The Storyblok Angular package provides features such as fetching content from the Content Delivery API, component registration, and real-time visual editing available across your project.

## Fetch a single story

Create a new file `src/app/routes/home.component.ts` and add the following code.

src/app/routes/home.component.ts

```typescript
import { Component, ChangeDetectionStrategy, inject, signal, OnInit } from "@angular/core";
import { StoryblokService, StoryblokComponent, type SbBlokData } from "@storyblok/angular";

@Component({
  selector: "app-home",
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [StoryblokComponent],
  template: `
    <div>
      <sb-component [sbBlok]="storyContent()" />
    </div>
  `,
})
export class HomeComponent implements OnInit {
  private readonly storyblok = inject(StoryblokService);
  readonly storyContent = signal<SbBlokData | null>(null);

  async ngOnInit() {
    const client = this.storyblok.getClient();
    const { data } = await client.stories.get("home", {
      query: { version: "draft" },
    });
    this.storyContent.set(data?.story?.content ?? null);
  }
}
```

`StoryblokService` provides access to the Storyblok API client to fetch story data. `StoryblokComponent` dynamically renders content type and nestable blocks. In this case, it looks for the content type block of the home story.

## Create and register blocks

Create `page.component.ts` component to render all stories of the `page` content type, such as the home story.

src/app/components/page/page.component.ts

```typescript
import { Component, ChangeDetectionStrategy, input } from "@angular/core";
import { type SbBlokData, StoryblokComponent } from "@storyblok/angular";

export interface PageBlok extends SbBlokData {
  body?: SbBlokData[];
}

@Component({
  selector: "app-page",
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [StoryblokComponent],
  template: `<sb-component class="page" [sbBlok]="blok().body" />`,
})
export class PageComponent {
  // Angular signal
  readonly blok = input.required<PageBlok>();
}
```

The `PageComponent` passes the array of blocks in the `body` block field to the `StoryblokComponent`, which dynamically renders each block. It uses Angular signal for its `blok` input, ensuring the template updates automatically when the input changes.

Stories can have a `body` field that contains an array of custom type blocks, such as Feature, Teaser, and Grid.

Create the code for these components as follows.

src/app/components/feature/feature.component.ts

```typescript
import { Component, ChangeDetectionStrategy, input } from "@angular/core";
import { type SbBlokData } from "@storyblok/angular";

export interface FeatureBlok extends SbBlokData {
  name?: string;
  description?: string;
}

@Component({
  selector: "app-feature",
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="feature">
      <h3>{{ blok().name }}</h3>
      <p>{{ blok().description }}</p>
    </div>
  `,
})
export class FeatureComponent {
  readonly blok = input.required<FeatureBlok>();
}
```

src/app/components/teaser/teaser.component.ts

```typescript
import { Component, ChangeDetectionStrategy, input } from "@angular/core";
import { type SbBlokData } from "@storyblok/angular";

export interface TeaserBlok extends SbBlokData {
  headline?: string;
}

@Component({
  selector: "app-teaser",
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="teaser">
      <h1>{{ blok().headline }}</h1>
    </div>
  `,
})
export class TeaserComponent {
  readonly blok = input.required<TeaserBlok>();
}
```

src/app/components/grid/grid.component.ts

```typescript
import { Component, ChangeDetectionStrategy, input } from "@angular/core";
import { StoryblokComponent, type SbBlokData } from "@storyblok/angular";

export interface GridBlok extends SbBlokData {
  columns?: SbBlokData[];
}

@Component({
  selector: "app-grid",
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [StoryblokComponent],
  template: `<sb-component class="grid" [sbBlok]="blok().columns" />`,
})
export class GridComponent {
  readonly blok = input.required<GridBlok>();
}
```

Similar to `page.component.ts`, `grid.component.ts` passes the array of blocks in the `columns` block field to the `StoryblokComponent`.

Create a component registry for Storyblok components in `storyblok.components.ts` and use lazy loading to reduce the initial bundle size.

src/app/storyblok.components.ts

```typescript
import { type StoryblokComponentsMap } from "@storyblok/angular";

export const storyblokComponents: StoryblokComponentsMap = {
  page: () => import("./components/page/page.component").then((m) => m.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),
};
```

Update `app.config.ts` to register Storyblok components. Pass the `storyblokComponents` registry to the `withStoryblokComponents` provider.

By default, Angular does not bind route data to component inputs. Add `withComponentInputBinding()` to the `app.config.ts` file to allow resolved data to be passed directly as component inputs. `withComponentInputBinding()` automatically binds route resolver data to matching component inputs.

src/app/app.config.ts

```typescript
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { environment } from '../environments/environment';

import { provideStoryblok, withStoryblokComponents } from '@storyblok/angular';
import { provideStoryblok } from '@storyblok/angular';
import { storyblokComponents } from './storyblok.components'

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideRouter(routes, withComponentInputBinding()),
    provideRouter(routes),
    provideClientHydration(withEventReplay()),
    provideStoryblok(
      {
        accessToken: environment.accessToken,
        region: 'eu',
      },
    withStoryblokComponents(storyblokComponents),
    ),
  ],
};
```

Update `src/app/app.routes.ts` file to define the routes, so that the root path `/` renders the `HomeComponent`.

src/app/app.routes.ts

```typescript
import { Routes } from "@angular/router";
import { HomeComponent } from "./routes/home.component";

export const routes: Routes = [{ path: "", component: HomeComponent }];
```

Finally, remove the contents in `src/app/app.html` and add the `router-outlet`. The router uses it to render the component that matches the current URL based on the defined routes.

src/app/app.html

```html
<router-outlet></router-outlet>
```

Run the server and visit the site in your browser.

```bash
npm start
```

## Related resources

[Storyblok's Angular Blueprint Repository](https://github.com/storyblok/blueprint-core-angular)

[@storyblok/angular Package Reference](https://www.storyblok.com/docs/libraries/js/angular-sdk)

[Concept: Blocks](/docs/concepts/blocks)

[Content Delivery API: Retrieve a Single Story](/docs/api/content-delivery/v2/stories/retrieve-a-single-story)

[Angular Docs](https://angular.dev/overview)

## Pagination

-   [Next: Visual Preview in Angular](/docs/guides/angular/visual-preview)
