As a library developer, you may create a popular utility that hundreds of
    thousands of developers rely on daily, such as lodash or React. Over time,
    usage patterns might emerge that go beyond your initial design. When this
    happens, you may need to extend an API by adding parameters or modifying
    function signatures to fix edge cases. The challenge lies in rolling out
    these breaking changes without disrupting your users’ workflows.
This is where codemods come in—a powerful tool for automating
    large-scale code transformations, allowing developers to introduce breaking
    API changes, refactor legacy codebases, and maintain code hygiene with
    minimal manual effort.
In this article, we’ll explore what codemods are and the tools you can
    use to create them, such as jscodeshift, hypermod.io, and codemod.com. We’ll walk through real-world examples,
    from cleaning up feature toggles to refactoring component hierarchies.
    You’ll also learn how to break down complex transformations into smaller,
    testable pieces—a practice known as codemod composition—to ensure
    flexibility and maintainability.
By the end, you’ll see how codemods can become a vital part of your
    toolkit for managing large-scale codebases, helping you keep your code clean
    and maintainable while handling even the most challenging refactoring
    tasks.
Breaking Changes in APIs
Returning to the scenario of the library developer, after the initial
      release, new usage patterns emerge, prompting the need to extend an
      API—perhaps by adding a parameter or modifying a function signature to
      make it easier to use.
For simple changes, a basic find-and-replace in the IDE might work. In
      more complex cases, you might resort to using tools like sed
      or awk. However, when your library is widely adopted, the
      scope of such changes becomes harder to manage. You can’t be sure how
      extensively the modification will impact your users, and the last thing
      you want is to break existing functionality that doesn’t need
      updating.
A common approach is to announce the breaking change, release a new
      version, and ask users to migrate at their own pace. But this workflow,
      while familiar, often doesn’t scale well, especially for major shifts.
      Consider React’s transition from class components to function components
      with hooks—a paradigm shift that took years for large codebases to fully
      adopt. By the time teams managed to migrate, more breaking changes were
      often already on the horizon.
For library developers, this situation creates a burden. Maintaining
      multiple older versions to support users who haven’t migrated is both
      costly and time-consuming. For users, frequent changes risk eroding trust.
      They may hesitate to upgrade or start exploring more stable alternatives,
      which perpetuating the cycle.
But what if you could help users manage these changes automatically?
      What if you could release a tool alongside your update that refactors
      their code for them—renaming functions, updating parameter order, and
      removing unused code without requiring manual intervention?
That’s where codemods come in. Several libraries, including React
      and Next.js, have already embraced codemods to smooth the path for version
      bumps. For example, React provides codemods to handle the migration from
      older API patterns, like the old Context API, to newer ones.
So, what exactly is the codemod we’re talking about here?
What is a Codemod?
A codemod (code modification) is an automated script used to transform
      code to follow new APIs, syntax, or coding standards. Codemods use
      Abstract Syntax Tree (AST) manipulation to apply consistent, large-scale
      changes across codebases. Initially developed at Facebook, codemods helped
      engineers manage refactoring tasks for large projects like React. As
      Facebook scaled, maintaining the codebase and updating APIs became
      increasingly difficult, prompting the development of codemods.
Manually updating thousands of files across different repositories was
      inefficient and error-prone, so the concept of codemods—automated scripts
      that transform code—was introduced to tackle this problem.
The process typically involves three main steps:
- Parsing the code into an AST, where each part of the code is
 represented as a tree structure.
- Modifying the tree by applying a transformation, such as renaming a
 function or changing parameters.
- Rewriting the modified tree back into the source code.
By using this approach, codemods ensure that changes are applied
      consistently across every file in a codebase, reducing the chance of human
      error. Codemods can also handle complex refactoring scenarios, such as
      changes to deeply nested structures or removing deprecated API usage.
If we visualize the process, it would look something like this:
 
Figure 1: The three steps of a typical codemod process
The idea of a program that can “understand” your code and then perform
      automatic transformations isn’t new. That’s how your IDE works when you
      run refactorings like 
      Essentially, your IDE parses the source code into ASTs and applies
      predefined transformations to the tree, saving the result back into your
      files.
For modern IDEs, many things happen under the hood to ensure changes
      are applied correctly and efficiently, such as determining the scope of
      the change and resolving conflicts like variable name collisions. Some
      refactorings even prompt you to input parameters, such as when using
      
      order of parameters or default values before finalizing the change.
Use jscodeshift in JavaScript Codebases
Let’s look at a concrete example to understand how we could run a
      codemod in a JavaScript project. The JavaScript community has several
      tools that make this work feasible, including parsers that convert source
      code into an AST, as well as transpilers that can transform the tree into
      other formats (this is how TypeScript works). Additionally, there are
      tools that help apply codemods to entire repositories automatically.
One of the most popular tools for writing codemods is jscodeshift, a toolkit maintained by Facebook.
      It simplifies the creation of codemods by providing a powerful API to
      manipulate ASTs. With jscodeshift, developers can search for specific
      patterns in the code and apply transformations at scale.
You can use jscodeshift to identify and replace deprecated API calls
      with updated versions across an entire project.
Let’s break down a typical workflow for composing a codemod
      manually.
Clean a Stale Feature Toggle
Let’s start with a simple yet practical example to demonstrate the
        power of codemods. Imagine you’re using a feature
        toggle in your
        codebase to control the release of unfinished or experimental features.
        Once the feature is live in production and working as expected, the next
        logical step is to clean up the toggle and any related logic.
For instance, consider the following code:
const data = featureToggle('feature-new-product-list') ? { name: 'Product' } : undefined;
Once the feature is fully released and no longer needs a toggle, this
        can be simplified to:
const data = { name: 'Product' };
The task involves finding all instances of featureToggle in the
        codebase, checking whether the toggle refers to
        feature-new-product-list, and removing the conditional logic surrounding
        it. At the same time, other feature toggles (like
        feature-search-result-refinement, which may still be in development)
        should remain untouched. The codemod needs to understand the structure
        of the code to apply changes selectively.
Understanding the AST
Before we dive into writing the codemod, let’s break down how this
        specific code snippet looks in an AST. You can use tools like AST
        Explorer to visualize how source code and AST
        are mapped. It’s helpful to understand the node types you’re interacting
        with before applying any changes.
The image below shows the syntax tree in terms of ECMAScript syntax. It
        contains nodes like Identifier (for variables), StringLiteral (for the
        toggle name), and more abstract nodes like CallExpression and
        ConditionalExpression.
 
Figure 2: The Abstract Syntax Tree representation of the feature toggle check
In this AST representation, the variable data is assigned using a
        ConditionalExpression. The test part of the expression calls
        featureToggle('feature-new-product-list'). If the test returns true,
        the consequent branch assigns { name: 'Product' } to data. If
        false, the alternate branch assigns undefined.
For a task with clear input and output, I prefer writing tests first,
        then implementing the codemod. I start by defining a negative case to
        ensure we don’t accidentally change things we want to leave untouched,
        followed by a real case that performs the actual conversion. I begin with
        a simple scenario, implement it, then add a variation (like checking if
        featureToggle is called inside an if statement), implement that case, and
        ensure all tests pass.
This approach aligns well with Test-Driven Development (TDD), even
        if you don’t practice TDD regularly. Knowing exactly what the
        transformation’s inputs and outputs are before coding improves safety and
        efficiency, especially when tweaking codemods.
With jscodeshift, you can write tests to verify how the codemod
        behaves:
const transform = require("../remove-feature-new-product-list"); defineInlineTest( transform, {}, ` const data = featureToggle('feature-new-product-list') ? { name: 'Product' } : undefined; `, ` const data = { name: 'Product' }; `, "delete the toggle feature-new-product-list in conditional operator" );
The defineInlineTest function from jscodeshift allows you to define
        the input, expected output, and a string describing the test’s intent.
        Now, running the test with a normal jest command will fail because the
        codemod isn’t written yet.
The corresponding negative case would ensure the code remains unchanged
        for other feature toggles:
defineInlineTest(
  transform,
  {},
  `
  const data = featureToggle('feature-search-result-refinement') ? { name: 'Product' } : undefined;
  `,
  `
  const data = featureToggle('feature-search-result-refinement') ? { name: 'Product' } : undefined;
  `,
  "do not change other feature toggles"
);
Writing the Codemod
Let’s start by defining a simple transform function. Create a file
        called transform.js with the following code structure:
module.exports = function(fileInfo, api, options) {
  const j = api.jscodeshift;
  const root = j(fileInfo.source);
  // manipulate the tree nodes here
  return root.toSource();
};
This function reads the file into a tree and uses jscodeshift’s API to
        query, modify, and update the nodes. Finally, it converts the AST back to
        source code with .toSource().
Now we can start implementing the transform steps:
- Find all instances of featureToggle.
- Verify that the argument passed is 'feature-new-product-list'.
- Replace the entire conditional expression with the consequent part,
 effectively removing the toggle.
Here’s how we achieve this using jscodeshift:
module.exports = function (fileInfo, api, options) {
  const j = api.jscodeshift;
  const root = j(fileInfo.source);
  // Find ConditionalExpression where the test is featureToggle('feature-new-product-list')
  root
    .find(j.ConditionalExpression, {
      test: {
        callee: { name: "featureToggle" },
        arguments: [{ value: "feature-new-product-list" }],
      },
    })
    .forEach((path) => {
      // Replace the ConditionalExpression with the 'consequent'
      j(path).replaceWith(path.node.consequent);
    });
  return root.toSource();
};
The codemod above:
- Finds ConditionalExpressionnodes where the test calls
 featureToggle('feature-new-product-list').
- Replaces the entire conditional expression with the consequent (i.e., {), removing the toggle logic and leaving simplified code
 name: 'Product' }
 behind.
This example demonstrates how easy it is to create a useful
        transformation and apply it to a large codebase, significantly reducing
        manual effort.
You’ll need to write more test cases to handle variations like
        if-else statements, logical expressions (e.g.,
        !featureToggle('feature-new-product-list')), and so on to make the
        codemod robust in real-world scenarios.
Once the codemod is ready, you can test it out on a target codebase,
        such as the one you’re working on. jscodeshift provides a command-line
        tool that you can use to apply the codemod and report the results.
$ jscodeshift -t transform-name src/
After validating the results, check that all functional tests still
        pass and that nothing breaks—even if you’re introducing a breaking change.
        Once satisfied, you can commit the changes and raise a pull request as
        part of your normal workflow.
Codemods Improve Code Quality and Maintainability
Codemods aren’t just useful for managing breaking API changes—they can
        significantly improve code quality and maintainability. As codebases
        evolve, they often accumulate technical debt, including outdated feature
        toggles, deprecated methods, or tightly coupled components. Manually
        refactoring these areas can be time-consuming and error-prone.
By automating refactoring tasks, codemods help keep your codebase clean
        and free of legacy patterns. Regularly applying codemods allows you to
        enforce new coding standards, remove unused code, and modernize your
        codebase without having to manually modify every file.
Refactoring an Avatar Component
Now, let’s look at a more complex example. Suppose you’re working with
        a design system that includes an Avatar component tightly coupled with a
        Tooltip. Whenever a user passes a name prop into the Avatar, it
        automatically wraps the avatar with a tooltip.
 
Figure 3: A avatar component with a tooltip
Here’s the current Avatar implementation:
import { Tooltip } from "@design-system/tooltip";
const Avatar = ({ name, image }: AvatarProps) => {
  if (name) {
    return (
      
         
    );
  }
  return 
The goal is to decouple the Tooltip from the Avatar component,
        giving developers more flexibility. Developers should be able to decide
        whether to wrap the Avatar in a Tooltip. In the refactored version,
        Avatar will simply render the image, and users can apply a Tooltip
        manually if needed.
Here’s the refactored version of Avatar:
const Avatar = ({ image }: AvatarProps) => {
  return 
Now, users can manually wrap the Avatar with a Tooltip as
        needed:
import { Tooltip } from "@design-system/tooltip";
import { Avatar } from "@design-system/avatar";
const UserProfile = () => {
  return (
    
       
  );
};
The challenge arises when there are hundreds of Avatar usages spread
        across the codebase. Manually refactoring each instance would be highly
        inefficient, so we can use a codemod to automate this process.
Using tools like AST Explorer, we can
        inspect the component and see which nodes represent the Avatar usage
        we’re targeting. An Avatar component with both name and image props
        is parsed into an abstract syntax tree as shown below:
 
Figure 4: AST of the Avatar component usage
Writing the Codemod
Let’s break down the transformation into smaller tasks:
- Find Avatarusage in the component tree.
- Check if the nameprop is present.
- If not, do nothing.
- If present:
-  Create a Tooltipnode.
-  Add the nameto theTooltip.
-  Remove the namefromAvatar.
-  Add Avataras a child of theTooltip.
-  Replace the original Avatarnode with the newTooltip.
To begin, we’ll find all instances of Avatar (I’ll omit some of the
        tests, but you should write comparison tests first).
defineInlineTest(
    { default: transform, parser: "tsx" },
    {},
    `
    
       
    `,
    "wrap avatar with tooltip when name is provided"
  );
Similar to the featureToggle example, we can use root.find with
        search criteria to locate all Avatar nodes:
root
  .find(j.JSXElement, {
    openingElement: { name: { name: "Avatar" } },
  })
  .forEach((path) => {
    // now we can handle each Avatar instance
  });
Next, we check if the name prop is present:
root
  .find(j.JSXElement, {
    openingElement: { name: { name: "Avatar" } },
  })
  .forEach((path) => {
    const avatarNode = path.node;
    const nameAttr = avatarNode.openingElement.attributes.find(
      (attr) => attr.name.name === "name"
    );
    if (nameAttr) {
      const tooltipElement = createTooltipElement(
        nameAttr.value.value,
        avatarNode
      );
      j(path).replaceWith(tooltipElement);
    }
  });
For the createTooltipElement function, we use the
        jscodeshift API to create a new JSX node, with the name
        prop applied to the Tooltip and the Avatar
        component as a child. Finally, we call replaceWith to
        replace the current path.
Here’s a preview of how it looks in
        Hypermod, where the codemod is written on
        the left. The top part on the right is the original code, and the bottom
        part is the transformed result:
 
Figure 5: Run checks inside hypermod before apply it to your codebase
This codemod searches for all instances of Avatar. If a
        name prop is found, it removes the name prop
        from Avatar, wraps the Avatar inside a
        Tooltip, and passes the name prop to the
        Tooltip.
By now, I hope it’s clear that codemods are incredibly useful and
        that the workflow is intuitive, especially for large-scale changes where
        manual updates would be a huge burden. However, that’s not the whole
        picture. In the next section, I’ll shed light on some of the challenges
        and how we can address these less-than-ideal aspects.


 
                                 
                                 
                                