JavaScript/TypeScript linter (ESLint wrapper) with great defaults
Opinionated but configurable ESLint wrapper with lots of goodies included. Enforces strict and readable code. Never discuss code style on a pull request again! No decision-making. No eslint.config.js to manage. It just works!
It uses ESLint underneath, so issues regarding built-in rules should be opened over there.
XO requires your project to be ESM.

unicorn, import-x, ava, n and more.$ npm init xo.$ xo --fix.$ xo --open.compat option.npm install xo --save-dev
You must install XO locally. You can run it directly with $ npx xo.
For framework-specific linting, see Astro, React, Svelte, and Vue.
$ xo --help
Usage
$ xo [<file|glob> ...]
Options
--fix Automagically fix issues
--fix-dry-run Automagically fix issues without saving the changes to the file system
--reporter Reporter to use
--space Use space indent instead of tabs [Default: 2]
--config Path to a XO configuration file
--semicolon Use semicolons [Default: true]
--prettier Format with prettier or turn off Prettier-conflicted rules when set to 'compat' [Default: false]
--print-config Print the effective ESLint config for the given file
--version Print XO version
--open Open files with issues in your editor
--quiet Show only errors and no warnings
--max-warnings Number of warnings to trigger nonzero exit code [Default: -1]
--stdin Validate/fix code from stdin
--stdin-filename Specify a filename for the --stdin option
--ignore Ignore pattern globs, can be set multiple times
--suppressions-location Path to a custom ESLint suppressions file
--cwd=<dir> Working directory for files [Default: process.cwd()]
Examples
$ xo
$ xo index.js
$ xo *.js !foo.js
$ xo --space
$ xo --print-config=index.js
$ echo 'const x=true' | xo --stdin --fix
Tips
- Add XO to your project with `npm init xo`.
- Put options in xo.config.js instead of using flags so other tools can read it.
Any of these can be overridden if necessary.
if (condition) {}=== instead of ==Check out an example and the ESLint rules.
The recommended workflow is to add XO locally to your project and run it with the tests.
Simply run $ npm init xo (with any options) to add XO to create an xo.config.js.
You can configure XO options by creating an xo.config.js or an xo.config.ts file in the root directory of your project, or you can add an xo field to your package.json. XO supports all js/ts file extensions (js,cjs,mjs,ts,cts,mts) and popular framework extensions (vue,svelte,astro) automatically. A XO config is an extension of ESLint's Flat Config. Like ESLint, an XO config exports an array of XO config objects. XO config objects extend ESLint Configuration Objects. This means all the available configuration params for ESLint also work for XO. However, XO enhances and adds extra params to the configuration objects to make them easier to work with.
XO exports the types FlatXoConfig, XoConfigItem, and other types for you to get TypeScript validation on your config files.
examples:
xo.config.js
/** @type {import('xo').FlatXoConfig} */
const xoConfig = [...]
xo.config.ts
import {type FlatXoConfig} from 'xo';
const xoConfig: FlatXoConfig = [...]
export default [...] satisfies import('xo').FlatXoConfig
Type: string | (string | string[])[]\
Default: **/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx,vue,svelte,astro}
A glob string, array of globs, or ESLint's native format (where nested arrays create AND patterns) indicating which files the config object applies to. By default XO will apply the configuration to all files. This is compatible with ESLint plugin configs, so you can spread them directly into your XO config.
Tip: If you are adding additional
@typescript-eslintrules to your config, these rules will apply to JS files as well unless you separate them appropriately with thefilesoption.@typescript-eslintrules set to'off'or0, however, will have no effect on JS linting.
Type: string | string[]
Some paths are ignored by default, including paths in .gitignore. Additional ignores can be added here.
Tip: For global ignores, keep
ignoresas the only key in the config item. You can optionally set anameproperty. Adding more properties will cause ignores to be scoped down to your files selection, which may have unexpected effects.
Global negated ignores are supported in both config files and the CLI for reopening XO's built-in ignored paths. This includes directory globs like !dist/**, file globs like !**/*.min.js, and literal file paths like !dist/src/index.js.
When global ignores are involved, XO uses them in this order:
ignoresignoresXO keeps positive ignores for fast file discovery and only rechecks XO's own default-ignored paths. ESLint makes the final ignore decision.
Type: boolean | number\
Default: false (tab indentation)
Set it to true to get 2-space indentation or specify the number of spaces.
This option exists for pragmatic reasons, but I would strongly recommend you read “Why tabs are superior”.
Type: boolean\
Default: true (Semicolons required)
Set it to false to enforce no-semicolon style.
Type: boolean | 'compat'\
Default: false
Format code with Prettier.
XO applies its own Prettier options:
truefalsefalseallAny options you set in a Prettier config still apply for anything XO does not configure (like printWidth or plugins), but XO's own style settings take precedence.
If the Prettier option is set to compat, instead of formatting your code automatically, XO will turn off all rules that conflict with Prettier code style and allow you to pass your formatting to the Prettier tool directly.
To lint Astro files, install eslint-plugin-astro:
npm install --save-dev eslint-plugin-astro
Then spread its recommended config in your xo.config.js:
import astroPlugin from 'eslint-plugin-astro';
const xoConfig = [
...astroPlugin.configs.recommended,
];
export default xoConfig;
To lint React files, install eslint-config-xo-react:
npm install --save-dev eslint-config-xo-react
Then spread it in your xo.config.js:
import xoReact from 'eslint-config-xo-react';
const xoConfig = [
...xoReact(),
];
export default xoConfig;
[!NOTE] Until
eslint-plugin-reactsupports ESLint 10 natively, you may need to wrap the config withfixupConfigRulesfrom@eslint/compat.
To lint Svelte files, install eslint-plugin-svelte:
npm install --save-dev eslint-plugin-svelte
Then spread its recommended config in your xo.config.js:
import sveltePlugin from 'eslint-plugin-svelte';
const xoConfig = [
...sveltePlugin.configs.recommended,
];
export default xoConfig;
To lint Vue files, install eslint-plugin-vue:
npm install --save-dev eslint-plugin-vue
Then spread its recommended config in your xo.config.js:
import vuePlugin from 'eslint-plugin-vue';
const xoConfig = [
...vuePlugin.configs['flat/recommended'],
];
export default xoConfig;
If you want to extend a shareable ESLint config or any other npm package, use xo.config.js instead of package.json, since package.json only supports serializable values and cannot import.
xo.config.js
export {default} from 'my-shareable-config';
You can also extend and override:
import myConfig from 'my-shareable-config';
export default [
...myConfig,
{
rules: {
// Your overrides
},
},
];
XO will automatically lint TypeScript files (.ts, .mts, .cts, and .tsx) with the rules defined in eslint-config-xo-typescript#use-with-xo.
XO will handle the @typescript-eslint/parser project option automatically even if you don't have a tsconfig.json in your project.
You can opt out of XO's automatic tsconfig handling by specifying your own languageOptions.parserOptions.project, languageOptions.parserOptions.projectService, or languageOptions.parserOptions.tsconfigRootDir. Files in a config with these properties will be excluded from automatic tsconfig handling.
There are two different ways to use XO's rules with ESLint directly, depending on whether you use the xo CLI.
xo CLIIf you don't use the xo CLI and just want XO's rules in ESLint, use eslint-config-xo. It accepts the same core style options as XO, including Prettier integration:
eslint.config.js
import eslintConfigXo from 'eslint-config-xo';
export default [
...eslintConfigXo({space: true, prettier: true}),
];
[!NOTE] This replaces the old
xoToEslintConfighelper. For example,xoToEslintConfig([{space: true, prettier: true}])becomeseslintConfigXo({space: true, prettier: true}). For per-file overrides, add normal ESLint config objects alongside it.
xo CLI (editor integration)If you use the xo CLI but your editor only has the ESLint extension (not XO's), add an eslint.config.js that re-exports the adapter. It reads your xo.config.js and generates the matching ESLint config automatically, so your editor shows the same errors as running xo — without duplicating your config.
eslint.config.js
export {default} from 'xo/eslint-adapter';
Put a xo.config.js with your config at the root and do not add a config to any of your bundled packages.
To include files that XO ignores by default, add them as negative globs in the [`ignore