What Is the Eject Process in Create React App and What Are Its Implications?

The eject process in Create React App is a one-way operation that exposes all hidden build configurations by copying Webpack, Babel, and other tooling files from react-scripts into your project, making you responsible for future maintenance.

The facebook/create-react-app repository provides a zero-configuration toolchain for React applications through the react-scripts package. While this abstraction simplifies development, the eject process in Create React App becomes necessary when you need to customize build tooling beyond the provided options. This irreversible operation, implemented in packages/react-scripts/scripts/eject.js, permanently transfers control of the build pipeline from the CRA maintainers to your development team.

How the Eject Process Works in Create React App

When you execute npm run eject or yarn eject, the shell invokes react-scripts eject, which runs the Node.js script located at packages/react-scripts/scripts/eject.js. This script orchestrates a complex migration of configuration files and dependencies through several distinct phases.

Pre-Flight Validation and User Confirmation

The eject script begins by verifying repository safety. It calls getGitStatus (lines 30-44 in eject.js) to check for uncommitted changes, aborting if the working directory is dirty. This prevents loss of work during the file migration. Next, the script prompts the user with a confirmation message (lines 65-70): "Are you sure you want to eject? This action is permanent." Only explicit confirmation allows the process to continue.

File Extraction and Content Sanitization

Once confirmed, the script constructs a list of folders and files to copy (lines 112-129), including the config/ and scripts/ directories. It verifies target paths using verifyAbsent (lines 100-110) to prevent accidental overwrites of existing files. During the copy phase, the script sanitizes file contents by stripping // @remove-on-eject comment blocks and completely skipping files marked with // @remove-file-on-eject (lines 52-69). This ensures that internal CRA utilities do not leak into the ejected project.

Dependency Migration and Script Rewriting

The most significant structural change occurs in package.json. The script removes react-scripts from dependencies and explicitly adds every package that react-scripts previously managed internally, including webpack, babel-loader, eslint, and jest (lines 77-108). It then rewrites the npm scripts to point to the local copies: "start": "node scripts/start.js" instead of "react-scripts start". Finally, it generates Jest configuration via createJestConfig, adds Babel and ESLint presets if missing, handles TypeScript declaration merging, and reinstalls node modules using npm install or yarn install.

Configuration Files Exposed by the Eject Process

After ejecting, your repository contains the entire build toolchain that was previously hidden inside node_modules/react-scripts. These files reside in two primary directories created during the process.

Webpack and Babel Configurations

The config/ directory contains webpack.config.js, which defines the complete module bundling pipeline, including loaders for JavaScript, CSS, and assets. You also gain webpackDevServer.config.js for development server customization. Babel configuration appears as babel.config.js or within package.json under the babel key, exposing the react-app preset and allowing you to add custom plugins or modify transpilation targets.

ESLint and Jest Setup

Linting configuration moves from an internal dependency to an explicit eslintConfig entry in package.json or a dedicated .eslintrc file, extending react-app and react-app/jest rules. Testing configuration becomes visible through the jest field in package.json, generated by the createJestConfig utility during ejection. This exposes transform patterns, module name mappers, and setup files that were previously abstracted.

Build and Test Scripts

The scripts/ directory contains the Node.js executables that power your npm commands. scripts/start.js launches the development server, scripts/build.js handles production optimization, and scripts/test.js runs the Jest test suite. These files import from the config/ directory and from react-dev-utils, giving you direct control over environment variables, build output, and development server behavior.

Implications of Ejecting from Create React App

Ejecting fundamentally changes the maintenance contract of your React application. While it unlocks customization capabilities, it introduces specific long-term responsibilities that teams must evaluate before executing the command.

Irreversibility remains the most critical characteristic. Once the script completes and react-scripts is removed from package.json, you cannot "uneject" to return to the managed configuration. Reversal requires creating a new CRA project and manually migrating source code, or restoring from version control.

Maintenance Burden shifts dramatically to your team. Future updates to Webpack, Babel, ESLint, or Jest must be applied manually by editing package.json version numbers and resolving breaking changes. You lose the convenience of upgrading a single react-scripts dependency to receive tested, compatible toolchain updates.

Repository Growth occurs as the config/ and scripts/ directories add approximately 2,000 lines of configuration code to your repository. This increases repository size, commit noise, and cognitive load for developers navigating the codebase.

Full Control represents the primary motivation for ejecting. You gain the ability to modify any aspect of the build pipeline, such as adding custom Webpack loaders, adjusting Babel presets, or configuring ESLint rules beyond the react-app defaults.

Dependency Visibility exposes all transitive dependencies that react-scripts previously managed internally. While this aids security auditing by making every package explicit in package.json, it also increases the surface area for npm audit warnings and requires manual curation of dependency trees.

Alternatives to Ejecting in Create React App

Before committing to the irreversible eject process, consider forking react-scripts as a maintainable alternative. This approach involves creating a custom npm package based on the official react-scripts source code, publishing it to a private registry, and referencing it in your package.json instead of the official version.

Forking preserves the abstraction layer while allowing organization-wide customizations. You can modify Webpack configurations, add Babel plugins, or adjust Jest settings in your fork, then propagate updates to multiple projects by bumping a single dependency version. The official documentation in docusaurus/docs/alternatives-to-ejecting.md provides detailed guidance on implementing this strategy.

For simple customizations, explore CRACO (Create React App Configuration Override) or react-app-rewired before ejecting. These community tools allow limited Webpack and Babel modifications without ejecting, though they add dependency risk and may lag behind official CRA updates.

Practical Example: Running the Eject Command

To initiate the eject process, execute the following command in your project root:

npm run eject

The script located at packages/react-scripts/scripts/eject.js immediately prompts for confirmation: "Are you sure you want to eject? This action is permanent." After confirmation, the script performs the migration.

Before eject, your package.json contains minimal configuration:

{
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "devDependencies": {
    "react-scripts": "5.0.1"
  }
}

After eject, the same file expands significantly:

{
  "scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "node scripts/test.js"
  },
  "dependencies": {
    "webpack": "5.x.x",
    "babel-loader": "8.x.x",
    "jest": "27.x.x",
    "eslint": "8.x.x"
  },
  "babel": {
    "presets": ["react-app"]
  },
  "eslintConfig": {
    "extends": "react-app"
  }
}

The config/ and scripts/ directories now exist in your repository, containing the full Webpack, Babel, and Jest configurations previously hidden inside node_modules/react-scripts.

Summary

  • The eject process in Create React App is an irreversible operation defined in packages/react-scripts/scripts/eject.js that exposes all build configurations by copying them from react-scripts into your project.
  • Running npm run eject triggers a multi-phase migration including Git cleanliness checks, file sanitization with // @remove-on-eject directives, and dependency promotion from transitive to explicit.
  • After ejecting, you assume full responsibility for maintaining Webpack, Babel, ESLint, and Jest configurations, losing the ability to upgrade react-scripts for automatic toolchain updates.
  • Alternatives such as forking react-scripts or using configuration override tools provide customization without sacrificing the zero-configuration benefits.

Frequently Asked Questions

Can you undo the Create React App eject process?

No, the eject process is permanently irreversible. Once packages/react-scripts/scripts/eject.js completes and removes the react-scripts dependency, you cannot "uneject" to restore the managed configuration. Reversal requires creating a new CRA project and manually migrating your source code, or restoring from a Git commit prior to the ejection.

What files are created when you eject from Create React App?

Ejecting creates two primary directories in your repository root: config/ and scripts/. The config/ directory contains webpack.config.js, webpackDevServer.config.js, and environment-specific settings, while scripts/ contains start.js, build.js, and test.js. These files are copied from node_modules/react-scripts and stripped of // @remove-on-eject blocks during the migration.

Why does Create React App require a clean Git status before ejecting?

The eject script enforces Git cleanliness to prevent data loss during the complex file migration. In packages/react-scripts/scripts/eject.js, the getGitStatus function (lines 30-44) checks for uncommitted changes and aborts if the working tree is dirty. This safety measure ensures you can revert the operation via Git if the eject process encounters errors or produces unexpected results.

What are the alternatives to ejecting from Create React App?

Instead of ejecting, you can fork react-scripts to create a custom build toolchain while maintaining the abstraction layer, as documented in docusaurus/docs/alternatives-to-ejecting.md. Community tools like CRACO (Create React App Configuration Override) or react-app-rewired allow limited Webpack and Babel modifications without ejecting, though they introduce additional dependencies that may lag behind official CRA updates.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →