🚨 The Refactoring Problem at Scale
Imagine you're working on a React application with a shared design system. Your team decides to update the Button component API to be more consistent:
In a small project, updating these props is straightforward. Your IDE can find and replace type=
with variant=
and size="large"
with size="lg"
across a few components in minutes.
But now imagine this design system is used across multiple applications, dozens of teams, and hundreds of components. Suddenly, this simple prop rename becomes a nightmare:
- 🔍 Hidden references — props aren't always in JSX; they may be destructured, e.g.
const { type, size } = buttonProps
orfunction MyButton({ type, size }) { ... }
, which a simple find-and-replace won't catch. - 🎯 Logic dependencies — business logic often relies on prop values, e.g.
if (type === "primary") { ... }
, so updating JSX alone breaks behavior. - 🏭 Generated props — factories or helpers can create props dynamically, e.g.
const buttonProps = createButtonProps({ type: "primary" })
, meaning the change isn't always local. - 💥 Unsafe replacements — global search risks false positives, like accidentally updating unrelated code such as
const user = { type: "admin" }
. - ⏰ Cross-team overhead — even if you fix your app, dozens of other teams are making changes in parallel, causing endless merge conflicts and weeks of coordination.
What started as a simple design system update is now a complex, error-prone, and time-consuming migration affecting your entire organization.
This is exactly why codemods exist: to automate safe, large-scale code transformations across codebases that are too big and complex for manual refactors.
🎛️ What Are Codemods?
A codemod (short for code modification) is a scripted transformation of source code. Instead of manually searching and replacing code, a codemod analyzes your code's Abstract Syntax Tree (AST) and rewrites it in a safe and predictable way. Think of them as "search & replace on steroids" — but instead of working with raw text, they understand your code's structure, context, and meaning.
Unlike simple find-and-replace operations, codemods follow a sophisticated 4-step process:
- 🧠 Code Analysis: Codemods analyze the codebase by parsing it into an Abstract Syntax Tree (AST). The AST represents the code's structure in a way that's easy for computers to understand and manipulate.
- 🔍 Pattern Matching: Codemods then traverse the AST, searching for specific code patterns or structures that need to be modified.
- ⚡ Transformation: Once a match is found, the codemod applies the necessary changes to the AST. This could involve adding, deleting, or replacing code elements.
- 📝 Code Regeneration: Finally, the codemod transforms the modified AST back into readable source code, effectively updating the codebase.
This process allows codemods to perform complex transformations that would be impossible with simple text-based find-and-replace operations.
🤩 Use-Cases for Codemods
Codemods are a proven tool for reducing technical debt and evolving codebases safely. They turn risky migrations into automated, repeatable processes. Here are some of the most impactful ways teams use them:
🔼 Framework & Library Upgrades
Frameworks evolve quickly, and upgrades often bring breaking API changes. Codemods make those migrations fast and reliable.
- 👉 Next.js publishes official codemods to help developers migrate between major versions (e.g., 14 → 15), turning what could take weeks into a script that runs in minutes.
- 👉 AWS also provides a codemod to migrate from aws-sdk v2 to v3, handling massive API changes without requiring teams to manually rewrite every single call.
✅ With codemods, painful upgrades become routine maintenance.
⚙️ Tooling Migrations
Switching test frameworks or developer tools is notoriously tedious. Codemods remove the pain.
- 👉 Entire organizations have migrated from Mocha/Tape → Jest, with codemods automatically rewriting imports, test syntax, and assertions.
- 👉 This keeps thousands of tests consistent and eliminates weeks of manual effort.
✅ Codemods let teams change their tools without slowing down development.
React & UI Transformations
React's fast evolution would have left developers behind — if not for codemods.
👉 Facebook used codemods internally (and later shared them publicly) to:
- Replace deprecated lifecycle methods.
- Migrate to Hooks.
- Convert PropTypes to TypeScript interfaces.
✅ Thanks to codemods, these massive refactors scaled across millions of lines of code without breaking development velocity.
🔄 Modernization & Consistency
Beyond upgrades, codemods are a powerful way to modernize legacy code and enforce consistent practices.
👉 Companies like Airbnb and Stripe have used codemods to:
- Replace callback patterns with async/await.
- Restructure imports for better tree-shaking.
- Adopt TypeScript by automatically inserting type annotations.
✅ This keeps teams aligned, codebases modern, and reviews focused on logic.
🛠️ Creating Your First Codemod
Now that we've seen what codemods are and some scenarios where they shine, it's time to take the next step: creating our own codemod.
In this section, we'll walk through the process of building a simple codemod to convert CommonJS require()
statements to ES6 import
statements. This is a common migration task that many teams face when modernizing their JavaScript codebases.
But before we dive into the own codemod solution, let's first see what tools we have available to help us write codemods effectively.
🧰 Codemod Tools
Codemods exist for many programming languages (Java, Python, Go, etc.), but in this case we'll focus on JavaScript/TypeScript. Within the JavaScript ecosystem, there are several powerful libraries for writing codemods, let's review some of most popular:
jscodeshift
- 🏢 Created by Facebook and battle-tested on massive codebases.
- 👥 Most popular codemod toolkit with extensive community support.
- 💻 Works with both JavaScript and TypeScript.
- 📚 Good documentation and examples.
🔎 Read more about jscodeshift
here.
ts-morph
- 🔷 TypeScript-first approach with excellent type awareness.
- 🎯 More intuitive API for developers familiar with TypeScript.
- 🔧 Great for complex type transformations and refactoring.
- 💪 Smaller community but very powerful for TS-specific tasks.
🔎 Read more about ts-morph
here.
✨ Our chosen solution
Both tools are excellent, but for our case, we'll use jscodeshift because:
- 📚 Learning curve: Extensive documentation and real-world examples make it beginner-friendly.
- 🏢 Industry standard: Used by Facebook, Airbnb, and countless other companies.
- 🔄 Flexibility: Handles both simple and complex transformations equally well.
- 🌍 Community: Largest ecosystem of existing codemods you can learn from and build upon.
📥 Setting Up Our Project
Now that we've chosen jscodeshift
as our codemod tool, let's set up a new project to create our codemod.
First, we need to create a new directory for our codemod project. Open your terminal and run the following commands:
Next, we need to initialize a new npm project. This will create a package.json
file where we can manage our dependencies:
Now, we need to install jscodeshift
as a dependency. This is the library that will help us write and run our codemod:
Note that here, we are using
jscodeshift
as a local dependency which is my recommended approach for custom codemods, since if we are working on a large codebase with multiple teams, we avoid potential conflicts with globally installed versions.
Finally, let's create a folder where we can have some test files to run our codemod against:
And let's create a sample file example.js
inside test-files
with some CommonJS require()
statements:
Now we have our project set up with jscodeshift
installed and a test file to work with. We are ready to write our codemod logic.
🪄 Writing the Transformation Logic
Let’s write our codemod in a new file named require-to-import.js
that will contain the transformation logic. This file will use jscodeshift's API to traverse the AST of our JavaScript files and replace require()
statements with import
statements.
At first glance, a codemod written with jscodeshift
can look a bit intimidating—there are many APIs and AST concepts that may not be obvious right away. But once you get comfortable with how the API works, you'll realize it gives you incredible power to perform safe, large-scale refactors and migrations that would otherwise be error-prone or impossible manually.
In this example, we've aimed to be very descriptive about every step of the transformation. Each small piece of logic is separated into atomic helper functions, and we've used JSDoc comments to clearly explain what each function does, including examples of the input and expected output. This approach makes the codemod easier to read, maintain, and extend, while also serving as a self-documenting reference for anyone looking at the code later.
🚀 Running Our Codemod
At this point, we have our codemod logic ready. Now we can test it against our test-files/example.js
.
For doing it, we can use the --dry
and --print
flags to preview the changes without modifying the file:
If everything looks good, we are ready to run the codemod:
Your test-files/example.js
should now show:
In this example, we're running the codemod manually on a single file (
test-files/example.js
) to see how it works. However,jscodeshift
allows you to run it over an entire directory or even filter by specific file extensions.
💡 Best Practices
Now that you know how to create a basic codemod, here are some best practices to keep in mind when building and using codemods in your projects:
- 🧪 Test in a safe environment – Run your codemod on a small subset of your codebase or a dedicated branch before applying it everywhere.
- 📄 Document your codemods – Explain what the codemod does, why it exists, and how it should be used for future team members.
- 📚 Document your codemod internally – Codemods can be hard to understand at first. To make them clearer, use JSDoc comments to describe what we are doing, and break your logic into small, descriptive functions wherever it helps readability and maintainability.
- 🔁 Make codemods idempotent – Running them multiple times shouldn't break the code or create duplicate changes.
- 🧠 Leverage type information and AST parsing – Avoid relying solely on text search; understand the structure of your code to make safe transformations.
🎯 Conclusion
In this way, we've seen how, with the use of codemods, we can automate large-scale changes in our code safely and efficiently, reducing manual errors and speeding up refactors in large projects.
We've also covered best practices that I've applied in some of my own codemods, such as creating small and descriptive functions, documenting behavior with JSDoc, and structuring the code clearly, which makes it easier to understand, maintain, and reuse in the future.
I hope this post has been helpful and inspires you to explore the power of codemods in your own projects.
Thank you for reading! 🙌