Let me kick things off with the basics and gradually increase the complexity as we move from one topic to the next. We'll start with straightforward rules and configuration options. From there, we'll dive into how to make the most of configs and plugins. Along the way, I'll share practical tips and insights to help you assemble your own ESLint and Prettier setup.

How it all began

Initially, I had no intention of adopting ESLint and Prettier. The need just wasn't there, given that Angular, which I work with every day, ships with its own linting tool and a straightforward formatter. However, a handful of factors ultimately pushed me toward using these tools more seriously.

To start with, there's the never-ending debate over code style and formatting. That's a tedious subject, at least from my perspective. Personal tastes should take a back seat here. There are far more pressing concerns to focus on.

Then there's the issue of bringing this into a team setting. Colleagues are bound to have questions, and I'd rather not be caught off guard. If you can't answer their queries or champion the tools effectively, you risk losing their interest.

Lastly, there's the sheer impact these tools have. They let you concentrate on what matters without constantly getting sidetracked from your workflow—for instance, when a code-review comment points out that the code wasn't formatted correctly.

At the end of the day, it's about the business aspect, regardless of how much we enjoy our work. These distractions just waste time, and that time could be better spent elsewhere.

So, as you can see, developers face plenty of distractions during their daily grind. Let's tackle these interruptions together with the help of these well-established web tools.

Info: TSLint might not be around for Angular much longer, since TypeScript has shifted its support toward ESLint. The Angular team is already working on moving from TSLint to ESLint. You can check it out here.

What is ESLint and how can it help us?

ESLint is a tool that examines the code you've written. In essence, it's a static code analyzer capable of spotting syntax issues, bugs, or style improvements. In other languages like Go, this functionality is baked right into the language itself.

What do I need to get started with ESLint?

I'll assume you have node and npm set up on your operating system and are comfortable using them.

Create a playground directory

You'll want to navigate into a directory that holds your JavaScript or TypeScript project, or, like I did, create a dedicated test folder named "lint-examples" to follow along with this article. On a Linux-based system, you can simply run mkdir lint-examples in the terminal and then switch into it using cd lint-examples.

Install ESLint

Let's set up a package.json first so we can get ESLint installed. Just run the command npm init, which generates a package.json—an essential step for installing eslint in your current directory.

Add eslint to your npm scripts

{
  "name": "eslint-examples",
  "version": "0.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "eslint": "eslint"
  },
  "devDependencies": {
    "eslint": "7.1.0"
  }
}

Pro tip: using "eslint": "eslint" inside scripts is a shorthand for invoking node_modules/.bin/eslint

Set up test.js

Let's now generate a basic JavaScript file within the lint-examples folder so we have something for ESLint to process. The unusual formatting is intentional — it'll serve as our initial input. Don't concern yourself with that just yet; we’ll build on this baseline.

var foo = 1
console.log(foo)
var bar
bar = 1
function test(



    ) {
  console.log(baz)
}
var baz = 123

First try on command-line

When you run test.js via ESLint at this point, no output appears. Its default behavior only flags syntax errors, with ES5 serving as the baseline option. Check out ESLint's documentation on parser options.

Had your earlier snippet relied on const or let, ESLint would have raised an error right away, since, as noted, ES5 remains the preset.

Tip: you can forward arguments to the eslint command-line tool from npm scripts by prefixing them with --.

npm run eslint -- ./test.js

Things start to get interesting here.

The ideal options depend on how up-to-date your project is. We’ll assume ES6 syntax for the upcoming examples.

Setting up the first .eslintrc

ESLint accepts configuration in several file formats, but .eslintrc is my go-to choice. Learn about all supported formats here.

{
  "env": {
    "es6": true
  }
}

Info: The env key is mandatory for global variables. Setting env with es6 to true makes ESLint enable globals for modern types such as Set, along with ES6 syntax like let and const.
Check out ESLint's specifying-parser-options documentation.

Let's add some rules to .eclintrc

Do we need to do anything special to use rules? No, because ESLint is already installed and ships with many built-in rules. However, for features like TypeScript or other non-standard syntax, you'll need to install an eslint-config-xxx or eslint-plugin-xxx module—we'll cover that shortly. The full rule list is available at: ESLint-Rules.

{
  "env": {
    "es6": true
  },
  "rules": {
    "no-var": "error",
    "semi": "error",
    "indent": "error",
    "no-multi-spaces": "error",
    "space-in-parens": "error",
    "no-multiple-empty-lines": "error",
    "prefer-const": "error",
    "no-use-before-define": "error"
  }
}

Executing npm run eslint at this point should produce an output similar to the one shown below.

error  'foo' is never reassigned. Use 'const' instead  prefer-const
error  Missing semicolon                               semi
error  Expected indentation of 0 spaces but found 4    indent
error  'bar' is never reassigned. Use 'const' instead  prefer-const
error  Multiple spaces found before ')'                no-multi-spaces
error  There should be no space after this paren       space-in-parens
error  There should be no space before this paren      space-in-parens
error  More than 2 blank lines not allowed             no-multiple-empty-lines
error  'baz' was used before it was defined            no-use-before-define
error  'baz' is never reassigned. Use 'const' instead  prefer-const

26 problems (26 errors, 0 warnings)
20 errors and 0 warnings potentially fixable with the `--fix` option.

We now know what our coding and styling rules look like, but in a real-world project, there are naturally many more. My goal was simply to demonstrate how straightforward rule configuration is.

You might have seen in ESLint's output that 20 of the 26 reported issues are auto-fixable. We'll dive into that in the next part.

Can ESLint handle code formatting?

To some extent, ESLint can auto-format code. As the earlier log shows, passing a --fix flag will adjust code according to eslint rules. For instance, a missed semicolon will be inserted, and extra blank lines will be stripped out. This applies to all other fixable rules as well.

Run the following command to fix the code: npm run eslint -- ./ --fix

var foo = 1;
console.log(foo);
var bar;
var = 1;
function test(

) {
    console.log(baz);
}
var baz = 123;
 1:1 error  Unexpected var, use let or const instead  no-var
 3:1 error  Unexpected var, use let or const instead  no-var
11:1 error  Unexpected var, use let or const instead  no-var

3 problems (3 errors, 0 warnings)

Not every ESLint rule is auto-fixable, as you have just observed. The remaining three errors require manual intervention. Yet other diagnostics — missing semicolons, indentation problems, redundant whitespace — did resolve themselves.

Be aware: var remains un-fixable because of browser-specific behavior. The background is described in more detail here.

The ESLint rule list marks each rule with an icon: those carrying a check mark can be enabled, while a wrench icon indicates code that is eligible for automatic formatting.

  • The rule catalog lives at ESLint-Rules.
  • Close to 300 rules exist, and that number keeps climbing.
  • Roughly 100 of those rules concern auto-formatting.

Things get significantly stronger once your editor applies formatting on every save, or a CI tool like Travis picks up the job at every Git push.

If ESLint is already capable of formatting, why bother with Prettier?

As the snippet above demonstrated, ESLint's formatting only goes so far. The result is not particularly readable, especially inside the function body. This is where the distinction becomes critical. ESLint concentrates on code quality. Prettier, true to its name, renders the code pretty. Let's test how much further Prettier can go.

How do I get going with Prettier?

It requires little effort. Add "prettier": "prettier" to your NPM scripts and execute npm install prettier.

Remember what the ESLint-formatted version looked like? It was far from neat, and that's where Prettier comes in.

// test.js
const foo = 1;
console.log(foo);
let bar;
bar = 1;
function test(

) {
    console.log(baz);
}
const baz = 123;

Executing npm run prettier -- --write ./test.js yields an improved code layout.

const foo = 1;
console.log(foo);
let bar;
bar = 1;
function test() {
  console.log(baz);
}
const baz = 123;

This is a significant improvement. The advantages only grow as your codebase expands.

Does Prettier allow for customization as well?

Absolutely. Prettier's parser options are nowhere near as comprehensive as those offered by ESLint. With Prettier, you're largely relying on its opinionated parser. The final appearance of your code is determined by a limited set of choices you provide.

Below are my configurations, specified in .prettierrc. For a complete rundown of all style-related options, head over to prettier-options. Now, let's set up a .prettierrc file using these settings.

{
  "semi": true,
  "trailingComma": "all",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2,
  "arrowParens": "avoid"
}

Is running ESLint and Prettier in parallel necessary?

Launching ESLint and Prettier independently to enforce coding and formatting standards is not recommended. In fact, the two tools may conflict, since they share certain overlapping rules, which can result in unpredictable outcomes. This issue is tackled and resolved in the upcoming section. Essentially, you will only invoke eslint from the terminal, and prettier will be handled automatically.

Let's go back to the starting point!

At the outset of this guide, I pointed out that I had zero prior experience with ESLint and Prettier. Consequently, I had no idea how to make the entire setup operational. Like any developer would, I grabbed a seemingly ideal configuration snippet from somewhere online and dropped it into my .eslintrc, without truly understanding its purpose. All that mattered was making things function.

Below is a small excerpt from my .eslintrc configuration—it originated from various copied sources and was gradually modified as I gained deeper insight into its mechanics.

Put simply, the open source ecosystem offers configs and plugins for ESLint, which means we don't have to build everything from scratch. The key is to grasp what happens underneath.

.eslintrc

{
  "plugins": [
    "@typescript-eslint",
    "prettier",
    "unicorn" ,
    "import"
  ],
  "extends": [
    "airbnb-typescript/base",
    "plugin:@typescript-eslint/recommended",
    "plugin:unicorn/recommended",
    "plugin:prettier/recommended",
    "prettier",
    "prettier/@typescript-eslint"
  ],
  "parserOptions": {
    "ecmaVersion": 2020,
    "sourceType": "module"
  },
  "env": {
    "es6": true,
    "browser": true,
    "node": true
  },
  "rules": {
    "no-debugger": "off",
    "no-console": 0
  }
}

Note: you may have spotted prettier listed under plugins, and you might recall my earlier question: "Must we run ESLint and Prettier together for formatting?" Not at all — eslint-plulgin-prettier and eslint-config-prettier handle that for us.

What do these settings and options mean?

Once I got it functioning, I was curious about the purpose behind it all. Honestly, it left me stunned. If you execute ESLint from your terminal with those settings, it will complain that the configs (extends) and plugins are missing. How can you determine what to install? It's a familiar scenario: you find a snippet on Stackoverflow or in some repo, yet the installation steps remain unclear.

Keep this in mind: every module referenced under extends and plugins is installable. The key is decoding the naming scheme of those properties, so you can properly run npm to fetch them.

What are the "plugins" options?

Plugins are bundles of rules crafted with a parser. They might include experimental rules from TC39 that ESLint hasn't adopted yet, or custom style guidelines beyond ESLint's defaults, such as unicorn/better-regex and import/no-self-import.

Suppose you want a rule that enforces every file to begin with an emoji-based comment before any actual code. That might sound odd, but it's achievable using an ESLint plugin.

// :penguin: emoji

Decoding the plugin naming convention

If a plugin name begins with neither eslint-plugin-, nor @, nor ./, simply prepend eslint-plugin- to it.

plugins: [
  "prettier", // npm module "eslint-plugin-prettier"
  "unicorn"   // npm module "eslint-plugin-unicorn"
]

You can achieve the same result with this alternative, which functions identically:

plugins: [
  "eslint-plugin-prettier", // the same as "prettier"
  "eslint-plugin-unicorn"   // the same as "unicorn"
]

When you encounter plugin names that begin with @ (a namespace), the situation becomes more nuanced. In the sample provided, the / character only appears once. It’s important to recognize that @mylint and @mylint/foo share the same namespace yet represent distinct plugins, each backed by its own npm module.

plugins: [
  "@typescript-eslint", // npm module "@typescript-eslint/eslint-plugin"
  "@mylint",        	// npm module "@mylint/eslint-plugin"
  "@mylint/foo",        // npm module "@mylint/eslint-plugin-foo"
  "./path/to/plugin.js  // Error: cannot includes file paths
]

The code example below is identical to the one shown above.

plugins: [
  "@typescript-eslint/eslint-plugin", // the same as "@typescript-eslint"
  "@mylint/eslint-plugin",            // the same as "@mylint"
  "@mylint/eslint-plugin-foo"         // the same as "@mylint/foo"
]

Tip: The compact syntax (first sample) beats the extended version (second sample) in readability. Just remember how ESLint expands it under the hood.

With the plugin naming convention cleared up, it’s time to pull in these ESLint plugins using NPM.

npm i eslint-plugin-prettier eslint-plugin-unicorn

Further details on the naming convention can be found in the ESLint documentation, refer to plugin namingconvention.

For verification purposes, your .eslintrc configuration should appear as follows.

{
  "plugins": [
    "prettier",
    "unicorn"
  ],
  "env": {
    "es6": true
  }
}

Prettier: An ESLint plugin dedicated to code formatting. Additional details are available here.

Unicorn: A set of supplementary rules that ESLint does not provide out of the box. Explore them here.

Executing npm run eslint from your terminal yields neither an error nor any linting output. This happens because the plugin module must first be registered within the extends property of your .eslintrc, or alternatively be enabled through the rules section.

Decoding the extends naming scheme

If you assume the naming convention for extends mirrors that of plugins, prepare for a surprise—they diverge. Honestly, grasping this distinction took me considerable time. Part of the reason is that ESLint, at least to me, is a dense and expansive subject.

When you stick to a plain name (such as foo) without a prefixed namespace (@) or a path (./to/my/config.js), the logic behind extends naming aligns with the plugins option. Consequently, foo is resolved to eslint-config-foo.

extends: [
  "airbnb-base", // npm module "eslint-config-airbnb-base"
  "prettier"     // npm module "eslint-config-prettier"
]

is equal to

extends: [
  "eslint-config-airbnb-base", // shortform is "airbnb-base"
  "eslint-config-prettier"     // shortform is "prettier"
]

At this stage, we arrive at the juncture where the naming conventions for plugins and extends diverge, particularly when you incorporate namespaces (@) in the extends section. The @mylint ESLint configuration example remains identical, referencing the @mylint/eslint-config NPM package; however, using @mylint/foo within extends may cause an issue because omitting the eslint-config- prefix from @mylint/eslint-congif-foo can lead to a failure.

extends: [
  "@bar",                   // npm module "@bar/eslint-config"
  "@bar/eslint-config-foo", // npm module "@bar/eslint-config-foo"
  "@bar/my-config"          // npm module "@bar/eslint-config/my-config"
]

The @mylint/my-config package I mentioned in the preceding section's introduction is unusual: it bundles an NPM module, yet from an ESLint standpoint, it also points internally to a rule set called my-config. We'll clarify this distinction in a moment. For the official rules on naming in the extends field, consult the shareable-configs documentation.

Now, let's fetch the remaining NPM packages needed for our sample application.

npm i eslint-config-airbnb-base eslint-config-prettier

Note: You may have spotted that eslint-plugin-prettier was added earlier, while eslint-config-prettier has just been installed. These two packages are distinctly different, yet they function as a pair. More on that ahead.

What is the actual role of extends inside .eslintrc?

A config bundles predefined rules. These rules may include ESLint core rules, rules from third-party plugins, or other configurations — covering the parser (babel, esprima, …), parser options (sourceType, …), environment settings (ES6, …), and similar.

That's a benefit for us, since we avoid doing this setup manually. Experienced developers and teams have already put significant effort into crafting these configs. Our only task is to enable them — either by referencing a full config or a specific rule set from a plugin.

How do I locate these rule sets?

You have several options for searching them out!

To start, check the README.md in the relevant repository and follow its guidance closely. In most cases, these rule sets are labeled "recommended" and need to be enabled inside the plugins section. Activating via extends isn't always required.

Another approach — one I find superior in practice — is figuring out which rule set to use without consulting the README.md. This method shines when the README.md lacks details or contains errors.

In short, "plugins" reference a single file where configurations are stored as an object, whereas "extends" points to rule sets that live in separate files.

eslint-config-airbnb-base

eslint-config-airbnb-base (repository)
| -- index.js
| -- legacy.js
| -- whitespace.js

While it is possible to enable every configuration simultaneously, caution is advised. It is essential to understand what each one does ahead of time. I previously demonstrated this by examining the relevant README.md or by going straight to the specific rules within the corresponding configuration set. Once you determine how to enable them, the process becomes quite straightforward.

Usage:

"extends": [
  "airbnb-base",            // index.js
  "airbnb-base/whitespace"  // whitespace.js
]

Important note: Because each ruleset can extend or override the one before it, the sequence matters. So avoid going overboard with configurations and plugins. A helpful explanation can be found on Stackoverflow.

eslint-plugin-prettier

This brings us to the most interesting part. Here's how you can integrate Prettier directly into ESLint, skipping the need to execute it as a separate tool via the command line or your IDE.

First, you'll need to enable eslint-plugin-prettier within the extends block, followed by the associated configuration eslint-config-prettier. That latter piece handles turning off specific ESLint rules that might otherwise clash with Prettier.

eslint-plugin-mylint (repository)
| -- eslint-plugin-prettier.js (because this is specified as entrypoint in package.json)

eslint-plugin-prettier.js

module.exports = {
  configs: {
    recommended: {
      extends: ['prettier'],
      plugins: ['prettier'],
      rules: {
        'prettier/prettier': 'error'
      }
    }
  }
  ...
  ...
  ...

Usage:

"extends": [
  "plugin:prettier/recommended"
]

Note: To make a plugin work, it must be added under plugins and then enabled inside extends with the :plugin suffix.

Setting up eslint-config-prettier

eslint-config-prettier (repository)
| -- index.js
| -- @typescript-eslint.js
| -- babel.js
| -- flowtype.js
| -- react.js
| -- standard.js
| -- unicorn.js
| -- vue.js

Usage:

"extends": [
  "prettier",                   // index.js
  "prettier/unicorn",           // unicorn.js
  "prettier/@typescript-eslint" // @typescript-eslint.js
]

Note: The bare "prettier" entry in extends is required, since it turns off a selection of core ESLint rules. The remaining entries are needed to disable rules coming from unicorn and @typescript-eslint.

My personal ESLint configuration matches the usage example shown above. Since I work with TypeScript and the Unicorn plugin, I want to prevent any overlap with ESLint. That is why Prettier is used to switch off specific rules from both TypeScript and Unicorn.

So far, we have enabled entire rule sets, which are essentially just collections of rules grouped together. Still, you are not forced to rely on a bundled configuration. Individual rules can be modified or switched off on your own.

It would not be reasonable to configure every rule manually instead of using a preset. What happens frequently, however, is that you disagree with a certain rule or its configuration. In such a situation, you can turn off that single rule. An example follows.

.eslintrc

"rules": {
  "unicorn/prevent-abbreviations": "off"
}

Let’s revisit the test example. At this point, our .eslintrc file should be structured as shown below.

{
  "plugins": [
    "prettier",
    "unicorn"
  ],
  "extends": [
    "airbnb-base",
    "plugin:unicorn/recommended",
    "plugin:prettier/recommended",
    "prettier",
    "prettier/unicorn"
  ],
  "env": {
    "es6": true,
    "browser": true
  },
  rules: {
    "unicorn/prevent-abbreviations": "off"
  }
}

Strategy: When moving over to ESLint, it’s common to see a large number of errors in the output. Fixing all of them right away might be time-consuming or even introduce unexpected issues. To keep the transition gradual and hassle-free, it’s advisable to set the rules to warning severity instead of error. Refer to configuring rules for details.

If you execute npm run eslint -- --fix on the example code at this point, ESLint will trigger Prettier, meaning a single command handles both tools.

How do we integrate this into an IDE?

Rather than detailing how to enable ESLint in a specific IDE, I’ll note that popular editors like IntelliJ and VS Code all come with ESLint support. However, you may need to supply the --fix flag in the IDE’s configuration for automated formatting to kick in.

Why are there several "ESLint" parsers?

ESLint only handles JavaScript syntax that has achieved the final stage in TC39. Often overlooked, the Babel compiler can also process features not yet at the final stage. A prime case is decorators: the variant Angular relied on was dropped, while the alternative features distinct syntax and behavior. The former was at stage 2 and the latter is still early in development.

In such scenarios, ESLint won’t be of assistance. You’d either need to locate an appropriate plugin or craft a custom ESLint plugin that leverages, say, the babel parser rather than espree, which is ESLint’s built-in parser.

Check the eslint-parser settings for more.

What about Angular and ESLint?

The Angular Team advises postponing the adoption of ESLint. That perspective is fair, as they aim for a seamless rollout. Still, if you’re keen to experiment, here are a few recommendations. See the Github link.

How does performance fare with ESLint?

There may be instances where ESLint doesn’t deliver the speed you’d expect in certain code sections, but this is typical and can occur with TSLint as well. To address it, consider using ESLint’s built-in cache or a separate ESLint daemon. Useful insights are available in this Stackoverflow thread.

Is Prettier limited to Javascript only?

Prettier officially supports various other languages, such as PHP, Ruby, and Swift. Moreover, there are community plugins for languages like Java, Kotlin, Svelte, and others.

What does ESLint v7 bring?

Every example in this article originally relied on ESLint v6, but v7 has since been introduced. Rest assured, version 7 works without necessitating any modifications to the code. For a rundown of updates and additions, browse the release notes for ESLint v7.

A full example repository

My own open-source project, https://github.com/pregular/core, also pushed me toward adopting ESLint and Prettier.

Wrapping up

In my view, you now have the essentials for working with ESLint and Prettier and can tackle this independently from here on. The key is consistent practice to truly reinforce what you’ve learned.

I’m grateful to the InDepth community for featuring my article. Special thanks go to Lars Gyrup Brink Nielsen for his guidance on my first piece.