How to Create Custom Decorators Using makeDecorator in Storybook

makeDecorator is a factory function exported from storybook/preview-api that creates reusable decorators by wrapping stories with custom logic based on provided options and story parameters.

Creating custom decorators using makeDecorator represents the standard pattern for Storybook addon authors who need to inject behavior, styles, or context around stories. The utility lives in the core preview API at code/core/src/preview-api/modules/addons/make-decorator.ts and provides a structured way to handle configuration via story parameters or direct options, as documented in docs/addons/addons-api.mdx.

Understanding the makeDecorator API Structure

The makeDecorator function follows a precise implementation pattern defined in lines 44‑66 of the source file. It accepts a configuration object and returns a decorator function that can be invoked with or without options.

Function Signature and Parameters

The makeDecorator signature accepts the following properties:

  • name: A string identifier for the decorator used in error messages and debugging.
  • parameterName: The key used to read configuration from story parameters.
  • wrapper: A function receiving (getStory, context, { options, parameters }) that returns the rendered story.
  • skipIfNoParametersOrOptions (optional): A boolean that prevents the wrapper from executing when neither options nor parameters are provided.

According to the source code in code/core/src/preview-api/modules/addons/make-decorator.ts, the returned function behaves differently based on invocation:

  • Without options: Used directly as addDecorator(myDecorator).
  • With options: Used as addDecorator(myDecorator({ foo: 'bar' })).

If a user mistakenly passes a story function directly into the returned decorator, the code throws a clear error at lines 84‑88.

Creating a Parameter-Driven Decorator

The most common pattern involves reading configuration from story parameters. The wrapper receives parameters via the third argument object, allowing you to customize rendering based on story-specific settings.

The following example demonstrates a background decorator that reads color values from story parameters, adapted from the minimal example in docs/_snippets/storybook-addons-api-makedecorator.md:

// src/decorators/withBackground.ts
import { makeDecorator } from 'storybook/preview-api';

export const withBackground = makeDecorator({
  name: 'withBackground',
  parameterName: 'background',
  wrapper: (getStory, context, { parameters }) => {
    const color = parameters?.color ?? 'transparent';
    return (
      <div style={{ background: color, padding: '1rem' }}>
        {getStory(context)}
      </div>
    );
  },
});

Register the decorator globally in your preview configuration:

// .storybook/preview.ts
import { withBackground } from '../src/decorators/withBackground';

export const decorators = [withBackground];

Individual stories can then configure the decorator via parameters:

// stories/Button.stories.tsx
export default {
  title: 'Button',
  parameters: {
    background: { color: '#ffeb3b' },
  },
};

export const Primary = () => <Button>Click me</Button>;

Advanced Configuration Options

Conditional Execution with skipIfNoParametersOrOptions

When building utilities that should only activate when explicitly configured, set skipIfNoParametersOrOptions: true. This flag prevents the wrapper from running when neither options nor parameters are present, avoiding unnecessary overhead.

// src/decorators/withLog.ts
import { makeDecorator } from 'storybook/preview-api';

export const withLog = makeDecorator({
  name: 'withLog',
  parameterName: 'log',
  skipIfNoParametersOrOptions: true,
  wrapper: (getStory, context, { options, parameters }) => {
    const prefix = options?.prefix ?? parameters?.prefix ?? '';
    console.log(`${prefix} Rendering story ${context.title}/${context.name}`);
    return getStory(context);
  },
});

Disabling Decorators Per Story

The implementation automatically checks for a disable: true property within the story's parameters under the specified parameterName. If found, the wrapper is bypassed and the original story renders unchanged. To disable the logging decorator for a specific story:

export const QuietStory = {
  parameters: {
    log: { disable: true },
  },
};

Integration and Usage Patterns

makeDecorator is publicly exported from storybook/preview-api via code/core/src/preview-api/index.ts. Import it directly from this package rather than deep-importing from core internals.

Global Registration vs Story-Level Parameters

Decorators can be applied globally in .storybook/preview.ts or individually to stories. Global registration applies the decorator to all stories, while parameters allow per-story customization without modifying the decorator code.

Passing Options Directly

Instead of relying solely on story parameters, you can pass options when registering the decorator:

// .storybook/preview.ts
import { withLog } from '../src/decorators/withLog';

export const decorators = [withLog({ prefix: '[Global] ' })];

When using this pattern, the options object in the wrapper contains these values, while parameters contains story-level overrides.

Summary

  • makeDecorator lives in code/core/src/preview-api/modules/addons/make-decorator.ts and is exported from storybook/preview-api.
  • The wrapper function receives getStory, context, and an object containing options and parameters.
  • Set skipIfNoParametersOrOptions to true to prevent execution when no configuration is provided.
  • Disable decorators per story by setting { parameterName: { disable: true } } in story parameters.
  • The API supports both global registration with options and story-level parameter configuration.

Frequently Asked Questions

What is the difference between options and parameters in makeDecorator?

Options are values passed directly to the decorator function when it is invoked, such as myDecorator({ prefix: '[Log] ' }), while parameters are values defined in the story's metadata under the specified parameterName. Inside the wrapper, options represents the decorator-level defaults and parameters represents story-specific overrides.

How do I disable a custom decorator for a specific story?

To bypass a decorator for an individual story, add a disable: true property to the story's parameters under the decorator's configured parameterName. For example, if parameterName: 'background', set parameters: { background: { disable: true } } in the story object.

Where should I import makeDecorator from in Storybook 7+?

Import makeDecorator from storybook/preview-api. This is the public API surface re-exported from code/core/src/preview-api/index.ts. Avoid importing directly from the core internal file paths to ensure compatibility with future Storybook versions.

Can I use makeDecorator with TypeScript?

Yes, makeDecorator is fully typed. The wrapper function receives properly typed arguments including the story context and a generic options/parameters object. TypeScript will infer types based on your configuration, though you may want to explicitly type the options interface for better IntelliSense when invoking the decorator with options.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →