Skip to content

[Bug]: CSF parser incorrectly detects Zod v4 .meta() method as Storybook's meta() factory function #33654

@zuodongxu

Description

@zuodongxu

Describe the bug

Storybook's CSF parser (csf-tools) incorrectly identifies Zod v4's .meta() method calls as Storybook's CSF Factories meta() function when the object calling .meta() is a directly imported variable.

This causes the error:

BadMetaError: CSF: meta() factory must be imported from .storybook/preview configuration

Note: This bug was originally reported for Storybook 9.x and has been verified to still exist in Storybook 10.3.0-alpha.0 (latest main branch).

Reproduction steps

  1. Create a story file that imports a Zod schema from an external module
  2. Use the imported schema with Zod v4's .meta() method
  3. Run Storybook - the CSF parser will throw BadMetaError

Minimal reproduction code

External module (schemas.ts):

import { z } from 'zod';

export const mySchema = z.union([
  z.number(),
  z.string(),
]);

Story file (Component.stories.tsx):

import { z } from 'zod';
import { mySchema } from './schemas'; // ← Line 2
import type { Meta, StoryObj } from '@storybook/react';

// This works fine - z.string() returns a new object, not a directly imported variable
const workingSchema = z.object({
  name: z.string().meta({ description: 'Name' }),
});

// This FAILS - mySchema is a directly imported variable
const failingSchema = z.object({
  value: mySchema.meta({ description: 'Value' }), // ← Triggers BadMetaError
});

const meta = {
  title: 'Example',
  component: () => <div>Test</div>,
} satisfies Meta;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {};

Expected behavior

The CSF parser should only detect meta() calls that match the CSF Factories pattern (e.g., preview.meta()), not any .meta() method call on imported variables.

Actual behavior

The parser throws BadMetaError pointing to line 2 (the import statement) because:

  1. It detects mySchema.meta(...) as a potential CSF Factory call
  2. It checks if mySchema is imported from .storybook/preview
  3. Since it's not, it throws the error

Root cause analysis

In code/core/src/csf-tools/CsfFile.ts (lines 829-868):

if (
  t.isMemberExpression(callee) &&
  t.isIdentifier(callee.property) &&
  callee.property.name === 'meta' &&  // ← Only checks method name is "meta"
  node.arguments.length > 0
) {
  let rootObject = callee.object;
  if (t.isCallExpression(rootObject) && t.isMemberExpression(rootObject.callee)) {
    rootObject = rootObject.callee.object;
  }

  if (t.isIdentifier(rootObject)) {
    const configCandidate = path.scope.getBinding(rootObject.name);
    const configParent = configCandidate?.path?.parentPath?.node;
    if (t.isImportDeclaration(configParent)) {
      if (isValidPreviewPath(configParent.source.value)) {
        // Valid CSF Factory
      } else {
        throw new BadMetaError(
          'meta() factory must be imported from .storybook/preview configuration',
          configParent,
          self._options.fileName
        );
      }
    }
  }
}

The detection logic is too broad - it matches any importedVar.meta(...) pattern instead of specifically targeting CSF Factories' preview.meta() pattern.

Suggested fix

The parser should additionally verify that the .meta() call is actually intended to be a CSF Factory call. Possible approaches:

  1. Check the context: Only trigger this check when the .meta() call result is used to define the story meta (e.g., assigned to a variable that's later used with .story() or exported as default)

  2. Check naming conventions: CSF Factory imports typically use names like preview, config, or come from paths containing .storybook/preview

  3. Skip non-factory files: Only perform this validation in files that have clear CSF Factory indicators (e.g., meta.story() calls)

  4. Graceful handling: Instead of throwing immediately when the import source doesn't match preview config, skip the validation entirely - let non-CSF-Factory .meta() calls pass through without error

Environment

Software Version
Storybook 10.3.0-alpha.0 (also affects 9.x)
Framework @storybook/react-vite
Builder Vite
Package Manager pnpm
Operating System macOS
Zod 3.25.x (v4 API with .meta())

Additional context

  • Zod v4 introduced the .meta() method for adding metadata to schemas (see Zod v4 docs)
  • This naming collision with Storybook's CSF Factories creates a false positive detection
  • The issue only occurs when calling .meta() on directly imported variables, not on intermediate results like z.string().meta()
  • This is a regression in the sense that any library using a .meta() method on exported objects will trigger this false positive

Reproduction link

https://github.com/xuzuodong/Storybook-CSF-Parser-Bug-Reproduction

Reproduction steps

No response

System

System:
    OS: macOS 26.1
    CPU: (8) arm64 Apple M3
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 22.12.0 - ~/.nvm/versions/node/v22.12.0/bin/node
    Yarn: 1.22.11 - /usr/local/bin/yarn
    npm: 8.5.0 - /usr/local/bin/npm
    pnpm: 10.12.3 - ~/Library/pnpm/pnpm <----- active
  Browsers:
    Chrome: 144.0.7559.97
    Safari: 26.1
  npmPackages:
    @storybook/react: ^9.1.0 => 9.1.17 
    @storybook/react-vite: ^9.1.0 => 9.1.17 
    storybook: ^9.1.0 => 9.1.17

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions