What is @babel/preset-env and why do I need it?

If you have followed any recent tutorial on how to set up a modern JavaScript project, you have installed Babel and then added @babel/preset-env to your .babelrc:

  "presets": ["@babel/preset-env"]

what’s the difference between @babel/preset-env and babel-preset-env?

…and things works fine. It transpiles the ES6 for you just like you wanted to and you move on with the tutorial.

But what did you just do with your .babelrc file and why does it work?

It’s not a nice feeling to just type something that you read in a tutorial without really understand why you should use it. Let’s look into what babel-preset-env really do, and why you need it.

This is part 2 of my post about implementing ES6 with Babel and webpack. In the first part, you learned the basics of what problems Babel solves.

Before I can tell you what babel-preset-env does, I need to explain the basics of Babel to you. It’s good to refresh even if you feel you already know it.

Babel from scratch

The most common way to use Babel is together with a build system such as webpack. When using Babel with a build tool it’s easy to overlook how Babel really works. It feels like Babel and webpack is the same thing.

A good exercise is to learn Babel without a build system - even though you don’t intend to use it that way in production. So get your editor and terminal ready and code along.

First create a new NPM project:

mkdir babel-test
cd babel-test
npm init -y

Next install Babel as dependency to your empty project:

npm install --save-dev @babel/core @babel/cli

Add a file called input.js and add the following to it:

const hello = () => console.log("hello world!")

That is an arrow function which is an ES6 feature. This is supported in most browsers but not all, that’s why it’s a good idea to transpile it to ES5. Now let’s run this file through Babel:

npx babel input.js --out-file output.js

If you open output.js, you’ll see that nothing has changed. It’s exactly the same content as input.js. That’s not what you expected, was it? The reason for this is that Babel doesn’t do anything out-of-the-box but Babel is built on presets and plugins. If you don’t add any presets or plugins, then Babel will do nothing.

Babel doesn't do anything out-of-the-box

It’s the Babel plugins that does the work

To make Babel actually do something, we need to add a plugin. It’s the plugins that does the heavy lifting.

Each plugin is it’s own NPM library. So for every plugin you want to install, you have to install a new NPM library or you can use a preset that we’ll come back to in the next section.

In the code you have written in input.js you only have one ES6 feature: an arrow function. So what we are going to now is to install a Babel plugin that transpiles ES6 arrow functions.

Let’s start by installing a plugin that transpiles the function you have written in input.js file.

npm install --save-dev @babel/plugin-transform-arrow-functions

Next, you need to tell Babel to use the dependency. Add a file called .babelrc file to the root of your project. This is the configuration file for Babel, and you’ll use it to tell babel to use the plugin @babel/plugin-transform-arrow-functions when doing the transpliation. This plugin only transpiles ES6 arrow functions.

  "plugins": ["@babel/plugin-transform-arrow-functions"]

Ok, that’s it. Now you can re-run babel and look at the generated output.js file. It now contains the transpiled code!

const hello = function () {
  return console.log("hello world!");

That’s awesome! But if you want to use more ES6 features you would need to install one NPM package and add an entry in .babelrc for every feature. That’s just too much work. But there is a solution to this: presets!

Babel presets bundles together common Babel plugins

The Babel foundation has created presets that contains common bundles of plugins. That means you only have to do the NPM installation and babel configuration once and then a bunch of plugins are automatically installed for you.

There are many different Babel presets, both official presets from Babel foundation and unofficial presets from other organizations such as Airbnb. Heck, you could even create your own preset if you want.

The official presets are:

  • @babel/preset-env
  • @babel/preset-flow
  • @babel/preset-react
  • @babel/preset-typescript

Every preset is its own NPM dependency that you need to install and configure. Ok, now you are finally ready to learn about babel-preset-env.

Ok, now babel-preset-env

Let’s convert you mini-code base to use babel-preset-env instead of @babel/plugin-transform-arrow-functions

Install the NPM dependency:

npm install --save-dev @babel/preset-env

Next, configure in the preset section of your .babelrc file

  "presets": ["@babel/preset-env"]

What you have done now, is that you have installed the basic configuration of babel-preset-env. Out-of-the-box you get all plugins you need to transpile all ES6 features!

But babel preset env is smarter than this. You can have even more fine grained controll over what plugins should be installed

The unique selling point with babel-preset-env is that you can define what browsers you support

By default, babel-preset-env just installs all ES6 plugin you’ll need. But this can bloat up your bundle. Look at this example how modern ES6 is transpiled to old legacy JavaScript code

From this tweet by @jamiebuilds

On the left is the ES6 code, then you can see in the middle how much code is generated if you must support very old browsers. And the amount of code needed to support not-super-old browsers to the right.

The amount of code needed to support old legacy browsers in this example is extreme.

So if you only use the default babel-preset-env you might end up in a bloated bundle. What can we do about this?

It would be nice if there were a way to tell Babel to not use all plugins, but only a subset of plugins that you really need. That way less of your code gets transpiled to bloated ES5 code.

If you read my previous post on this subject, you learned that most modern browsers already support most ES6 features. If you look at Google analytics for your site you might realize that 99% are on modern browsers. So maybe you don’t have to support all old browsers that have ever existed.

When you have made a decision on what browsers you want to support, you can tell babel exactly what browsers you should support with babel-preset-env. You do that with the config file called .browserlistrc which is part of a standard that defines what browsers your app supports. Babel-preset-env reads from this config file.

You can, for example, say you don’t support IE by adding the following to your .browserlistrc file:

not ie all

or that you define that you only support browsers with a market share of 0.25% or more:


If you describe to babel-preset-env what browsers you support, it can use less plugins, and that means less unneeded code.

How should my browserconfig look like?

Read your Google Analytics stats (or whatever tool you are using) to get an impression of what browsers your users are using, then use the browerlist docs to compose your configuration.

If you don’t use Google Analytics then a good default can be to support all browsers with a market share of 0.25% or higher like this:


Using “last 2 versions” is not recommended.

You can get a list of all browsers supported by your browserlist config by running following command

npx browserslist

It will list all browsers in the console.

Next step is to learn how to configure webpack

As I mentioned earlier, the most common way to use Babel is with webpack. So now that you know how babel works, a natural next step is to start looking into webpack.

I have created an email course that gives you one email per day that will only take a few minutes to read. After five days you have learned how to build a complete production ready webpack app. Get access today by signing up below. It’s free!