Skip to main content
Codemod CLI and workflow features are free for local use. For large-scale refactoring across many repos, the Codemod app provides:
  • Visual parameter configuration - Edit workflow parameters through the UI instead of YAML
  • Centralized state management - Coordinate migrations with persistent state across repos and teams
  • Multi-repo orchestration - Run workflows across your entire codebase with progress tracking
  • Business insights - Track migration progress with custom metrics and dashboards

This is an advanced topic. If you’re just getting started, we recommend using Codemod Studio or Codemod MCP to create your Codemod package with the help of AI.

Package Structure

Directory Layout

my-codemod-package/
├─ codemod.yaml
├─ workflow.yaml
├─ scripts/
└─ rules/
The folder—called a Codemod package—is the root when you run npx codemod workflow run -w ./my-codemod-package/.
You can combine types in a single package. The scripts/ and rules/ folders are conventional, not required—use any paths and reference them from workflow.yaml.
A Codemod package is a directory containing your workflow.yaml and any scripts, rules, or assets referenced by your workflow.
1

Scaffolding packages

npx codemod init
2

Validating workflows

npx codemod workflow validate -w workflow.yaml
3

Running workflows

npx codemod workflow run -w ./my-codemod-package/

Package Metadata (codemod.yaml)

The codemod.yaml file defines your Codemod package’s metadata and configuration. Example codemod.yaml file:
codemod.yaml
schema_version: "1.0"

name: "my-awesome-codemod"
version: "0.1.0"
description: "Transform legacy patterns to modern syntax"
author: "Your Name <you@example.com>"
license: "MIT"
workflow: "workflow.yaml"
category: "migration"

targets:
  languages: ["typescript"]

keywords: ["upgrade", "breaking-change", "react", "v17-to-v18"]

registry:
  access: "public"
  visibility: "public"
schema_version
string
default:"1.0"
required
Codemod workflow schema version.
name
string
required
Codemod package name (unique within scope).Naming rules: /^[a-z0-9-_/]+$/ (lowercase letters, numbers, hyphens, underscores, and / for scope separation only)Not allowed: uppercase letters, dots, commas, spaces, or other special charactersValid examples:
  • remove-console-logs
  • @scope/remove-console-logs (using @organization-or-project-scope/name provides better discoverability in the Codemod Registry)
version
string
default:"0.1.0"
required
Semantic version of the package.
description
string
required
Brief description of what the codemod does.
author
string
required
Author name and email, e.g., Jane Doe <jane@example.com>.
license
string
default:"MIT"
required
License identifier (SPDX), e.g., MIT.
workflow
string
default:"workflow.yaml"
required
Relative path to your workflow file.
category
string
default:"migration"
Category for the codemod.
targets.languages
string[]
Languages targeted by this codemod (selected language during codemod init; editable later).
keywords
string[]
Keyword tags are an optional feature helping developers quickly identify the purpose, scope, and relevance of a codemod. They also enable better search, filtering, and reporting when managing many codemods across frameworks and projects.Example: keywords: ["react", "v18-to-v19", "migration"]
  • Keep tags concise (1–2 words).
  • Use lowercase for consistency.
  • Don’t overload with tags — 2–4 per codemod is ideal.
  • Prioritize transformation type + framework/library + version (if relevant).

1. Transformation Type TagsConsider these categories when describing why the codemod exists:
  • upgrade – helps upgrade code to newer versions (encompasses both breaking changes and feature adoption). You may also consider adding one of the following tags:
    • breaking-change – adapts code to framework/library breaking API changes.
    • feature-adoption – helps adoption of new optional or incremental features.
  • security – addresses known vulnerabilities or unsafe patterns.
  • cross-migration – replaces one library/framework with another.
  • i18n – internationalization migrations or improvements.
  • a11y – accessibility improvements and compliance.
  • standardization – unifies patterns, conventions, or APIs across a codebase.
  • code-mining – identifies, flags, or extracts patterns without transforming. Use if codemod is for detection-only.
Rule of thumb: Pick one primary type tag per codemod. Avoid mixing breaking-change and feature-adoption in the same codemod.
2. Target Version TagsUse these to indicate the framework/library version the codemod prepares for.
  • Format: vX or vX-to-vY (for upgrades).
  • Examples:
    • v17-to-v18 (React 17 → 18)
    • v5-to-v6 (React Router 5 → 6)
    • v16 (Angular 16 breaking changes)
Rule of thumb: If the codemod is version-specific, always tag it.
3. Framework / Library / SDK TagsAlways add the ecosystem name to improve discoverability.
  • Examples:
    • react
    • nextjs
    • nodejs
    • angular
    • msw
    • i18next
Rule of thumb: Use the official, common name of the framework/library.
4. Example Tag SetsHere are some examples to illustrate how tags combine:
  • React Root API Upgrade (17 → 18)
    • Tags: upgrade, breaking-change, v17-to-v18, react
  • Adopt React Hooks
    • Tags: upgrade, feature-adoption, react
  • Migrate from Moment.js to Day.js
    • Tags: cross-migration, momentjs, dayjs
  • Remove Hardcoded Strings for i18n
    • Tags: i18n, i18next
  • Add ARIA labels for accessibility
    • Tags: a11y, react
  • Detect Insecure crypto API usage
    • Tags: security, nodejs, crypto
registry.access
string
default:"public"
Access controls who can run/use the codemod (once they can see it).
  • public: Anyone can run the codemod.
  • private: Only the owner can run the codemod.
  • pro: Only Pro plan users can run the codemod.
Access applies on top of visibility. For example, visibility: public with access: pro shows the package publicly, but only Pro users can run it.
registry.visibility
string
default:"public"
Visibility controls who can see the package in the Registry (search, listings, and UI).
  • public: Everyone can see the package in the Registry.
  • private: Only the owner can see the package.
  • org: Members of the package’s organization scope can see the package.
  • user: Visible only to the publishing user (user-scoped visibility).
During scaffolding, the CLI sets public/private based on —private. You can change to any supported value above when publishing.

Workflow File (workflow.yaml)

The workflow.yaml file defines your Codemod package’s workflow. A workflow is a collection of nodes that are executed in order. A workflow has four top-level keys:
workflow.yaml
version: "1"
state:
  schema: []
templates: []
nodes: []
KeyRequiredPurpose
versionDeclare workflow schema version (default: "1").
nodesExecutable DAG.
stateDeclares shared-state schema.
templatesRe-usable blocks.

Nodes & Steps

Nodes

Nodes are execution units in your workflow. They can be automatic or manual, depend on other nodes, and may define strategy (e.g., matrix), trigger, runtime, env, and an ordered list of steps.
version: "1"
nodes:
  - id: build
    name: Build
    type: automatic
    steps:
      - name: npm install
        run: npm ci
id
string
required
Unique within the workflow.
name
string
required
Display name.
type
string
automatic (default) or manual.
depends_on
string[]
Upstream node IDs. Learn more about cyclic dependencies here.
trigger
object
{ type: manual } → approval gate. Learn more about manual triggers here.
strategy
object
Matrix configuration. Learn more about matrix strategies here.
steps
array
required
Ordered list of steps.
env
object
Environment variables for the node or step.

Steps

Steps are the atomic actions inside a node. They run sequentially and each step performs one transformation or action using a specific engine:
  • js-ast-grep - Primary transformation engine: JavaScript/TypeScript codemods using ast-grep
  • ast-grep - Declarative pattern matching with YAML rules
  • run - Shell commands for external tools and setup
  • ai - LLM-powered transformations and reviews
  • codemod - Compose other published codemods

jssg (JS ast-grep) step

Runs JavaScript/TypeScript codemods with full programmatic control over AST transformations. Use jssg for:
  • Complex transformations requiring logic (loops, conditions, functions)
  • AST manipulations beyond find/replace
  • Cross-file analysis or coordination
  • Type-safe transformations with TypeScript
New to jssg? Start with the jssg quickstart to write your first transform, then see the jssg API reference for the complete API.
To use a jssg step:
1

Create a jssg codemod:

Under scripts/ folder, create a new jssg codemod.
scripts/codemod.ts
export default function transform(root: SgRoot<TSX>, options) {
  // Your transform logic here
}
Learn more about how to write jssg codemods: jssg documentation.
2

Add a jssg step to your workflow:

workflow.yaml
steps:
  - name: Run jssg codemod
    js-ast-grep:
      js_file: "scripts/codemod.ts"
      base_path: "."
      language: "typescript"
      include:
        - "**/*.ts"
        - "**/*.tsx"
name
string
required
Step label.
if
string
Conditional expression to gate step execution. Supports params.x, state.x, and matrix value keys. Operators: ==, !=, >, <, &&, ||.
- name: Transform only for specific shard
  if: params.shardingMethod == "directory"
  js-ast-grep:
    js_file: "src/codemod.ts"
    language: "tsx"
See Variable resolution for full syntax.
js-ast-grep.js_file
string
required
Path to the JS/TS file that implements the codemod.
js-ast-grep.language
string
Target language (e.g., typescript, javascript, etc.).
js-ast-grep.include
string[]
Include glob patterns.
js-ast-grep.exclude
string[]
Exclude glob patterns.
js-ast-grep.base_path
string
Base path for resolving globs.
js-ast-grep.max_threads
number
Maximum concurrent threads.
js-ast-grep.dry_run
boolean
default:"false"
Perform a dry run without applying changes.

AI step

Calls an AI agent with a prompt. This is helpful when your workflow requires leveraging LLM intelligence for more complex tasks for capabilities beyond deterministic transformations. To use a AI step:
1

Set the environment variables:

You can either set the environment variables in your shell or in your environment file.
.env
LLM_API_KEY=YOUR_KEY
LLM_PROVIDER=openai # or anthropic, azure_openai
LLM_MODEL=gpt-4o # or the model you want to use
LLM_BASE_URL=https://api.openai.com/v1 # or the base URL of the provider you want to use
2

Add an AI step to your workflow:

workflow.yaml
steps:
  - name: Review diffs with AI
    ai:
      prompt: |
        Summarize risky changes and suggest tests.
      model: "gpt-4o"
name
string
required
Step label.
if
string
Conditional expression to gate step execution. Supports params.x, state.x, and matrix value keys. Operators: ==, !=, >, <, &&, ||.
- name: AI review only if enabled
  if: params.autoAIReview
  ai:
    prompt: "Review the changes"
See Variable resolution for full syntax.
ai.prompt
string
required
Prompt to send to the AI agent.
ai.model
string
default:"gpt-4o"
Model identifier. Overrides LLM_MODEL if set.
ai.system_prompt
string
System prompt to scope the AI agent’s behavior.
ai.max_steps
number
default:"30"
Maximum number of agent steps before stopping.
ai.llm_protocol
string
default:"openai"
LLM provider/protocol. Supported: openai, anthropic, azure_openai.
ai.endpoint
string
LLM base URL. If omitted, defaults to the provider’s standard endpoint for the selected protocol. You can override with a custom endpoint.
ai.api_key
string
API key to access the LLM.The AI step requires valid LLM credentials. Provide them via environment variables:
LLM_API_KEY=YOUR_KEY
LLM_PROVIDER=openai
LLM_MODEL=gpt-4o
LLM_BASE_URL=https://api.openai.com/v1
LLM_BASE_URL is optional. If omitted, defaults to the provider’s standard endpoint for the selected protocol. You can override with a custom endpoint.

YAML ast-grep step

Executes ast-grep using declarative YAML rules. Use for simple, fast pattern matching when you don’t need programmatic logic. When to use YAML ast-grep:
  • Simple find/replace transformations
  • No conditional logic needed
  • Fastest to write for basic patterns
When to use jssg instead:
  • Need conditional logic or loops
  • Complex AST manipulations
  • Cross-file coordination
To use a YAML ast-grep step:
1

Create a YAML ast-grep rules file:

Under rules/ folder, create a new YAML ast-grep rules file.
rules/config.yml
id: replace-console-log
language: typescript
rule:
  any:
    - pattern: console.log($ARG)
    - pattern: console.debug($ARG)
fix: logger.log($ARG)
Learn more about how to write YAML ast-grep rules: ast-grep YAML reference
2

Add a YAML ast-grep step to your workflow:

workflow.yaml
steps:
  - name: "Scan {language} files and apply fixes"
    ast-grep:
      config_file: "rules/config.yml"
      base_path: "./src"
      include:
        - "**/*.js"
      exclude:
        - "**/*.test.js"
name
string
required
Step label.
if
string
Conditional expression to gate step execution. Supports params.x, state.x, and matrix value keys. Operators: ==, !=, >, <, &&, ||.
- name: Apply rules only for TypeScript
  if: params.language == "typescript"
  ast-grep:
    config_file: "rules/typescript.yml"
See Variable resolution for full syntax.
ast-grep.config_file
string
required
Path to the ast-grep configuration file (.yaml).
ast-grep.include
string[]
Include glob patterns.
ast-grep.exclude
string[]
Exclude glob patterns.
ast-grep.base_path
string
Base path for resolving globs.
ast-grep.max_threads
number
Maximum concurrent threads.

Codemod Registry step

Runs another codemod by package name (or local path). Use to compose larger migrations by chaining codemods. To use a Codemod Registry step:
1

Find or publish a codemod to the Codemod Registry:

You can:
2

Add a Codemod Registry step to your workflow:

workflow.yaml
steps:
  - name: Run registry codemod
    codemod:
      source: "@scope/package"
      args: ["--flag", "value"]
      env:
        NODE_ENV: production
        working_dir: "."
name
string
required
Step label.
if
string
Conditional expression to gate step execution. Supports params.x, state.x, and matrix value keys. Operators: ==, !=, >, <, &&, ||.
- name: Run only for React 18
  if: params.reactVersion == "18"
  codemod:
    source: "@codemod/react-18-19"
See Variable resolution for full syntax.
codemod.source
string
required
Codemod source (registry package or local path).
Version pinning: specify an exact version in source (e.g., @scope/pkg@1.2.3) for reproducible runs. If you omit the version, the latest published version is used.
codemod.args
string[]
CLI arguments passed to the codemod.
codemod.env
object
Environment variables used during execution.
codemod.working_dir
string
Working directory for execution.
env
object
Step-level environment variables. For codemod steps, these are forwarded to the invoked workflow as parameters with an env_ prefix (e.g., FOOenv_FOO). They are not applied to the OS environment of the nested workflow.
Aggregate multiple codemods:
You can aggregate multiple codemods by adding more than one codemod step in the same node. A Codemod package may itself orchestrate other codemods, enabling nested compositions for larger upgrades.
workflow.yaml
  steps:
    - name: Convert `createRequireFromPath` to `createRequire`
      codemod:
        source: "@nodejs/create-require-from-path"
    - name: Handle DEP0147 (`fs.rmdir` to `fs.rm`)
      codemod:
        source: "@nodejs/rmdir"
  • Steps in a node run sequentially from top to bottom.
  • Use depends_on or a matrix strategy across multiple nodes for gating/parallelism.
  • If a step fails, the node fails and subsequent steps in that node are skipped.

Shell command step

Runs shell commands on the host. Use for setup/cleanup, invoking external tools, or glue logic between transformations.
workflow.yaml
steps:
  - name: Install dependencies
    run: |
      npm install
      npm run build
name
string
required
Step label.
if
string
Conditional expression to gate step execution. Supports params.x, state.x, and matrix value keys. Operators: ==, !=, >, <, &&, ||.
- name: Run only in production
  if: params.environment == "production"
  run: npm run deploy
See Variable resolution for full syntax.
run
string
required
Inline shell command to execute.
env
object
Step-level environment variables applied to the process environment for this run step.

Shared State

State enables workflows to persist data across runs and coordinate work across repositories and teams:
  • Sharding: Distribute work across teams or repositories
  • Progress tracking: Resume interrupted migrations without losing work
  • Coordination: Maintain consistency across related codebases
The Codemod app provides centralized state management for large-scale refactoring across many repos. Pro codemods like i18n codemod use state to coordinate multi-repo migrations.
workflow.yaml
version: "1"
state:
  schema:
    - name: shards
      type: array
      items:
        type: object
        properties:
          team:    { type: string }
          shardId: { type: string }
nodes: []
You can also declare state schema using an object map:
workflow.yaml
version: "1"
state:
  schema:
    shards:
      type: array
      items:
        type: object
        properties:
          team:    { type: string }
          shardId: { type: string }
nodes: []

Parameters

Parameters make workflows configurable and reusable across different projects and teams. Define them in your workflow with a schema:
workflow.yaml
version: "1"
params:
  schema:
    library:
      name: "Library"
      description: "Internationalization library to use"
      type: string
      default: "next-intl"
    shardingMethod:
      name: "Sharding Method"
      type: string
      default: "directory"
    prSize:
      name: "PR Size"
      type: string
      default: "50"
nodes:
  - id: transform
    steps:
      - name: Run transform
        js-ast-grep:
          js_file: ./src/codemod.ts
          language: tsx

Accessing Parameters in Steps

Parameters are exposed differently depending on the step type: In jssg transforms - access via options.params:
codemod.ts
export default function transform(root: SgRoot<TSX>, options) {
  // Access parameters
  const library = options.params?.library || "next-intl";
  const catalogMethod = options.params?.catalogFileSelectionMethod;
  
  // Use parameters to drive behavior
  if (library === "react-i18next") {
    // Apply react-i18next specific logic
  }
}
For complete jssg parameter patterns and examples, see jssg Advanced: Accessing Parameters.
In shell steps - parameters become environment variables with PARAM_ prefix:
workflow.yaml
- name: Run script
  run: |
    echo "Library: $PARAM_LIBRARY"
In codemod steps - forwarded with env_ prefix:
workflow.yaml
- name: Run nested codemod
  codemod:
    source: "@org/package"
  env:
    LIBRARY: ${{ params.library }}
# The nested workflow receives env_LIBRARY
Common parameter use cases:
  • Sharding configuration: Method (directory, codeowner) and targets
  • PR settings: Size limits, auto AI review, pre/post run scripts
Enterprise Features: In the Codemod app, you can configure parameters visually through the UI. See Campaigns for running parameterized workflows at scale.

Matrix Strategy

workflow.yaml
version: "1"
state:
  schema:
    - name: shards
      type: array
      items:
        type: object
        properties:
          team: { type: string }
          shardId: { type: string }
nodes:
  - id: matrix-codemod
    name: Matrix Codemod
    strategy:
      type: matrix
      from_state: shards
    steps:
      - name: Codemod
        run: node codemod.js --team=$team --shard=$shardId
When the array referenced by from_state changes, Codemod CLI:
  1. Creates new tasks for new items.
  2. Marks tasks as WontDo if their item is removed.
  3. Leaves existing tasks untouched if their item remains.
Matrix nodes have a master task that tracks the status of all generated tasks.

Accessing Matrix Values in Steps

Matrix values are exposed differently per step type: In jssg transforms - access via options.matrixValues:
scripts/codemod.ts
const transform: Transform<TSX> = async (root, options) => {
  const team = options.matrixValues?.team;
  const shardId = options.matrixValues?.shardId;
  
  // Use for sharding logic
  if (team && !isOwnedByTeam(root.filename(), team)) {
    return null; // Skip files not owned by this team
  }
};
For complete jssg sharding patterns and examples, see jssg Advanced: Accessing Matrix Values.
In shell steps - matrix values become environment variables:
workflow.yaml
- name: Process shard
  run: |
    echo "Team: $team"
    echo "Shard: $shardId"

Manual Trigger

workflow.yaml
version: "1"
nodes:
  - id: manual-approval
    name: Manual Approval
    trigger:
      type: manual
    steps:
      - name: Wait for approval
        run: echo "Waiting for manual approval"
Manual tasks are assigned unique UUIDs. You can resume:
  • All paused tasks:
    npx codemod workflow resume -i <run-id> --trigger-all
    
  • A specific task:
    npx codemod workflow resume -i <run-id> -t <task-uuid>
    

State Updates

SyntaxMeaningExample
KEY=VALSet state key to valuecount=10
KEY@=VALAppend value to array at state keyshards@={"team":"core","shardId":"1"}
Dot notationSet nested state fieldsconfig.retries=5
JSON valuesUse valid JSON for objects/arraysuser={"name":"Alice","id":123}
All state updates must be valid JSON if not a primitive. Updates are applied only if the task exits successfully.
Planned feature: containerized execution (e.g., Docker/Podman). Currently, workflows run in the host shell. When available, you’ll be able to specify a runtime per node or template.
Workflow state is persisted after every task. If interrupted, you can resume from the last saved state—no work is lost.
For matrix nodes, a master task aggregates the status of all generated tasks.
If all child tasks complete, the master is Completed. If any fail, the master is Failed.
If your workflow has a cycle:
workflow.yaml
nodes:
  - id: a
    name: Task A
    depends_on: [b]
    steps:
      - name: Task A
        run: echo "Task A"
  - id: b
    name: Task B
    depends_on: [a]
    steps:
      - name: Task B
        run: echo "Task B"
You’ll see:
console
 Workflow definition is invalid
Error: Cyclic dependency detected: a b a
This error is shown when you run npx codemod workflow validate or npx codemod workflow run on a workflow with a cyclic dependency.

Task Statuses

Pending
Queued; waiting for runner.
Running
Currently executing.
Completed
Succeeded; diff applied.
Failed
Script exited non-zero; diff discarded.
AwaitingTrigger
Waiting for manual approval.
Blocked
Dependencies not finished.
WontDo
Matrix item removed; task skipped.

Variable Resolution

  • Parameter: ${{params.branch}} — Supplied at runtime
  • Environment: ${{env.CI}} — Host env var
  • Shared State: ${{state.counter}} — Live JSON value
  • In matrix tasks, each object key becomes an environment variable (e.g., $team, $shardId, …). Inside steps, variables are unprefixed.
  • Operators (==, !=, >, <, &&, ||) are supported inside string interpolations like ${{ ... }} for resolving parameters, state, and matrix values.

Roadmap

Container runtime support

Support for runtime: docker and other container runtimes, allowing tasks to run in isolated environments.

Nested matrix strategies

Support for matrix strategies within matrix strategies, enabling more complex task fan-out.

Next Steps