TL;DR
unimport is the UnJS auto-import engine — scans for available exports and generates import statements automatically, powers Nuxt's auto-imports (composables, utils, components). unplugin-auto-import is the universal build plugin — uses unplugin to work with Vite, Webpack, Rollup, and esbuild, auto-imports from libraries like Vue, React, and custom directories. babel-plugin-auto-import is the Babel transform — resolves missing identifiers at compile time, works with any Babel-based setup. In 2026: unplugin-auto-import for Vite/Webpack projects, unimport for framework authors building Nuxt-like DX, babel-plugin-auto-import for Babel-only setups.
Key Takeaways
- unimport: ~5M weekly downloads — UnJS, powers Nuxt auto-imports, generates TypeScript declarations
- unplugin-auto-import: ~3M weekly downloads — universal plugin (Vite/Webpack/Rollup), preset-based
- babel-plugin-auto-import: ~50K weekly downloads — Babel transform, simple identifier mapping
- Auto-importing eliminates repetitive
import { ref, computed } from "vue"boilerplate - unimport and unplugin-auto-import generate
.d.tsfiles for TypeScript support - unplugin-auto-import has presets for Vue, React, Svelte, VueUse, and more
The Problem
// Without auto-imports — every file starts with boilerplate:
import { ref, computed, watch, onMounted } from "vue"
import { useRouter, useRoute } from "vue-router"
import { storeToRefs } from "pinia"
import { useUserStore } from "@/stores/user"
import { formatDate } from "@/utils/date"
import { debounce } from "@/utils/helpers"
// With auto-imports — just use them:
const count = ref(0)
const doubled = computed(() => count.value * 2)
const router = useRouter()
const { user } = storeToRefs(useUserStore())
const formatted = formatDate(new Date())
unimport
unimport — the auto-import engine:
Basic usage
import { createUnimport } from "unimport"
const { injectImports } = createUnimport({
// Import from packages:
imports: [
{ name: "ref", from: "vue" },
{ name: "computed", from: "vue" },
{ name: "watch", from: "vue" },
{ name: "useRouter", from: "vue-router" },
],
})
// Transform source code — adds missing imports:
const input = `
const count = ref(0)
const doubled = computed(() => count.value * 2)
`
const { code } = await injectImports(input)
// Output:
// import { ref, computed } from "vue"
// const count = ref(0)
// const doubled = computed(() => count.value * 2)
Presets
import { createUnimport } from "unimport"
import { builtinPresets } from "unimport"
const { injectImports } = createUnimport({
presets: [
// Built-in presets for popular frameworks:
"vue", // ref, computed, watch, onMounted, etc.
"vue-router", // useRouter, useRoute, etc.
"pinia", // defineStore, storeToRefs, etc.
"react", // useState, useEffect, useCallback, etc.
"svelte", // onMount, onDestroy, etc.
],
})
Directory scanning
import { createUnimport } from "unimport"
const { injectImports } = createUnimport({
// Auto-import from local directories:
dirs: [
"./src/composables", // useAuth, useTheme, etc.
"./src/utils", // formatDate, debounce, etc.
"./src/stores", // useUserStore, useCartStore, etc.
],
// Options for directory scanning:
dirsScanOptions: {
filePatterns: ["*.ts", "*.js"],
fileFilter: (file) => !file.includes(".test."),
},
})
TypeScript declaration generation
import { createUnimport } from "unimport"
const unimport = createUnimport({
imports: [
{ name: "ref", from: "vue" },
{ name: "computed", from: "vue" },
],
dirs: ["./src/composables"],
})
// Generate .d.ts for TypeScript support:
const dts = await unimport.generateTypeDeclarations()
// Writes: declare global { const ref: typeof import("vue")["ref"] }
// Generate ESLint globals config:
const eslintConfig = await unimport.generateESLintFlatConfig()
How Nuxt uses unimport
// Nuxt's auto-imports are powered by unimport:
// nuxt.config.ts — these are auto-imported without manual imports:
// useAsyncData, useFetch, useHead, useRoute, useRouter
// ref, computed, watch, onMounted
// definePageMeta, navigateTo, useState
// In your Nuxt component — no imports needed:
const { data } = await useFetch("/api/packages")
const route = useRoute()
const count = ref(0)
// Nuxt configures unimport with:
// - Vue composables (ref, computed, etc.)
// - Nuxt composables (useFetch, useState, etc.)
// - Auto-scanned composables/ directory
// - TypeScript declarations generated automatically
unplugin-auto-import
unplugin-auto-import — universal auto-import plugin:
Vite setup
// vite.config.ts
import AutoImport from "unplugin-auto-import/vite"
export default defineConfig({
plugins: [
AutoImport({
// Presets for popular libraries:
imports: [
"vue",
"vue-router",
"pinia",
"@vueuse/core",
],
// Auto-import from directories:
dirs: [
"./src/composables",
"./src/stores",
],
// Generate TypeScript declarations:
dts: "./auto-imports.d.ts",
// Generate ESLint config:
eslintrc: {
enabled: true,
},
}),
],
})
Webpack setup
// webpack.config.js
const AutoImport = require("unplugin-auto-import/webpack")
module.exports = {
plugins: [
AutoImport({
imports: ["react", "react-router-dom"],
dts: "./auto-imports.d.ts",
}),
],
}
React preset
// vite.config.ts
import AutoImport from "unplugin-auto-import/vite"
export default defineConfig({
plugins: [
AutoImport({
imports: [
"react",
// Custom imports:
{
"react-router-dom": [
"useNavigate",
"useParams",
"useSearchParams",
"Link",
],
"@tanstack/react-query": [
"useQuery",
"useMutation",
"useQueryClient",
],
},
],
dts: true,
}),
],
})
// Now in any component — no imports needed:
function PackageList() {
const [search, setSearch] = useState("")
const navigate = useNavigate()
const { data } = useQuery({
queryKey: ["packages", search],
queryFn: () => fetchPackages(search),
})
// useState, useNavigate, useQuery all auto-imported
}
Custom resolvers
import AutoImport from "unplugin-auto-import/vite"
export default defineConfig({
plugins: [
AutoImport({
imports: ["vue"],
// Custom resolvers for complex cases:
resolvers: [
// Auto-import from a UI library:
(name) => {
if (name.startsWith("El"))
return { name, from: "element-plus" }
},
],
// Ignore specific auto-imports:
ignore: ["useFetch"],
// Transform options:
include: [
/\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
/\.vue$/,
/\.vue\?vue/,
],
}),
],
})
VueUse preset
import AutoImport from "unplugin-auto-import/vite"
export default defineConfig({
plugins: [
AutoImport({
imports: [
"vue",
"@vueuse/core",
"@vueuse/head",
],
dts: true,
}),
],
})
// All VueUse composables available without imports:
const { x, y } = useMouse()
const isDark = useDark()
const { copy } = useClipboard()
const { isFullscreen, toggle } = useFullscreen()
babel-plugin-auto-import
babel-plugin-auto-import — Babel-based auto-import:
Basic setup
// babel.config.json
{
"plugins": [
["auto-import", {
"declarations": [
{ "default": "React", "path": "react" },
{ "members": ["useState", "useEffect", "useCallback"], "path": "react" },
{ "members": ["useNavigate", "Link"], "path": "react-router-dom" }
]
}]
]
}
Usage
// Input (no imports):
function App() {
const [count, setCount] = useState(0)
const navigate = useNavigate()
return <div onClick={() => setCount(count + 1)}>{count}</div>
}
// Babel transforms to:
import React, { useState } from "react"
import { useNavigate } from "react-router-dom"
function App() {
const [count, setCount] = useState(0)
const navigate = useNavigate()
return <div onClick={() => setCount(count + 1)}>{count}</div>
}
Limitations
babel-plugin-auto-import:
✅ Works with any Babel setup
✅ Simple configuration
✅ No build tool dependency
❌ No directory scanning
❌ No TypeScript declarations
❌ No presets — must list every import manually
❌ No ESLint integration
❌ Babel-only — doesn't work with esbuild/SWC
❌ Not actively maintained
For most projects: use unplugin-auto-import instead
Feature Comparison
| Feature | unimport | unplugin-auto-import | babel-plugin-auto-import |
|---|---|---|---|
| Build tool support | Programmatic | Vite, Webpack, Rollup, esbuild | Babel only |
| Presets | Vue, React, Svelte, etc. | Vue, React, Svelte, VueUse, etc. | ❌ (manual) |
| Directory scanning | ✅ | ✅ | ❌ |
| TypeScript .d.ts | ✅ | ✅ | ❌ |
| ESLint config | ✅ | ✅ | ❌ |
| Custom resolvers | ✅ | ✅ | ❌ |
| Framework use | Nuxt core | Plugin for any project | Babel projects |
| API level | Low-level engine | Build plugin | Babel transform |
| Weekly downloads | ~5M | ~3M | ~50K |
When to Use Each
Use unimport if:
- Building a framework with Nuxt-like auto-import DX
- Need a programmatic auto-import engine
- Want full control over import injection and scanning
- Building custom tooling that needs import resolution
Use unplugin-auto-import if:
- Want auto-imports in your Vite, Webpack, or Rollup project
- Need presets for Vue, React, VueUse, etc.
- Want TypeScript declarations and ESLint config generated
- Building an app (not a framework)
Use babel-plugin-auto-import if:
- Stuck with a Babel-only build pipeline
- Need simple identifier-to-import mapping
- Can't use build plugins (legacy setup)
TypeScript and ESLint Integration in Practice
The .d.ts generation that both unimport and unplugin-auto-import provide is not merely a convenience — it is a prerequisite for a correct TypeScript workflow. Without it, TypeScript reports "identifier not found" errors for every auto-imported symbol, breaking type checking even though the build succeeds. The generated declaration file adds declare global entries that tell TypeScript these identifiers exist without requiring a manual import at the top of every file.
unplugin-auto-import writes the declaration file to the path specified in the dts option on every dev server start and on each subsequent change to the dirs configuration. This means the .d.ts file must be committed to your repository (or regenerated in CI before tsc runs). Teams that .gitignore the generated declaration file will see TypeScript errors in CI even though local development works — local dev automatically regenerates the file, but CI starts fresh.
The ESLint flat config generation (eslintrc: { enabled: true }) addresses a different problem: ESLint's no-undef rule flags auto-imported identifiers as undefined references because ESLint does not run the same transform pipeline as Vite. The generated ESLint config file adds each auto-imported identifier to the globals object. In ESLint v9 flat config format, you import this file directly in eslint.config.mjs; in legacy .eslintrc format, you extend it by path. Without this step, ESLint produces false positives that clutter the editor and break lint-on-commit hooks.
Migration from Explicit Imports to Auto-Imports
Adopting auto-imports in an existing codebase requires a methodical approach to avoid breaking changes. The safest migration path is additive: configure unplugin-auto-import with the desired presets, generate the .d.ts file, and then incrementally remove explicit imports from files as you touch them. The build will succeed throughout the migration because auto-imports are injected at build time regardless of whether the source file has explicit imports or not — duplicate imports are deduplicated by the module bundler.
The ignore list in unplugin-auto-import is essential when a project has naming conflicts. If you have a local useQuery composable in src/composables/ and also want to auto-import useQuery from @tanstack/react-query, the last registration wins by default. The ignore option lets you exclude specific names from auto-import resolution, forcing those to remain explicit imports and making the resolution unambiguous.
For unimport specifically, the addons API allows framework authors to write custom scanning logic beyond directory scanning. A Nuxt module that auto-imports from a dynamic plugin registry uses this API to register import entries programmatically after the plugin list is resolved. This composability is why Nuxt itself uses unimport as its engine rather than unplugin-auto-import — the lower-level API supports the dynamic, configuration-driven import topology that Nuxt requires, while unplugin-auto-import optimizes for the common case of static preset-based configuration.
One operational consideration: auto-imports make tree-shaking less predictable. When imports are explicit, bundlers can trace exactly which exports from a given module are used across the project. With auto-imports, the injected imports are added at the file level just before bundling, so tree-shaking still functions correctly — but debugging "why is this symbol included in my bundle?" becomes harder because there is no source file that contains a visible import statement. Using rollup-plugin-visualizer or Vite's built-in --reporter alongside auto-imports helps maintain visibility into bundle composition.
Security Implications of Auto-Import Transforms
Auto-import transforms have a subtle security consideration that deserves attention in security-conscious codebases. When a symbol is auto-imported, the import resolution is handled by the build tool rather than visible at the source level. This means code review tooling, static analysis, and security audits must account for the auto-import configuration to understand what a given file actually imports. A developer reviewing code that uses ref() without an import statement must know that ref is auto-imported from Vue, not defined locally — context that is implicit rather than explicit.
For security audits and dependency reviews, this means the auto-import configuration files (the imports array in unplugin-auto-import, or unimport's module scanning configuration) are effectively part of the module surface area. Changes to the auto-import configuration can introduce new dependencies into files without any visible change to those files. Teams conducting security reviews should include the auto-import configuration in scope and treat changes to it with the same scrutiny as changes to explicit import statements.
The dependency footprint of auto-import presets also deserves scrutiny. unplugin-auto-import's built-in presets for Vue, React hooks, VueUse, and Pinia add those packages to the auto-import resolution graph. If a preset includes a composable or hook from a package that introduces a transitive vulnerability, the auto-import configuration is the entry point — not an import statement in your source code. Running npm audit remains essential, but understanding the auto-import surface area helps audit findings map correctly to affected code. The imports array in unplugin-auto-import can be inspected programmatically to generate an exhaustive list of auto-imported symbols and their sources for compliance documentation.
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on unimport v3.x, unplugin-auto-import v0.18.x, and babel-plugin-auto-import v1.x.
Compare auto-import tools and developer utilities on PkgPulse →
In 2026, auto-import tooling is standard in Nuxt and Vue projects (where it is built-in) and increasingly common in Vite-based React projects. unimport is the foundational library used by Nuxt's auto-import system. unplugin-auto-import is the practical choice for adding auto-imports to any Vite, Rollup, or webpack project with minimal configuration. babel-plugin-auto-import is a fallback for projects that cannot use Vite or unplugin — it handles the transform step but requires more manual configuration. For React projects in 2026, the question is usually whether auto-imports improve DX enough to justify the magic — most React teams still prefer explicit imports for clarity.
See also: cac vs meow vs arg 2026 and cosmiconfig vs lilconfig vs conf, archiver vs adm-zip vs JSZip (2026).