# import/extensions

> Enforce that import paths always include the file extension, or never do

`import "./user"` and `import "./user.js"` can name the same file. Browsers and Node.js only accept the second in ES modules, while bundlers and Bun accept both. This rule keeps a project on one style, for all files or per extension.

It checks `import`, `export ... from`, `import()` and `require()`. Built-in modules and bare package names such as `"react"` or `"@babel/core"` are never checked.

{/* Generated by src/codegen/lint-rule-docs.ts. Do not edit this section. */}

## Configure

Off by default. To turn it on:

```toml bunfig.toml
[lint.rules]
"import/extensions" = "error" # or "warn"
```

Every option, set to its default:

```toml bunfig.toml
[lint.rules]
"import/extensions" = [
  "error",
  "never", # "always", "ignorePackages", "never" or object
  {
    pattern = {},
    ignorePackages = false,
    checkTypeImports = false,
    pathGroupOverrides = [], # list of objects
  },
]
```

{/* End of the generated section. */}

## Examples

Reported:

{/* file "user.js" */}
```js
export function createUser(name) {
  return { name };
}
```

{/* bad {"options": ["always"]} */}
```js
import { createUser } from "./user";
```

Not reported:

{/* file "user.js" */}
```js
export function createUser(name) {
  return { name };
}
```

{/* good {"options": ["always"]} */}
```js
import { createUser } from "./user.js";
import { join } from "node:path";
import { useState } from "react";
```

## Options

```toml bunfig.toml
[lint.rules]
"import/extensions" = ["error", "never", { json = "always" }]
```

The first option sets the style for every extension:

| Option | What it does |
| --- | --- |
| `"never"` (default) | Report an extension that the path doesn't need, such as `./user.js` where `./user` finds the same file. |
| `"always"` | Report a path without an extension. |
| `"ignorePackages"` | Like `"always"`, but paths into packages such as `"lodash/fp/map"` aren't checked. |

{/* file "user.js" */}
```js
export function createUser(name) {
  return { name };
}
```

{/* bad {"options": ["never"]} */}
```js
import { createUser } from "./user.js";
```

{/* bad {"options": ["always"]} */}
```js
import map from "lodash/fp/map";
```

{/* good {"options": ["ignorePackages"]} */}
```js
import map from "lodash/fp/map";
```

An object after it, or instead of it, holds exceptions and more settings:

| Option | Default | What it does |
| --- | --- | --- |
| an extension, such as `js` or `json` | the first option | `"always"` or `"never"` for files with that extension. |
| `pattern` | `{}` | The same extension entries, in their own object. If you set `ignorePackages` or `checkTypeImports`, put the entries in here. Entries written next to those two options are ignored. |
| `ignorePackages` | `false` | Don't require an extension in paths into packages. |
| `checkTypeImports` | `false` | Also require extensions in `import type` and `export type`. |
| `pathGroupOverrides` | `[]` | A list of `{ pattern, action }`. A path that matches the glob `pattern` is skipped if `action` is `"ignore"`. With `"enforce"` it's checked even if it looks like a package. It's ignored unless you also set `pattern`, `ignorePackages` or `checkTypeImports`. |

{/* good {"options": ["never", {"json": "always"}]} */}
```js
import { createUser } from "./user";
import settings from "./settings.json";
```

{/* good {"options": ["always"]} */}
```ts
import type { User } from "./user";
```

{/* bad {"options": ["always", {"checkTypeImports": true}]} */}
```ts
import type { User } from "./user";
```

{/* bad {"options": ["ignorePackages"]} */}
```js
import { routes } from "~/generated/routes";
```

{/* good {"options": ["always", {"ignorePackages": true, "pathGroupOverrides": [{"pattern": "~/generated/**", "action": "ignore"}]}]} */}
```js
import { routes } from "~/generated/routes";
```

The rule looks up which file `./user` stands for, the way Bun and TypeScript do, so it knows that it's `./user.ts`:

{/* file "user.ts" */}
```ts
export function createUser(name: string) {
  return { name };
}
```

{/* bad {"options": ["ignorePackages", {"ts": "never"}]} */}
```ts
import { createUser } from "./user.ts";
```

{/* file "user.ts" */}
```ts
export function createUser(name: string) {
  return { name };
}
```

{/* good {"options": ["ignorePackages", {"ts": "never"}]} */}
```ts
import { createUser } from "./user";
```

With an ESLint config it only tries `.mjs`, `.js`, `.json` and `.node`, like Node.js does, unless `settings["import/resolver"]` says otherwise.
