How to Customize the Babel Configuration in Create React App Without Ejecting
Create React App deliberately disables external Babel configuration files via babelrc: false and configFile: false in its Webpack setup, forcing developers to use Babel macros or configuration override tools like CRACO and react-app-rewired to customize Babel without ejecting.
Create React App (CRA) provides a zero-configuration build setup that intentionally shields users from underlying tooling complexity. However, when you need to add custom Babel plugins or presets to the facebook/create-react-app toolchain, you'll find that standard configuration files like .babelrc or babel.config.js are ignored by design.
Why CRA Blocks Direct Babel Configuration
In packages/react-scripts/config/webpack.config.js (lines 433-435), the babel-loader options explicitly disable external configuration:
babelrc: false,
configFile: false,
These settings tell Babel to ignore any .babelrc, babel.config.js, or babel field in package.json. This design ensures build stability across CRA releases but prevents direct customization of the Babel pipeline.
Method 1: Use Babel Macros
CRA ships with babel-plugin-macros pre-installed, providing a way to run compile-time code transformations without modifying the core Babel configuration. Macros are configured via macro.config.json in your project root.
Create a macro.config.json file:
{
"my-macro": {
"optionA": true
}
}
Then use the macro in your source code:
// src/example.js
import myMacro from 'my-macro.macro';
const result = myMacro('input');
Method 2: Override Configuration with CRACO
CRACO (Create React App Configuration Override) is the most robust solution for modifying Babel settings without ejecting. It intercepts CRA's configuration before Webpack runs, allowing you to inject custom plugins and presets.
First, install CRACO as a development dependency:
npm install @craco/craco --save-dev
Create a craco.config.js file in your project root to extend Babel:
module.exports = {
babel: {
plugins: [
['@babel/plugin-proposal-optional-chaining', { loose: false }],
['@babel/plugin-proposal-decorators', { legacy: true }],
],
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
],
},
};
Update your package.json scripts to use CRACO instead of react-scripts:
{
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test"
}
}
Method 3: Use react-app-rewired with customize-cra
As an alternative to CRACO, you can use react-app-rewired paired with customize-cra. This combination provides a function-based API for overriding CRA's internal Webpack and Babel configurations.
Install both packages:
npm install customize-cra react-app-rewired --save-dev
Create a config-overrides.js file:
const { override, addBabelPlugin } = require('customize-cra');
module.exports = override(
addBabelPlugin(['@babel/plugin-proposal-nullish-coalescing-operator']),
addBabelPlugin(['@babel/plugin-proposal-do-expressions'])
);
Update your package.json scripts:
{
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test"
}
}
Method 4: Ejecting (Last Resort)
If you require full control over the Babel pipeline—such as modifying the core preset order or replacing babel-preset-react-app—you must run npm run eject. This command exposes all configuration files, including webpack.config.js, allowing you to remove the babelrc: false and configFile: false restrictions.
Warning: Ejecting is a one-way operation. You cannot revert to the protected CRA setup, and you lose the seamless upgrade path for react-scripts.
Summary
- CRA explicitly disables external Babel configuration via
babelrc: falseandconfigFile: falseinpackages/react-scripts/config/webpack.config.jsto ensure build stability. - Babel macros provide compile-time transformations without configuration files, utilizing the pre-installed
babel-plugin-macros. - CRACO and react-app-rewired are the officially recommended paths to customize Babel plugins and presets without ejecting.
- Ejecting remains the only option for fundamental changes to the Babel pipeline, but it permanently exits the managed CRA environment.
Frequently Asked Questions
How can I tell if my Babel configuration is being loaded by CRA?
If your custom plugins are not transforming code as expected, CRA is likely ignoring your configuration. Verify this by inspecting node_modules/react-scripts/config/webpack.config.js for the babelrc: false and configFile: false settings, which explicitly disable external Babel configuration files.
What is the difference between CRACO and react-app-rewired?
Both tools allow you to customize CRA without ejecting, but CRACO provides a more structured plugin API and dedicated configuration file (craco.config.js) specifically designed for Babel and Webpack overrides. react-app-rewired uses a function-based override system via config-overrides.js and is typically paired with customize-cra for convenience methods.
Can I use TypeScript with custom Babel plugins in CRA?
Yes. CRA supports TypeScript out of the box, and configuration override tools like CRACO can inject Babel plugins that process TypeScript files. Ensure your custom Babel plugins are compatible with TypeScript syntax or are configured to run after the TypeScript preset transformation to avoid compilation errors.
Is ejecting from CRA reversible?
No. Once you run npm run eject, the command exposes all configuration files—including webpack.config.js and Babel configurations—directly into your project directory. This is a permanent operation that cannot be undone, and you lose the ability to receive seamless updates to react-scripts. Always exhaust macro and override tool options before ejecting.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →