How to Build a Storybook Addon
Choose between a UI addon and a preset, build and test it locally, then package and publish it for Storybook users.
To build a Storybook addon, first choose its shape: make a UI addon if users need a panel, toolbar, or tab in Storybook; make a preset if the addon configures a framework, build tool, or other integration. Start from Storybook’s Addon Kit, implement code in the runtime where it belongs, install the package in a host Storybook to exercise it, and publish it to npm. Exact commands and APIs depend on the Storybook version: the detailed starter guide cited here is for Storybook 8, while the install guide is for Storybook 9.
1. Choose the right addon architecture
Storybook has two main runtime environments. The Manager renders Storybook’s navigation and addon interface. The Preview is an iframe where stories render. They communicate over a channel. Presets run in Node and contribute configuration, such as build-tool or framework integration. Pick the environment based on what the feature needs to do; importing code across these boundaries without checking its runtime can break the addon. See the Storybook addon overview.
| Need | Likely shape | Runs in |
|---|---|---|
| A user-facing panel, toolbar, or tab | UI addon | Manager, sometimes with Preview communication |
| Story-level behavior or access to story context | UI addon or preview annotation, depending on the feature | Preview, Manager, or both |
| Framework, compiler, bundler, or other tool integration | Preset | Node configuration |
| Integration requiring both configuration and controls | Preset plus manager or preview entries | Node and the relevant browser runtime |
Prefer a preset when the integration should work through configuration and does not need its own visible controls. Prefer a UI addon when users need to inspect or change something while browsing stories. A package can combine these pieces when the feature actually needs them.
2. Start from the Addon Kit
- Decide the Storybook major version, framework, and runtime you intend to support.
- Use the Storybook Addon Kit guide to create a starter repository and follow its setup prompts. That guide’s detailed commands and build setup target Storybook 8; check the documentation for your target release before copying them.
- Install dependencies and run the kit’s development command. The Storybook 8 guide describes
npm run startfor watch-mode development. - Inspect the generated package scripts and entry points before adding code. The kit uses TypeScript and
tsupin the documented setup, but output configuration may differ across runtimes and releases.
The kit is a starting point, not a compatibility guarantee. Confirm that its dependencies, exports, and peer requirements match your supported Storybook versions before publishing.
3. Build a minimal panel
For a visible extension, give the addon and its UI element stable unique IDs, register the addon, then add the panel with a type, title, and render function. The following is a small TypeScript-shaped example of the registration pattern documented by Storybook. Treat imports and internal component paths as version-specific: check the Addon API reference for your target release and the exact files in the kit.
import React from 'react';
import { addons, types } from 'storybook/manager-api';
import { AddonPanel } from 'storybook/internal/components';
const ADDON_ID = 'acme/story-inspector';
const PANEL_ID = `${ADDON_ID}/panel`;
function InspectorPanel() {
return <div style={{ padding: 12 }}>Story Inspector</div>;
}
addons.register(ADDON_ID, () => {
addons.add(PANEL_ID, {
type: types.PANEL,
title: 'Inspector',
render: ({ active }) =>
active ? <AddonPanel><InspectorPanel /></AddonPanel> : null,
});
});
This example shows the shape of registration, not a release-independent copy-and-paste package. The panel component import and available manager APIs can change. Start with the kit’s manager entry and adapt its established pattern rather than adding a guessed import path. Keep React and Storybook APIs in the browser-side manager bundle, not in a Node preset.
Make the panel configurable
Use story parameters when users need to disable an addon feature for a particular story. Storybook’s knowledge base documents panel configuration through a paramKey and a story parameter; follow the corresponding API for your release. Keep defaults useful, make the opt-out discoverable, and avoid adding parameters that do not represent a real user choice.
// Illustrative story-level configuration; use the parameter key
// and panel configuration supported by your target Storybook release.
export default {
parameters: {
inspector: { disable: true },
},
};
4. Use channels for Manager and Preview communication
A panel can read manager-side state without involving the Preview. When the addon needs story-side data or must ask preview code to do work, use Storybook’s channel rather than importing Preview code into the Manager. The channel has an event-emitter-compatible API. Define intentional event names and payloads, handle the case where a response has not arrived yet, and clean up listeners when UI components unmount. See the API documentation on channels.
// Pattern only: use the channel access and lifecycle APIs for your version.
const channel = addons.getChannel();
channel.on('acme/inspector:result', handleResult);
// When the owning UI component is removed:
channel.off('acme/inspector:result', handleResult);
Avoid sending large objects or firing repeated events on every render. Send only the data the other runtime needs, and make the request/response flow explicit so users can understand loading and error states.
5. Build a preset for configuration work
Presets are collections of configuration hooks and run in Node. They are appropriate for integrations that need to modify configuration for a tool such as Webpack or Babel, or compose other addon entries and preview annotations. Storybook’s configuration guide explains the preset role; the addon knowledge base covers composition examples.
Keep preset code separate from manager and preview code. Export the hooks and configuration in the format expected by the target Storybook version, and test the resulting configuration in an actual host project. Avoid treating a browser-only dependency as safe to import in Node.
6. Develop and test in a host Storybook
- Run the kit’s watch script so changes rebuild while you work.
- Install or link the local package into a host Storybook project using the workflow documented for the kit and your package manager.
- For an existing Storybook installation, test the addon entry in
.storybook/main.jsor.storybook/main.tsas appropriate for the project. - Exercise the addon with multiple stories, including stories with parameters that disable it, stories that load slowly, and stories with missing or unusual data.
- Check browser console output and Storybook’s terminal output. Verify that the panel activates and deactivates cleanly, and that channel listeners are removed when no longer needed.
- Test each supported Storybook major version and framework combination. A successful build of the addon package does not prove that the host can load every entry point.
Storybook’s local development guidance distinguishes standalone addon development from developing an addon in an existing installation; it notes that a local addon built on an existing installation has HMR available out of the box. See the knowledge base.
7. Install the addon as a user would
For supported addons, Storybook 9 documents the CLI flow:
npx storybook@latest add your-addon
The alternative is to install the package and add its name to the host project’s Storybook configuration. Preset addons can require separate steps and may not use the CLI flow. Follow the installation instructions you publish and verify them in a clean project. The Storybook 9 installation guide describes both routes.
8. Package, publish, and submit to the catalog
Before publishing, make the package installable and explain its compatibility and setup clearly. Storybook distributes addons through npm. Its integration catalog guidance calls for package module information and addon metadata, a README with installation and configuration instructions, a /dist directory, and a root preset.js written as an ES5 module for catalog integration. It also specifies that the first keyword should be storybook-addon, followed by the addon category; additional keywords can help with search. Check the live catalog checklist before submitting because requirements can change.
- Set a clear package name, version, description, license, and repository metadata.
- Include the built files and the entry points consumers and Storybook need.
- Document supported Storybook versions, frameworks, configuration, and known limits.
- Test installation from the packed or published package, not only from the source checkout.
- Use the catalog’s current metadata and checklist when preparing a submission.
The Storybook 8 addon-writing guide covers publishing and its starter setup. Its notes about bundling or listing Storybook-provided packages are tied to that guide’s environment; verify current package guidance for your release rather than assuming dependency rules are universal.
Or skip the browser setup
If you need screenshots of your addon’s documentation, demos, or Storybook pages, ScreenshotNeo can return an image or PDF from one GET request. Its screenshot API can also help capture pages without setting up a browser automation stack. See the ScreenshotNeo website and API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers identify the page verdict and billing status.
- An MCP server offers
take_screenshot,get_page_info, andcapture_pdftools for AI agents. - The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
Performance, reliability, and cost considerations
Addon performance
- Keep manager bundles focused; load expensive work only when the panel is active where your architecture permits.
- Debounce high-frequency updates and avoid sending large payloads across the channel.
- Clean up listeners and timers. Duplicate subscriptions can cause repeated work after navigation or hot reload.
- For presets, do only the configuration work required during startup. Avoid unnecessary filesystem or network work in configuration hooks.
Reliability and compatibility
- Check version-specific documentation for API imports, package exports, and configuration shapes. The sources here span Storybook 8 and 9.
- Test the addon in the actual framework and host configuration you claim to support.
- Handle missing story data, disabled panels, delayed channel responses, and host projects without optional configuration.
- Keep Node preset dependencies out of browser entries and browser-only dependencies out of Node code.
Cost
Storybook addon development and npm distribution are software workflows; the cited documentation does not establish a required paid tool or cost. If you use ScreenshotNeo for page captures, its listed plans are Free: 1,000 shots/month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Addon does not appear | The package is not installed or registered in the host, or the entry point is not loaded. | Check the host’s Storybook configuration, package name, build output, and terminal errors. Follow the install path for the target release. |
| Import cannot be resolved | An API or internal component path differs by Storybook version, or the expected kit output is missing. | Use the version-matched API reference and kit entry points; rebuild and inspect the package exports. |
| Panel renders blank | The render callback returns nothing while inactive, throws, or expects unavailable story data. | Check active state, browser console errors, and fallback behavior for missing data. |
| Panel appears more than once or events duplicate | Registration or channel listeners are being installed repeatedly, often during hot reload. | Use stable IDs, ensure registration follows the kit pattern, and remove listeners during component cleanup. |
| Preset fails during startup | Node configuration imports browser-only code, the hook shape is wrong, or the preset output is missing. | Separate runtime entry points, compare hooks to the target-version docs, and verify the built preset is present. |
| CLI installation fails | The addon is unsupported by the CLI flow or is a preset with separate installation requirements. | Use the documented manual package and configuration steps for that addon. |
| Catalog submission is rejected | Package metadata, README instructions, distribution files, preset format, or keywords do not meet current criteria. | Review the live integration catalog checklist and correct the package before resubmitting. |
FAQ
Do I need both a preset and a panel?
No. Add both only when the integration needs Node configuration as well as a user-facing Storybook interface or preview behavior.
Can I target multiple Storybook versions?
Only claim versions you have checked. API and packaging details vary, so document the tested range and verify the current docs for each supported major version.
Does every addon need to be in the integration catalog?
No. npm distribution and catalog listing are separate steps; catalog inclusion is useful when you want the addon discoverable in Storybook’s catalog.


