Skip to main content
Version 1.0 is a major update that moves to ESLint 9’s flat config format and unbundles third-party rules to give you more control over your linting setup. The flat config files don’t allow nesting configuration files in subdirectories — instead, all nesting must be in one configuration file. If you already have an ESLint configuration in your project, review the table below to see what’s changed:
Before (v0.x)After (v1.0)
ESLint 8ESLint 9+ required
Legacy config (.eslintrc.*)Flat config (eslint.config.js)
Bundled eslint:recommendedInstall separately via @eslint/js
Bundled eslint-plugin-react recommended rulesInstall and configure separately
Bundled eslint-plugin-react-hooksInstall and configure separately
Bundled eslint-config-prettierInstall and configure separately
CommonJS (require())ESM (import)
After reviewing the above changes, proceed with the steps below to migrate to the latest version.

Migrate your ESLint config

1. Review your current config

Before migrating, open your existing .eslintrc.* file and note:
  • Any custom rules you’ve added or modified
  • Any additional plugins you’re using
  • Any overrides for specific file patterns
  • Any env or globals settings
You’ll need to translate these to flat config format. Keep your old config file for reference until migration is complete.

2. Update dependencies

Upgrade to ESLint 9 and the new package version by running the following command:
npm i --save-dev eslint@^9 @hubspot/eslint-config-ui-extensions@^1

3. Set up ESM

ES modules is required for a flat configuration, so you’ll need to ensure your package.json includes "type": "module":
{
  "type": "module"
}

4. Create a new ESLint config file

Create an eslint.config.js file in your project root. Start with either the minimal or recommended setup below, then add your custom rules. Read more about each addition in the Recommended setup section of the overview. Minimal setup (HubSpot UI extensions rules only):
import { config } from '@hubspot/eslint-config-ui-extensions';

export default [
  ...config,
  // Add your custom rules here (see Step 5)
];
Recommended setup (replaces previously bundled configs, plus extras):
npm i --save-dev @eslint/js typescript-eslint eslint-plugin-react eslint-plugin-react-hooks eslint-config-prettier eslint-plugin-unused-imports
import { defineConfig } from 'eslint/config';
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import unusedImports from 'eslint-plugin-unused-imports';
import prettier from 'eslint-config-prettier';
import { config as uiExtensionsConfig } from '@hubspot/eslint-config-ui-extensions';

export default defineConfig([
  js.configs.recommended,
  tseslint.configs.recommended,
  tseslint.configs.stylistic,
  react.configs.flat.recommended,
  react.configs.flat['jsx-runtime'],
  {
    settings: {
      react: { version: 'detect' },
    },
  },
  ...uiExtensionsConfig,
  reactHooks.configs['recommended-latest'],
  {
    plugins: {
      'unused-imports': unusedImports,
    },
    rules: {
      'no-unused-vars': 'off',
      '@typescript-eslint/no-unused-vars': 'off',
      'unused-imports/no-unused-imports': 'error',
      'unused-imports/no-unused-vars': [
        'warn',
        {
          vars: 'all',
          varsIgnorePattern: '^_',
          args: 'after-used',
          argsIgnorePattern: '^_',
        },
      ],
    },
  },
  prettier,
  // Add your custom rules here (see Step 5)
]);

5. Migrate your custom configuration

Refer to your old config file and translate your customizations to flat config format. Custom rules:
// Before (.eslintrc.js)
module.exports = {
  rules: {
    'no-console': 'warn',
    'prefer-const': 'error',
  },
};

// After (eslint.config.js) - add to your config array
{
  rules: {
    'no-console': 'warn',
    'prefer-const': 'error',
  },
}
Overrides (file-specific rules):
// Before (.eslintrc.js)
module.exports = {
  overrides: [
    {
      files: ['*.test.js'],
      rules: {
        'no-console': 'off',
      },
    },
  ],
};

// After (eslint.config.js) - add to your config array
{
  files: ['**/*.test.js'],
  rules: {
    'no-console': 'off',
  },
}
Globals:
// Before (.eslintrc.js)
module.exports = {
  globals: {
    myGlobal: 'readonly',
  },
};

// After (eslint.config.js) - add to your config array
{
  languageOptions: {
    globals: {
      myGlobal: 'readonly',
    },
  },
}
Environments: Flat config uses globals instead of env.
// Before (.eslintrc.js)
module.exports = {
  env: {
    browser: true,
    node: true,
  },
};

// After (eslint.config.js)
import globals from 'globals';

export default [
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
      },
    },
  },
  // ... rest of config
];
For more details, see the official ESLint migration guide.

6. Testing and cleanup

Run ESLint to verify your migration:
npm run lint
Once you’ve verified that everything works, you can delete your old .eslintrc.* file.

Troubleshooting

ESLint seems to ignore your new config

ESLint 9 looks for eslint.config.js by default and ignores .eslintrc.* files. If it seems like ESLint is ignoring your new config, make sure:
  1. You created eslint.config.js in your project root.
  2. The file is correctly exporting an array (check for syntax errors).

Error: Cannot use require() to import an ES module

Your config file is being loaded as CommonJS. Make sure:
  1. Your package.json has "type": "module".
  2. Your config file is named eslint.config.js (not .cjs).

Errors about missing plugins or rules

If you see errors like Definition for rule 'react/prop-types' was not found, you’re using a rule from a plugin that was previously bundled but is now opt-in. Either:
  1. Install the plugin and add it to your config (see recommended setup).
  2. Remove the rule if you no longer need it.

Error: Invalid option ‘extends’

Flat config uses a different structure than legacy config. You can’t use extends, env, plugins (as an array), or overrides the same way. See Step 5 above for translation examples.
Last modified on February 19, 2026