Loaders

Purpose and Scope

Loaders are Storybook’s advanced mechanism for preparing asynchronous data before a story appears in the canvas. A loader is an asynchronous function associated with a story, component, or global preview configuration. Storybook runs applicable loaders before rendering, then exposes their combined return values through the story render context. This page explains when to use that escape hatch, how loaded data reaches stories and decorators, and how inheritance rules determine the final context. It is focused on story authors who already understand stories, args, decorators, and the preview configuration file. Sources: docs/writing-stories/loaders.mdx

The main reader problem is deciding whether a story really needs a loader. The documentation positions loaders as useful for assets, lazy loaded components, or remote API data, but it also warns that args are the preferred way to manage story data. Args integrate with Storybook’s growing tool ecosystem, including controls and other story-level workflows, while loaded data is more specialized. A good default is to model component state with args and reserve loaders for data that genuinely must be fetched or prepared asynchronously before rendering. Sources: docs/writing-stories/loaders.mdx

Relevant Source Files

  • docs/writing-stories/loaders.mdx — Defines the reader-facing loaders feature, the timing of loader execution, global loader configuration, and inheritance precedence across global, component, and story levels.

Core Concepts

A story is normally an isolated component example whose state is described inside the story file, often through args. A loader changes that flow by introducing a pre-render phase. During that phase, Storybook calls asynchronous functions and merges their results into a field named loaded on the story context. The context is then available to the story function and to decorators, so both the rendered component and its wrappers can respond to data that was fetched or computed before the story mounted. Sources: docs/writing-stories/loaders.mdx

The distinction between args and loaded data matters for maintainability. Args describe controllable component inputs, such as React props, Vue props, Angular inputs, or equivalent framework concepts. Loaded data is not primarily a control surface; it is a prepared payload that accompanies the render context. For example, an API response can be fetched by a loader, then a story can combine that response with args before passing values to a component. The docs explicitly describe a React pattern where args are spread first so they take priority over static loader-provided data. Sources: docs/writing-stories/loaders.mdx

Execution Flow

When Storybook selects a story, it determines every loader that applies to that story before the canvas render. Applicable loaders can come from the preview file, from the component’s default export, and from the individual story export. Storybook runs all of them before rendering the story in the canvas, and the returned objects contribute to the context’s loaded data. This is why loaders are appropriate for preconditions such as fetching a current user, loading an external asset, or preparing data that decorators need before they wrap the story. Sources: docs/writing-stories/loaders.mdx

The docs describe parallel execution rather than a sequential pipeline. That has an important design consequence: a loader should not assume that another loader has already completed and populated the context. If two pieces of data have a dependency, keep them inside one loader or make the dependency explicit in the asynchronous work that loader performs. Parallel execution is useful for performance because unrelated fetches can begin together, but it also means authors should avoid hidden ordering assumptions except for the documented result precedence when keys overlap. Sources: docs/writing-stories/loaders.mdx

API Components and Inheritance Reference

The public authoring surface is intentionally compact. At the story level, add a loaders annotation to the story export. At the component level, add loaders to the component metadata. At the global level, export loaders from .storybook/preview.js or the equivalent preview file used by the project. Each loader returns an object, and those objects are merged into context.loaded. A global loader is a convenient way to make shared data, such as the current user, available to every story. Sources: docs/writing-stories/loaders.mdx

export const Example = {
  loaders: [async () => ({ todo: await fetchTodo() })],
  render: (args, { loaded }) => renderTodo({ ...loaded.todo, ...args }),
};

Loader inheritance follows the same broad placement model as parameters: global, component, and story. The exact precedence is documented from lowest to highest as global loaders in their defined order, component loaders in their defined order, and story loaders in their defined order. All applicable results are placed under loaded. If multiple loaders return the same key, the later loader wins according to that precedence model. That lets a story override a globally supplied fixture while still benefiting from common setup declared once in preview configuration. Sources: docs/writing-stories/loaders.mdx

Usage Guidance and Edge Cases

Use loaders sparingly and deliberately. They are described as an advanced feature and an escape hatch, not the default data modeling style for stories. A loader is a good fit when the story cannot reasonably define its data inline, when the data must come from a remote API, when a component or asset should be lazily loaded for performance, or when a decorator needs asynchronous setup before rendering. If the same goal can be reached with stable args, the docs recommend args because more Storybook tools and techniques are built around them. Sources: docs/writing-stories/loaders.mdx

The most common edge case is overlapping keys. Because results are merged into a single loaded field, authors should choose clear names and avoid accidental collisions between global and local loaders. Intentional collisions can be useful for overriding defaults, but accidental collisions make stories harder to reason about. Another edge case is framework-specific rendering. The docs call out React as an example where a story function receives context as its second argument, while other frameworks such as Angular can write stories in their usual style. The shared contract is still the same loaded context data. Sources: docs/writing-stories/loaders.mdx

Practical Workflow

Start by writing the story with args and static data. If that becomes inadequate because the data is external, expensive, or shared with decorators, introduce a story-level loader first. Keeping the loader close to the story makes the behavior easy to inspect when browsing the story file. If several stories for the same component need the same data, move the loader to the component level. If every story needs the data, such as a current user or application shell fixture, move it to the preview configuration as a global loader. Sources: docs/writing-stories/loaders.mdx

After adding a loader, verify the story in the Storybook canvas and check the relationship between args and loaded values. Args should remain the primary way to express the component state that readers and controls need to understand. Loaded values should be treated as setup data that the story consumes. If a loader fetches remote data, consider whether the story remains deterministic enough for development, documentation, and testing workflows. Stable examples are easier for teammates to browse and easier for automated checks to reproduce. Sources: docs/writing-stories/loaders.mdx

Read the args documentation before reaching for loaders, because the loaders page explicitly recommends args for story data whenever possible. Read decorators next if the asynchronous setup is needed by wrappers such as layout providers, theme providers, or application context. Read parameters if you want to compare loader inheritance with Storybook’s other hierarchical annotation system. Finally, review play functions if the goal is not data preparation but post-render interaction or assertion logic, because play functions run after the story has rendered rather than before it. Sources: docs/writing-stories/loaders.mdx