Jest Configuration: jest.config.js, jest.config.ts, and package.json "jest" Field

Last updated:

Jest config can live in three places: jest.config.js (the most common choice), jest.config.ts (TypeScript projects that have ts-node available), or a "jest" key inside package.json (fine for small projects). Jest 29 documents roughly 150 configuration options, but the ones you actually touch in production are about thirty: testEnvironment, transform, moduleNameMapper, setupFilesAfterEach,coverageThreshold, and projects for monorepos. This guide is the practical subset — everything else in the official docs is rarely needed.

Need to inspect or fix a jest.config.json? Paste it into Jsonic's JSON Validator to catch trailing commas and quoting issues before Jest does.

Validate jest.config.json

Three config locations and which to use

Jest searches for configuration in this priority order: an explicit --config <path> flag wins; otherwise Jest looks for jest.config.js, jest.config.ts, jest.config.mjs,jest.config.cjs, and jest.config.json in the project root; finally it checks for a "jest" key inside package.json. Only one source is used per run — Jest does not merge across locations.

LocationUse whenTradeoffs
jest.config.jsDefault for almost every projectDynamic values (env, path.resolve); no type-checking
jest.config.tsYou want type-checked config and already have ts-nodeLoaded via ts-node at runtime — adds ~200ms startup
jest.config.mjsYou want top-level await or pure ESM configWorks in Jest 29+; older versions need a flag
jest.config.jsonRarely — a tool requires JSON inputNo comments, no logic, no imports
"jest" in package.jsonTiny projects, examples, single-file demosInflates package.json; same JSON limits
// jest.config.js  (recommended)
/** @type {import('jest').Config} */
module.exports = {
  testEnvironment: 'node',
  testMatch: ['**/__tests__/**/*.test.ts', '**/*.spec.ts'],
  transform: { '^.+\\.tsx?$': '@swc/jest' },
  moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1' },
  collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'],
}

The @type JSDoc comment gives you autocomplete and type-checking inside jest.config.js without switching to jest.config.ts — usually the best of both worlds.

The 30 options you'll actually use

Of Jest's ~150 documented options, most teams touch fewer than thirty. Grouped by category, here are the ones worth knowing — defaults shown in parentheses.

CategoryOptionPurpose
EnvironmenttestEnvironment ("node")Switch to "jsdom" for DOM tests
testEnvironmentOptionsPass options to the env (e.g., userAgent for jsdom)
globalSetup / globalTeardownRun once before / after the entire suite (start DB, etc.)
setupFiles / setupFilesAfterEachPer-test-file hooks (load polyfills, reset mocks)
TransformtransformMap regex of file paths to a transformer (babel-jest, @swc/jest, ts-jest)
transformIgnorePatterns (["/node_modules/"])Skip these paths — biggest source of ESM errors
moduleFileExtensionsExtensions Jest resolves (default: js, mjs, cjs, jsx, ts, tsx, json, node)
moduleNameMapperPath aliases and asset stubs (CSS, images)
extensionsToTreatAsEsmEnable native ESM for listed extensions (still experimental)
Test discoverytestMatchGlobs for test files (defaults cover **/__tests__/** + *.test.*)
testPathIgnorePatterns (["/node_modules/"])Regex of paths to ignore
roots (["<rootDir>"])Directories Jest scans for tests
testRegexAlternative to testMatch for regex-based discovery
CoveragecollectCoverage (false)Equivalent to --coverage flag
collectCoverageFromGlobs of files to include in the coverage denominator
coverageDirectory ("coverage")Output directory for HTML / LCOV reports
coverageReportersFormats: text, lcov, json-summary, cobertura
coverageThresholdMinimum branch/function/line/statement %; fails CI on regression
SnapshotssnapshotResolverCustomize snapshot file location
snapshotSerializersPlug in serializers for React, Emotion, custom types
snapshotFormatPretty-format options (changed defaults in Jest 29)
ParallelismmaxWorkers ("50%")Concurrency cap — set to 2 in CI to avoid OOM
workerIdleMemoryLimitRestart workers above this RSS (e.g., "512MB")
testTimeout (5000)Default per-test timeout in ms
bail (0)Stop after N failures (1 is common in CI)
MiscverbosePrint every test name (slow on huge suites)
clearMocks / resetMocks / restoreMocksAuto-clear mock state between tests
presetInherit a ready-made config (ts-jest, jest-preset-angular)
projectsRun multiple project configs from one Jest invocation (monorepos)

Transform: babel-jest, @swc/jest, ts-jest

The transform option maps a file-path regex to a transformer module. Jest installs babel-jest by default if you have a Babel config; otherwise you pick one explicitly. The three serious choices in 2026 are babel-jest, @swc/jest, and ts-jest — picking the right one is usually the biggest config decision you make.

TransformerSpeedType-checksBest for
babel-jestSlow (JS-based)NoProjects with an existing Babel config
@swc/jestFast (Rust)NoTypeScript monorepos that need speed; pair with separate tsc --noEmit in CI
ts-jestSlowest (full tsc)YesLibraries where type errors must fail tests
esbuild-jestFastNoMostly superseded by @swc/jest; still works
// jest.config.js — @swc/jest, the most common modern choice
module.exports = {
  transform: {
    '^.+\\.(t|j)sx?$': ['@swc/jest', {
      jsc: {
        parser: { syntax: 'typescript', tsx: true, decorators: true },
        transform: { react: { runtime: 'automatic' } },
        target: 'es2022',
      },
    }],
  },
  transformIgnorePatterns: ['/node_modules/(?!(uuid|nanoid|@some-esm-lib)/)'],
}

If you keep babel-jest, configure it via the standard babel.config.js — Jest picks it up automatically. For TypeScript add @babel/preset-typescript. Remember Babel strips types only — for actual type-checking, add a separate tsc --noEmit step in CI.

Mocking and moduleNameMapper

moduleNameMapperrewrites module specifiers before resolution. Two main uses: matching your bundler's path aliases, and stubbing assets (CSS, SVGs, images) that Jest can't parse natively.

// jest.config.js
module.exports = {
  moduleNameMapper: {
    // Path alias matching tsconfig.json paths
    '^@/(.*)$': '<rootDir>/src/$1',
    '^~/(.*)$': '<rootDir>/$1',

    // Stub CSS / SCSS imports
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',

    // Stub image / SVG imports
    '\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/test/fileMock.js',

    // Force a specific package version (e.g., to deduplicate)
    '^react$': '<rootDir>/node_modules/react',
  },
}

Mocking the moduleNameMapper way is for entire modules. For function-level mocks use jest.mock() at the top of your test file. Manual mocks live in __mocks__ next to the file they replace — jest.mock('axios') will find __mocks__/axios.js automatically. Reset mock state between tests with clearMocks: true (clears call history) or resetMocks: true (also resets implementations) in the config.

// Example: stub a module in a single test
import { fetchUser } from './user'
jest.mock('./api', () => ({
  getUser: jest.fn().mockResolvedValue({ id: 1, name: 'Test' }),
}))

test('fetchUser returns the mocked user', async () => {
  const u = await fetchUser(1)
  expect(u.name).toBe('Test')
})

Coverage: thresholds, reporters, collectCoverageFrom

Jest delegates coverage collection to Istanbul (via babel-plugin-istanbul or v8 native coverage). Three options matter: what to collect, where to report, and what minimum to enforce.

// jest.config.js
module.exports = {
  collectCoverage: false, // run via CLI flag --coverage in CI

  // What goes in the denominator — include untested files too
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/__tests__/**',
    '!src/**/index.ts', // re-export barrels
  ],

  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'json-summary', 'cobertura'],
  coverageProvider: 'v8', // faster than 'babel' for native ESM

  coverageThreshold: {
    global: { branches: 80, functions: 80, lines: 80, statements: 80 },
    './src/auth/**/*.ts': { branches: 95, functions: 95, lines: 95, statements: 95 },
    './src/legacy/**/*.ts': { branches: 40, functions: 40, lines: 40, statements: 40 },
  },
}

Set collectCoverageFrom deliberately — without it, the report only counts files your tests imported, which makes untested modules invisible. With it, untested files show as 0% and the coverage number is honest.

coverageReporters common picks: text for local CLI, lcov for HTML browsing in coverage/lcov-report/index.html, json-summary for badge generators, cobertura for Jenkins / Azure DevOps.

Projects: monorepo support with multiple project configs in one runner

The projects option lets one Jest invocation execute multiple independent configs in a single worker pool, with combined coverage. Each project can have its own testEnvironment, transform,moduleNameMapper, and setupFiles — so you can run Node tests, jsdom tests, and ESLint-as-a-runner in one pass.

// jest.config.js at the monorepo root
module.exports = {
  projects: [
    // Glob: every package with its own jest.config.js
    '<rootDir>/packages/*',

    // Inline project config — node-side API tests
    {
      displayName: 'api',
      testEnvironment: 'node',
      testMatch: ['<rootDir>/apps/api/**/*.test.ts'],
      transform: { '^.+\\.tsx?$': '@swc/jest' },
    },

    // Inline project config — browser-side React tests
    {
      displayName: 'web',
      testEnvironment: 'jsdom',
      testMatch: ['<rootDir>/apps/web/**/*.test.tsx'],
      transform: { '^.+\\.tsx?$': '@swc/jest' },
      moduleNameMapper: { '\\.css$': 'identity-obj-proxy' },
    },

    // ESLint as a Jest runner — fails tests on lint errors
    {
      displayName: 'lint',
      runner: 'jest-runner-eslint',
      testMatch: ['<rootDir>/src/**/*.{ts,tsx}'],
    },
  ],
  // Top-level options that apply across all projects
  collectCoverageFrom: ['packages/*/src/**/*.ts', 'apps/*/src/**/*.{ts,tsx}'],
  coverageThreshold: { global: { branches: 75, functions: 75, lines: 75, statements: 75 } },
}

Caveats: --coverage emits a single merged report at the root, not one per project. Some options must be set per-project (rootDir, testPathIgnorePatterns), while options like watch, bail, and maxWorkers are top-level only. Nx and Turborepo wrap this pattern with caching but the underlying Jest config is identical.

Migrating to Vitest (where Jest config maps over)

Vitest was designed Jest-API-compatible, so most config options have a direct twin. If you are considering a move, here is how the main fields translate.

Jest optionVitest equivalentNotes
testEnvironmenttest.environmentSame values: node, jsdom, happy-dom
testMatchtest.includeGlob patterns; defaults are similar
testPathIgnorePatternstest.excludeGlobs, not regex
transformHandled by Vite pluginsYou usually don't configure it directly
moduleNameMapperresolve.aliasStandard Vite alias config
setupFilestest.setupFilesSame semantics
coverageThresholdtest.coverage.thresholdsSame shape (branches/functions/lines/statements)
collectCoverageFromtest.coverage.includePlus exclude array
projectstest.workspaceVitest workspaces serve the same purpose
jest.mock()vi.mock()Same call signature; runs at module level

For automated migration the official vitest-codemod rewrites jest globals to vi, and most config translates by hand in under an hour for typical projects. The two real porting risks: custom snapshot serializers (Vitest uses pretty-format like Jest, but plugin formats differ) and custom test runners (e.g., jest-runner-eslint has no direct Vitest equivalent — replace with a separate lint script).

Key terms

Jest
A JavaScript test framework maintained by the OpenJS Foundation (originally by Meta). Provides a test runner, assertion library (expect), mocking utilities (jest.mock, jest.fn), and snapshot testing in one package. Current major version is 29.
Test runner
The component that discovers test files, schedules them across worker processes, executes them, and aggregates results. Jest's default runner is jest-jasmine2-derived; alternative runners (jest-runner-eslint, jest-runner-tsc) repurpose the Jest infrastructure to run non-test tasks.
Transformer
A module Jest uses to compile source files before executing them. Maps source code (TypeScript, JSX, ES modules) to JavaScript Jest can run. Common transformers: babel-jest, @swc/jest, ts-jest, esbuild-jest.
moduleNameMapper
A Jest config object mapping regex patterns to replacement paths. Used to match bundler path aliases (e.g., @/componentssrc/components) and to stub non-JS imports like CSS or images with proxy modules during tests.
Coverage threshold
A minimum percentage of branches, functions, lines, or statements that must be covered by tests. Set under coverageThreshold.global (project-wide) or per file/glob. If any metric falls below the threshold, Jest exits non-zero — useful for failing CI on regressions.
projects
A Jest config option that runs multiple independent project configs from a single Jest invocation. Each project has its own testEnvironment, transforms, and setup files; results merge into one coverage report. Primary mechanism for monorepo test orchestration.

Frequently asked questions

Where should I put my Jest config?

Jest reads config from three places, in this priority order: a config file explicitly passed via --config, then a jest.config.js / jest.config.ts / jest.config.mjs / jest.config.cjs / jest.config.json file in the project root, then a "jest" key in package.json. For most projects use jest.config.js — it is the universally supported default and lets you compute values (path.resolve, process.env, conditional logic). Pick jest.config.ts if you want type-checking on the config itself and you already have ts-node installed; Jest 29+ loads it via ts-node automatically. Use the package.json "jest" field only for tiny projects or examples where you want a single config file. Avoid jest.config.json — it has no type safety, no comments, and no dynamic values, so it offers the worst of every world.

Why does Jest fail with "Cannot use import statement outside a module"?

This error means Jest tried to execute ES module syntax (import/export) without a transformer that converts it to CommonJS, OR you imported a dependency that ships ESM-only and Jest skipped transforming it because of transformIgnorePatterns. The default transformIgnorePatterns is ["/node_modules/", "\.pnp\.[^\/]+$"], which skips ALL node_modules — fine when packages were CommonJS, broken now that many ship pure ESM. Two fixes: (1) add a transformer like babel-jest or @swc/jest with appropriate presets to your transform config and your own source files will work; (2) override transformIgnorePatterns to NOT skip the offending ESM packages, e.g. transformIgnorePatterns: ["/node_modules/(?!(uuid|nanoid|chalk)/)"]. Alternatively run Jest in native ESM mode with NODE_OPTIONS=--experimental-vm-modules and extensionsToTreatAsEsm, but that path is still flagged experimental in Jest 29.

What's the difference between babel-jest, @swc/jest, and ts-jest?

babel-jest is the default transformer Jest installs automatically — it uses your project Babel config (.babelrc or babel.config.js) to compile JS, JSX, and TS (if you configure @babel/preset-typescript). It is universally compatible but the slowest of the three because Babel is JS-based. @swc/jest uses the Rust-based SWC compiler — typically 5–10x faster than babel-jest on a cold run and 2–3x faster on incremental runs; it handles TypeScript natively without ts-node. The tradeoff: SWC strips types but does NOT typecheck, so you still need tsc --noEmit in CI. ts-jest is purpose-built for TypeScript — it runs the real TS compiler so you get full type-checking inside Jest (catches type errors as test failures), at the cost of being the slowest option, often 2x slower than babel-jest. Pick @swc/jest for speed on monorepos, ts-jest when type errors must fail tests, babel-jest when you already have a Babel config.

How do I set a coverage threshold to fail CI on regression?

Add a coverageThreshold object to jest.config.js. The "global" key sets project-wide minimums; per-file or per-glob keys set stricter thresholds for critical paths. Example: coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80 }, "./src/auth/**/*.ts": { branches: 95, functions: 95, lines: 95, statements: 95 } }. Then run jest --coverage in CI — if any metric falls below the threshold, Jest exits with a non-zero code and the CI step fails. Pair with collectCoverageFrom to include untested files in the denominator (otherwise you can hit 100% coverage by writing zero tests for files you have never imported). Common collectCoverageFrom: ["src/**/*.{ts,tsx}", "!src/**/*.d.ts", "!src/**/__tests__/**"]. Use negative thresholds (e.g. -10) to allow up to N uncovered lines instead of a percentage when you have many tiny utility files.

How do I run Jest in a monorepo with shared config?

Use the "projects" field at the root jest.config.js. Each entry is either a glob pointing at packages with their own jest.config.js, or an inline object that becomes a project-scoped config. Example: projects: ["<rootDir>/packages/*", { displayName: "lint", runner: "jest-runner-eslint", testMatch: ["<rootDir>/src/**/*.ts"] }]. Running jest at the root executes every project in a single worker pool, prints a combined coverage report, and lets each package have its own testEnvironment, transform, and moduleNameMapper. Use displayName per project to label output (e.g., "api", "web", "shared"). Caveat: --coverage with projects emits one merged report at the root; some options (rootDir, testPathIgnorePatterns) must be set per-project, while others (watch, bail) are top-level only. Tools like Nx and Turborepo wrap this pattern with their own caching layer but the underlying Jest config is the same.

Should I use Jest or Vitest in 2026?

Honest take: for new projects in 2026, Vitest is the safer default. It runs ES modules natively (no transformIgnorePatterns gymnastics), starts cold in ~1 second on a typical project, uses Vite's transformer so config aligns with your dev server, and the API is intentionally Jest-compatible — describe/it/expect/vi.mock all work. Jest is still the right choice when: (a) your project already has 500+ Jest tests and migration is not free; (b) you need a feature that has no Vitest equivalent (snapshot serializers from a specific Jest plugin, certain custom test runners); (c) you are inside React Native, where Jest is the framework-blessed runner. For everything else — new Node libraries, new Next.js / Vite / SvelteKit apps — start with Vitest. The "jest is the industry standard" argument was true through ~2023 and is no longer a reason to default to Jest in green-field code.

Can jest.config.json have comments?

No — jest.config.json is parsed as strict JSON (RFC 8259), so // and /* */ are not allowed. Any comment line breaks the parser and Jest exits with a parse error before running tests. This is the same restriction that applies to package.json. If you want comments, use jest.config.js or jest.config.ts instead — both are evaluated as code, so // and /* */ work, plus you get to import path, define helpers, and read environment variables. If you must stay on a JSON file (e.g., a tool reads it), the workaround is the "//" key pattern: {"//": "transformer chosen for SWC compatibility", "preset": "@swc/jest"} — keys named "//" are valid JSON and Jest ignores them. See our JSON Comments guide for a longer treatment of comment workarounds across config formats.

How do I exclude files from Jest coverage?

Two options that work together. First, collectCoverageFrom takes positive globs (what to include) and negated globs prefixed with "!" (what to exclude): collectCoverageFrom: ["src/**/*.{ts,tsx}", "!src/**/*.d.ts", "!src/**/__tests__/**", "!src/types.ts"]. Second, coveragePathIgnorePatterns is an array of regex strings applied AFTER collection — anything matched is dropped from the report: coveragePathIgnorePatterns: ["/node_modules/", "/test-utils/", "\.config\.[jt]s$"]. Use collectCoverageFrom to define your codebase scope, then coveragePathIgnorePatterns for surgical removals of generated files, mocks, or platform-specific shims. Both can be combined in projects configs. Inline ignore for specific blocks of code: add /* istanbul ignore next */ above the statement (Jest uses Istanbul under the hood for coverage), or /* istanbul ignore file */ at the top of a file to skip the whole file.

Further reading and primary sources