Skip to main content
This guide walks you through setting up Codemod in your agent, building a JSSG codemod, and testing and publishing it to the registry. It assumes you are familiar with codemods. If not, check out this intro to codemods.

1. Set up Codemod in your coding agent

Start by installing Codemod Master Skill and Codemod MCP for your preferred coding agent:
npx codemod ai
This is the main setup path for Codemod’s AI-native CLI. It installs Codemod Master Skill and Codemod MCP so your agent can use Codemod CLI with built-in best practices for building, running, testing, and publishing codemods.

2. Scaffold and build your codemod

Create a dedicated folder or repo for your codemod work, then scaffold a starter package:
npx codemod init
The starter package gives your agent a concrete structure to refine or replace while keeping the workflow, tests, and publishing flow in place. Once the package is scaffolded, you can ask your AI agent to help you build codemods for things like framework upgrades, design system migrations, i18n work, code-mining tasks, and more. For example:

Extract React components from inside other components to module scope to prevent re-creation on render.

Open in Cursor

Prompting best practices

Tips for writing effective prompts and getting the most out of building codemods with AI.
You will end up with a Codemod package:
my-codemod
codemod.yaml
workflow.yaml
scripts
codemod.ts
tests
fixtures
input.tsx
expected.tsx
schema_version: "1.0"

name: "extract-inner-components"
version: "0.1.0"
description: "Move React components defined inside other components to module scope"
author: "Your Name <you@example.com>"
license: "MIT"
workflow: "workflow.yaml"

targets:
  languages: ["tsx"]

keywords: ["react", "performance", "components"]

registry:
  access: "public"
  visibility: "public"

capabilities: []
# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json

version: "1"

params:
  schema:
    run_ai_step:
      name: "Run AI step"
      description: "Enable AI-powered fixups for edge cases"
      type: string
      default: "false"

nodes:
  - id: extract-inner-components
    name: Extract inner React components to module scope
    type: automatic
    steps:
      - name: "Move inner components to module scope"
        js-ast-grep:
          js_file: scripts/codemod.ts
          language: "tsx"

      - name: "AI fixups for complex cases"
        if: params.run_ai_step == "true"
        ai:
          prompt: |
            The previous AST step extracted inner React components to module scope.
            Some cases could not be handled deterministically. Scan for remaining
            components defined inside other components and fix them:

            1. If the inner component closes over parent state or props, convert those
               to explicit props on the extracted component and update the call site.
            2. If the inner component uses generics from the parent, lift the type
               parameters to the extracted component's signature.
            3. Add a TODO comment for any case you cannot safely transform.

            Only modify files that still contain inner component definitions.
import type { Codemod, Edit } from "codemod:ast-grep";
import type { SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";

const codemod: Codemod<TSX> = async (root) => {
  const rootNode = root.root();
  const edits: Edit[] = [];

  // Find function components defined inside other function components.
  // A component is a function_declaration whose name starts with uppercase,
  // nested inside another function_declaration's statement_block.
  const innerFuncComponents = rootNode.findAll({
    rule: {
      kind: "function_declaration",
      has: {
        field: "name",
        kind: "identifier",
        regex: "^[A-Z]",
      },
      inside: {
        kind: "statement_block",
        inside: {
          kind: "function_declaration",
          has: {
            field: "name",
            kind: "identifier",
            regex: "^[A-Z]",
          },
        },
      },
    },
  });

  // Find arrow function components: `const Foo = (...) => { ... }`
  const innerArrowComponents = rootNode.findAll({
    rule: {
      kind: "lexical_declaration",
      has: {
        kind: "variable_declarator",
        has: {
          field: "name",
          kind: "identifier",
          regex: "^[A-Z]",
        },
      },
      inside: {
        kind: "statement_block",
        inside: {
          kind: "function_declaration",
          has: {
            field: "name",
            kind: "identifier",
            regex: "^[A-Z]",
          },
        },
      },
    },
  });

  if (innerFuncComponents.length === 0 && innerArrowComponents.length === 0) {
    return null;
  }

  const allInner = [...innerFuncComponents, ...innerArrowComponents];

  // Group by parent component
  const parentMap = new Map<
    number,
    { parent: SgNode<TSX>; children: SgNode<TSX>[] }
  >();

  for (const inner of allInner) {
    const ancestors = inner.ancestors();
    const parentComponent = ancestors.find(
      (a) =>
        a.kind() === "function_declaration" &&
        a.field("name")?.matches({ rule: { regex: "^[A-Z]" } }),
    );
    if (!parentComponent) continue;

    const parentId = parentComponent.id();
    if (!parentMap.has(parentId)) {
      parentMap.set(parentId, { parent: parentComponent, children: [] });
    }
    parentMap.get(parentId)!.children.push(inner);
  }

  for (const { parent, children } of parentMap.values()) {
    const extractedParts: string[] = [];

    for (const child of children) {
      extractedParts.push(child.text());

      // Remove the inner component from its original location
      edits.push({
        startPos: child.range().start.index,
        endPos: child.range().end.index,
        insertedText: "",
      });
    }

    // Insert extracted components before the parent function.
    // If the parent is wrapped in an export_statement, insert before that instead.
    const parentParent = parent.parent();
    const insertTarget =
      parentParent && parentParent.kind() === "export_statement"
        ? parentParent
        : parent;

    const insertText = extractedParts.join("\n\n") + "\n\n";
    edits.push({
      startPos: insertTarget.range().start.index,
      endPos: insertTarget.range().start.index,
      insertedText: insertText,
    });
  }

  return rootNode.commitEdits(edits);
};

export default codemod;

3. Validate and test

From your scaffolded codemod package directory, validate the workflow and run the generated tests. Validate the workflow schema:
npx codemod workflow validate -w workflow.yaml
Run tests against your input/expected fixtures:
npx codemod jssg test -l tsx ./scripts/codemod.ts
Tests use fixtures in the tests/ directory. You can use simple input.*/expected.* file pairs or input/ + expected/ directory snapshots when your codemod creates or deletes files. If MCP generated test fixtures, they will run automatically. For test structure, CI integration, and debugging strategies, see Testing.

4. Run your codemod

From the same package directory, run your codemod on a target codebase:
npx codemod workflow run -w . -t /path/to/your/project
AI-powered steps: If your workflow includes an AI step gated by a parameter, enable it at runtime:
LLM_API_KEY=your-key npx codemod workflow run -w . -t /path/to/project --param run_ai_step=true
Set LLM_API_KEY as an environment variable for your chosen provider. With an API key, the built-in AI step runs normally. Without one, Codemod prints [AI INSTRUCTIONS], which parent agents such as Codex- or Claude-style tooling can pick up. See Workflow Reference — AI steps for provider configuration.
For all CLI options, see CLI reference.

5. Publish

When you’re happy with the result, publish from the package directory: Share your codemod with your team or the community:
npx codemod login
npx codemod publish
Your codemod is now in the Codemod Registry and runnable by anyone via npx codemod @your-scope/codemod-name. For CI/CD automation, trusted publishers, and API keys, see the Publishing guide.

Next steps

Scale your migrations

Your migration experience does not stop here. When you’re managing migrations across large codebases or multiple teams, the same codemod package can power Campaigns for automated multi-repo rollouts and Insights for tracking adoption progress. See the Platform quickstart for the full enterprise workflow.

Getting help

Join the community

Ask questions, get help, and connect with other codemod users on Slack.

Book a call

Schedule a demo or get hands-on help from the Codemod team.

Learn more

CLI reference

Full CLI command reference.

Workflow reference

Complete workflow.yaml specification.

JSSG

Learn the transformation engine in depth.

Publishing

Authentication methods and CI/CD setup.