Extract and expose your Storybook component metadata via REST API
A generic, framework-agnostic solution to extract complete metadata from any Storybook project and expose it through a comprehensive REST API.
# Install via npm
npm install storybook-api --save-dev
# Or with yarn
yarn add storybook-api --dev
# Or with pnpm
pnpm add storybook-api --save-dev
# Run setup (automatic)
npx storybook-api-setup
# Or if installed locally
node node_modules/storybook-api/setup.js
The setup script automatically:
.storybook/.storybook/main.js with middlewarepackage.json.storybook/package.json if needed (for ESM projects)Terminal 1 - Start Storybook:
npm run storybook
# โ
Storybook starts with REST API automatically available!
Terminal 2 - Generate metadata:
npm run metadata:dev
# โ
Extracts metadata in ~5 seconds
Test the API:
curl http://localhost:6006/api/health
curl http://localhost:6006/api/components
That's it! ๐
# Terminal 1: Start Storybook
npm run storybook
# Terminal 2: Extract metadata
npm run metadata:dev
Storybook API:
storybook-static/stories.json in ~5 secondsnpm run build-storybook
Metadata is automatically generated during build at storybook-static/stories.json
# Specify port manually
node .storybook/extract-metadata.js --dev --port=6007
# Or use environment variable
STORYBOOK_PORT=6007 npm run metadata:dev
Once installed, your Storybook automatically exposes 7 REST API endpoints!
http://localhost:6006/api
| Endpoint | Method | Description |
|---|---|---|
/api/health |
GET | Health check and API status |
/api/stories |
GET | All stories with optional filtering |
/api/components |
GET | List all unique components |
/api/components/:id |
GET | Get specific component details |
/api/components/:id/docs |
GET | Get component documentation |
/api/components/:id/examples |
GET | Get code examples and usage |
/api/search?q=query |
GET | Search stories and components |
GET /api/health
Response:
{
"status": "healthy",
"timestamp": "2025-12-15T10:00:00.000Z",
"message": "Storybook metadata API is running",
"metadata": {
"totalStories": 405,
"generatedAt": "2025-12-15T09:59:00.000Z",
"storybookVersion": "8.0.0",
"extractedFrom": "source-files"
}
}
GET /api/stories
GET /api/stories?title=button
GET /api/stories?tag=autodocs
GET /api/stories?kind=components
Query Parameters:
title - Filter by title (partial match, case-insensitive)tag - Filter by tag (exact match)kind - Filter by kind (partial match)Response:
{
"total": 10,
"filtered": true,
"stories": [
{
"id": "components-button--default",
"title": "Components/Button",
"name": "Default",
"args": { "variant": "primary", "size": "medium" },
"argTypes": { "variant": { "control": { "type": "select" } } },
"tags": ["autodocs"],
"importPath": "./src/Button.stories.tsx"
}
],
"metadata": {
"generatedAt": "2025-12-15T09:59:00.000Z",
"storybookVersion": "8.0.0"
}
}
GET /api/components
Response:
{
"total": 5,
"components": [
{
"id": "components-button",
"name": "Components/Button",
"title": "Components/Button",
"storyCount": 3,
"tags": ["autodocs", "test"],
"importPath": "./src/Button.stories.tsx",
"stories": [
{ "id": "components-button--default", "name": "Default" },
{ "id": "components-button--primary", "name": "Primary" }
]
}
]
}
GET /api/components/:id
Example:
curl http://localhost:6006/api/components/components-button
Response:
{
"id": "components-button",
"name": "Components/Button",
"title": "Components/Button",
"storyCount": 3,
"importPath": "./src/Button.stories.tsx",
"tags": ["autodocs"],
"args": { "variant": "primary", "size": "medium" },
"argTypes": {
"variant": {
"control": { "type": "select" },
"options": ["primary", "secondary", "tertiary"]
}
},
"stories": [
{
"id": "components-button--default",
"name": "Default",
"args": { "variant": "primary" }
}
]
}
GET /api/components/:id/docs
Response:
{
"component": "Components/Button",
"description": "Button component for user interactions",
"mdx": "...",
"sourceCode": "export const Button = ({ variant, ...props }) => ...",
"argTypes": { ... },
"stories": [
{
"name": "Default",
"description": "Default button state",
"args": { "variant": "primary" }
}
]
}
GET /api/components/:id/examples
Response:
{
"component": "Components/Button",
"importPath": "./src/Button.stories.tsx",
"examples": [
{
"name": "Primary",
"description": "Primary example",
"code": "export const Primary = Template.bind({});",
"args": { "variant": "primary" },
"usage": "<Button variant=\"primary\" size=\"medium\" />"
}
]
}
GET /api/search?q=button
Response:
{
"query": "button",
"total": 5,
"results": [
{
"id": "components-button--default",
"title": "Components/Button",
"name": "Default"
}
]
}
For backward compatibility with v1.3.0:
GET /stories.json # Full metadata dump
GET /stories.json/stats # Statistics only
GET /stories.json/refresh # Refresh instructions
// Get all components
const response = await fetch('http://localhost:6006/api/components');
const { components } = await response.json();
console.log(`Found ${components.length} components`);
components.forEach(comp => {
console.log(`- ${comp.name}: ${comp.storyCount} stories`);
});
// Get specific component
const button = await fetch('http://localhost:6006/api/components/components-button');
const buttonData = await button.json();
console.log(buttonData.argTypes);
// Search
const results = await fetch('http://localhost:6006/api/search?q=input');
const { results: stories } = await results.json();
import requests
# Get all components
r = requests.get('http://localhost:6006/api/components')
components = r.json()['components']
for comp in components:
print(f"{comp['name']}: {comp['storyCount']} stories")
# Get component docs
docs_r = requests.get('http://localhost:6006/api/components/components-button/docs')
docs = docs_r.json()
print(f"Description: {docs['description']}")
# Pretty print with jq
curl http://localhost:6006/api/components | jq
# Save to file
curl http://localhost:6006/api/stories > stories.json
# Search and filter
curl "http://localhost:6006/api/stories?title=button&tag=autodocs" | jq
Complete OpenAPI 3.0 specification is included for all API endpoints!
swagger.yaml - Full OpenAPI spec (17KB, human-readable)swagger.json - JSON format (7KB, machine-readable)swagger.yamlnpm install swagger-ui-express yamljs
# Quick server
node -e "
const express = require('express');
const swaggerUi = require('swagger-ui-express');
const YAML = require('yamljs');
const app = express();
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(YAML.load('./swagger.yaml')));
app.listen(3000, () => console.log('http://localhost:3000/api-docs'));
"
Open: http://localhost:3000/api-docs
swagger.jsonnpm install -g redoc-cli
redoc-cli bundle swagger.yaml -o api-docs.html
open api-docs.html
From Swagger Editor/UI, generate SDKs in 40+ languages:
Just click "Generate Client" and choose your language!
Run all tests:
cd storybook-api
./test-api.sh http://localhost:6006
Expected output:
๐งช Testing Storybook Metadata REST API
======================================
Test: Health Check
โ
PASS
Test: Get All Stories
โ
PASS
... (9 tests total)
======================================
๐ Test Results: 9 passed, 0 failed
๐ All tests passed!
# Health check
curl http://localhost:6006/api/health
# List components
curl http://localhost:6006/api/components | jq
# Get component details
curl http://localhost:6006/api/components/components-button | jq
# Search
curl "http://localhost:6006/api/search?q=button" | jq
# Filter stories
curl "http://localhost:6006/api/stories?tag=autodocs" | jq
/api/stories returns array of stories/api/components returns array of components/api/components/:id returns specific component/api/components/:id/docs returns documentation/api/components/:id/examples returns code examples/api/search?q=query returns search resultsFor each story, Storybook API captures:
id - Unique story identifiertitle - Component title (e.g., "Components/Button")name - Story name (e.g., "Primary")kind - Story kind/categoryargs - All argument values (props)argTypes - Control configurations (select, text, boolean, etc.)initialArgs - Default argument valuesactions - Event handlers (onClick, onChange, etc.)parameters - Design links (Figma), layout, backgrounds, viewportdocs - Component descriptions and documentationsource - Full source code of the storytags - Story tags (autodocs, dev, test, etc.)importPath - Source file pathcomponent - Component reference{
"totalStories": 405,
"generatedAt": "2025-12-15T10:00:00.000Z",
"storybookVersion": "8.0.0",
"extractedFrom": "source-files",
"stories": {
"components-button--default": {
"id": "components-button--default",
"title": "Components/Button",
"name": "Default",
"kind": "Components/Button",
"story": "Default",
"importPath": "./src/Button.stories.tsx",
"tags": ["autodocs", "test"],
"args": {
"variant": "contained",
"size": "medium",
"color": "primary",
"disabled": false
},
"argTypes": {
"variant": {
"control": { "type": "select" },
"options": ["contained", "outlined", "text"],
"description": "Button variant"
},
"size": {
"control": { "type": "select" },
"options": ["small", "medium", "large"]
}
},
"actions": {
"onClick": { "name": "onClick", "action": "onClick" }
},
"parameters": {
"design": {
"type": "figma",
"url": "https://www.figma.com/file/..."
},
"fileName": "./src/Button.stories.tsx"
},
"source": "export const Default = Template.bind({});",
"docs": {
"description": "The default button state",
"sourceCode": "..."
}
}
}
}
Cause: Metadata hasn't been generated yet.
Solution:
# Run metadata extraction
npm run metadata:dev
Cause: The previewMiddleware was incorrectly placed inside webpackFinal.
Solution: Move previewMiddleware to the root level of your .storybook/main.js:
// โ WRONG - inside webpackFinal
export default {
webpackFinal: async (config) => {
config.output = {
...config.output,
previewMiddleware: middleware, // โ Don't put it here!
};
return config;
}
};
// โ
CORRECT - at config root level
export default {
stories: [...],
addons: [...],
previewMiddleware: middleware, // โ
Put it here!
webpackFinal: async (config) => {
// ... webpack config ...
return config;
}
};
Cause: Your .storybook folder is using ES modules but Node.js doesn't know to treat .js files as ES modules.
Solution: Create .storybook/package.json:
{
"type": "module"
}
Note: The setup script now creates this automatically!
Storybook API automatically detects which port Storybook is running on by checking common ports (6006, 6007, 6008, 6009, 9009, 9001, 8080).
If auto-detection fails, you can specify the port manually:
# Option 1: Command line flag
node .storybook/extract-metadata.js --dev --port=6007
# Option 2: Environment variable
STORYBOOK_PORT=6007 npm run metadata:dev
# Option 3: Update package.json script
"metadata:dev": "STORYBOOK_PORT=6007 node .storybook/extract-metadata.js --dev"
Cause: Stories don't define args or argTypes.
Solution: Make sure your stories export args:
export default {
title: 'Components/Button',
component: Button,
argTypes: {
variant: {
control: { type: 'select' },
options: ['primary', 'secondary']
}
}
} as Meta;
export const Default = Template.bind({});
Default.args = {
variant: 'primary',
size: 'medium'
};
Cause: Middleware configuration error.
Solution:
.storybook/main.js has previewMiddleware: middleware at root levelmiddleware.js was copied to .storybook/.storybook/package.json exists with {"type": "module"}Edit .storybook/extract-metadata.js:
const CONFIG = {
storybookUrl: 'http://localhost:6006',
outputDir: path.join(__dirname, '../custom-output'),
outputFile: 'metadata.json',
timeout: 30000,
};
The middleware checks these locations in order:
storybook-static/stories.json.storybook-metadata-temp.json.storybook/stories.jsonprocess.cwd()/storybook-static/stories.jsonTo add custom paths, edit .storybook/middleware.js:
const possiblePaths = [
path.join(dirname, '../storybook-static/stories.json'),
path.join(dirname, '../your-custom-path/metadata.json'), // Add here
// ... other paths
];
| Version | Supported | Notes |
|---|---|---|
| 8.x | โ Yes | Fully tested |
| 7.x | โ Yes | Fully tested |
| 6.x | โ ๏ธ Maybe | Not tested, might work |
| Framework | Supported | Notes |
|---|---|---|
| React | โ Yes | .tsx, .jsx |
| Vue | โ Yes | .js, .ts |
| Angular | โ Yes | .ts |
| Svelte | โ Yes | .js, .ts |
| Web Components | โ Yes | .js |
| HTML | โ Yes | .js |
| Ember | โ Yes | .js |
| Preact | โ Yes | .jsx |
| System | Supported | Notes |
|---|---|---|
| ES Modules (ESM) | โ Yes | Preferred |
| CommonJS (CJS) | โ Yes | Supported |
| Mixed | โ Yes | Auto-detected |
| Tool | Supported | Notes |
|---|---|---|
| Webpack | โ Yes | Tested |
| Vite | โ Yes | Tested |
| esbuild | โ Yes | Supported |
| Rollup | โ Yes | Should work |
| Parcel | โ Yes | Should work |
The middleware includes several security features for production use:
By default, CORS is set to * (allow all origins) for easy development. For production, restrict CORS to specific origins:
# Set allowed origins (comma-separated)
export STORYBOOK_CORS_ORIGIN="https://yourdomain.com,https://app.yourdomain.com"
# Or in your .env file
STORYBOOK_CORS_ORIGIN=https://yourdomain.com
All user inputs are validated and sanitized:
The API automatically includes security headers:
X-Content-Type-Options: nosniffAccess-Control-Allow-Origin (configurable)Cache-Control headersMetadata is cached in memory with a configurable TTL (default: 5 seconds):
# Set cache TTL in milliseconds
export STORYBOOK_CACHE_TTL=10000 # 10 seconds
# Disable caching (not recommended)
export STORYBOOK_CACHE_TTL=0
Note: Cache is automatically invalidated when metadata files are updated.
| Variable | Default | Description |
|---|---|---|
STORYBOOK_CORS_ORIGIN |
* |
Allowed CORS origins (comma-separated) |
STORYBOOK_CACHE_TTL |
5000 |
Cache TTL in milliseconds |
STORYBOOK_PORT |
6006 |
Storybook port for metadata extraction |
STORYBOOK_URL |
http://localhost:6006 |
Full Storybook URL |
The API includes basic error logging:
For production monitoring, consider:
/api/health)The current implementation does not include rate limiting. For high-traffic production APIs, consider:
Initial npm release ๐
This is the first public release of storybook-api on npm. The package includes all features developed during internal development.
The following versions were used during internal development:
With this tool and its REST API, you can:
Tested with a large Storybook project:
Comparison:
| Method | Time | Completeness |
|---|---|---|
| Source Parsing | 5 sec | 100% โ |
| Browser Automation | 15-20 min | 70% (timeouts) |
| HTTP Endpoint | < 1 sec | Basic only |
Contributions are welcome! Here's how you can help:
# Clone the repo
git clone https://github.com/Hrishikesh410/storybook-api.git
cd storybook-api
# Install dependencies
npm install
# Test in a Storybook project
cd path/to/test-storybook
npm install storybook-api --save-dev
npx storybook-api-setup
# Make changes and test
npm run storybook
npm run metadata:dev
./test-api.sh http://localhost:6006
MIT License - Feel free to use this in any project!
Copyright (c) 2025
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Made with โค๏ธ for developers who automate documentation
Version: 1.0.0
Status: Production Ready โ
Last Updated: December 26, 2025
If this tool helped you, consider giving it a โญ!