Comparing ast-grep and jscodeshift

If you’ve ever had to update thousands of files for a framework migration, API rename, or design-system refresh, you already know the pain: regex is too fragile, and manual edits are too slow.
Two tools show up over and over in these conversations: ast-grep and jscodeshift.
Both are good. Both can save days (or weeks). But they have very different ergonomics and strengths.
This guide is a practical, “what should I actually use?” comparison based on real codemod workflows.
TL;DR
- Use jscodeshift when you need deep JavaScript/TypeScript transforms and want full control in JS code.
- Use ast-grep when you want speed, multi-language matching, and easier pattern-first rules.
- Important nuance: ast-grep is not just YAML rules. It also ships with a Node API (N-API binding), so you can write codemods in JavaScript/TypeScript instead of YAML when that feels more natural.
- If your transformation crosses language boundaries (for example, JavaScript string literals containing HTML, or css-in-js template literals), ast-grep’s multi-language story is often more straightforward.
What is ast-grep?
ast-grep is an AST-based search and transform engine with a pattern-first workflow.
Most people first meet it via declarative rules (YAML/JSON), which is great for quickly codifying “find this shape, replace with that shape.” But ast-grep also has a Node API through N-API, which means you can drive transformations programmatically in JS/TS and avoid YAML entirely if that’s your preference.
Why teams like ast-grep
- Fast scans and rewrites (Rust core)
- Pattern matching that reads close to source code
- Works across many languages (JS/TS, JSON, Rust, Go, Python, HTML, CSS, and more)
- Can run as rules in CI, or as scripted transforms in Node
Quick declarative example
id: no-console-log
language: JavaScript
rule:
pattern: console.log($ARG)
fix: console.debug($ARG)
Read-onlyThat’s enough to match and rewrite console.log(...) calls.
What is jscodeshift?
jscodeshift is the classic JavaScript codemod toolkit.
You write transformations in JavaScript, powered by recast + AST tooling. It’s imperative, explicit, and very flexible for JS/TS codebases.
Why teams like jscodeshift
- Mature ecosystem and lots of existing codemods
- Great for deep, custom JS/TS migrations
- Recast-based printing that generally preserves style/comments well
- Fully programmable transform logic in JavaScript
Quick jscodeshift example
export default function transformer(fileInfo, api) {
const j = api.jscodeshift;
return j(fileInfo.source)
.find(j.CallExpression, {
callee: {
object: { type: 'Identifier', name: 'console' },
property: { type: 'Identifier', name: 'log' },
},
})
.replaceWith((path) =>
j.callExpression(
j.memberExpression(j.identifier('console'), j.identifier('debug')),
path.value.arguments
)
)
.toSource();
}
Read-onlyThorough comparison: where each tool shines
1) Authoring experience
- jscodeshift: You write JavaScript. If your team already thinks in AST nodes and traversals, this is comfortable and powerful.
- ast-grep: You can start with concise declarative rules, which lowers the barrier for many migrations. And when rules get complex, you can switch to the Node API (N-API) and keep everything in JS/TS.
In practice: ast-grep gives you both a quick declarative entry point and a programmable path without forcing YAML forever.
2) Language coverage
- jscodeshift: Primarily JS/TS-focused.
- ast-grep: Multi-language by design. This matters when a migration touches more than one syntax in the same repo.
In practice: if your codemod spans UI templates, styles, config files, and JS/TS app code, ast-grep usually requires fewer tool handoffs.
3) Performance and scale
- ast-grep: Rust implementation is generally very fast for scanning and matching at repo scale.
- jscodeshift: Capable and battle-tested, but often slower on very large codebases depending on transform complexity.
In practice: both can handle serious migrations, but ast-grep often feels faster in broad pattern-matching passes.
4) Transform complexity
- jscodeshift: Excellent when you need highly specific AST logic, custom control flow, or stateful transformations.
- ast-grep: Great for structural patterns and bulk rewrites; can still handle advanced workflows through rule composition and Node API scripting.
In practice: for “surgical JS-only AST programming,” jscodeshift remains a top choice.
5) Cross-language migrations (real-world edge)
This is where ast-grep can be unexpectedly strong.
It’s common to find language boundaries inside one file:
- JS string literals that contain HTML
styled-components/ emotion template literals containing CSS- Framework macros that embed SQL/GraphQL/CSS
With ast-grep, you can parse and transform those embedded languages as part of one workflow more naturally than a JS-only AST stack.
Working examples: switching languages in one migration
Below are practical examples that teams actually run into.
Example A: JavaScript string with embedded HTML
Input
const template = '<button class="btn btn-primary">Save</button>';
Read-onlyGoal
Rename btn-primary to button-primary inside the HTML string.
ast-grep approach (Node API concept)
- Parse JavaScript and find string literals.
- For candidate literals that look like HTML, run an HTML ast-grep transform on the string value.
- Write the transformed HTML back into the JS string literal.
Pseudo-code shape:
// Conceptual flow using ast-grep Node API + HTML parsing
for (const jsString of findJavaScriptStringLiterals(source)) {
if (!looksLikeHtml(jsString.value)) continue;
const updatedHtml = transformWithAstGrep({
language: 'html',
code: jsString.value,
find: '<button class="btn btn-primary">$$$</button>',
replace: '<button class="btn button-primary">$$$</button>',
});
replaceStringLiteral(jsString, updatedHtml);
}
Read-onlyThis pattern is hard to do robustly with plain regex and awkward with JS-only AST tools unless you bolt on additional parsers manually.
Example B: css-in-js template literals
Input
const Button = styled.button`
color: #fff;
background: var(--brand-primary);
`;
Read-onlyGoal
Migrate --brand-primary to --color-primary.
ast-grep approach
- Parse TS/JS and find tagged templates where tag is
styled.*(orcss). - Parse template content as CSS.
- Apply CSS-level rewrite and write it back.
Pseudo-code shape:
for (const cssTemplate of findCssInJsTemplates(source)) {
const nextCss = transformWithAstGrep({
language: 'css',
code: cssTemplate.content,
find: 'var(--brand-primary)',
replace: 'var(--color-primary)',
});
replaceTemplateContent(cssTemplate, nextCss);
}
Read-onlyThis is a great example of why ast-grep’s multi-language + N-API path can be more versatile than a single-language codemod tool.
So… which one should you pick?
Choose jscodeshift when
- Your migration is purely JS/TS AST work
- You want full imperative control and already have AST expertise
- You rely on existing jscodeshift codemods in your ecosystem
Choose ast-grep when
- You want fast structural matching and rewriting
- You need one codemod workflow across multiple languages
- You like declarative rules for simple cases, but still want to drop into JS/TS via Node API (N-API) for advanced logic
- You’re dealing with embedded-language scenarios (HTML in strings, css-in-js, etc.)
Final take
This isn’t really a “winner takes all” situation.
- jscodeshift is still excellent for deep JS/TS codemod engineering.
- ast-grep is often the more versatile choice for modern repos where migrations cross language boundaries, and its N-API Node API closes much of the flexibility gap people assume exists.
If you’re building codemods for a large org, there’s a good chance you’ll want both in your toolbox—just used for different kinds of jobs.