
Nuxt Modules
- 2k installs
- 696 repo stars
- Updated July 27, 2026
- onmax/nuxt-skills
nuxt-modules is an agent skill that teaches developers to create, test, publish, and CI-automate Nuxt modules using defineNuxtModule, Kit utilities, and runtime/server extensions.
About
The nuxt-modules skill guides developers through creating Nuxt modules that extend framework functionality across published npm packages, local modules directories, and inline config hooks. It covers defineNuxtModule anatomy, Nuxt Kit utilities, runtime injection of components, composables, plugins, and server routes, plus playground and fixture-based E2E testing. Reference files split guidance into development patterns, testing and npm publishing best practices, and copy-paste CI/CD workflow templates. The skill emphasizes loading only the reference file relevant to the current task rather than reading everything at once. Module types include published @nuxtjs/ or nuxt- packages, local modules/ extensions, and inline nuxt.config.ts hooks. Quick start uses nuxi init -t module with dev, dev:build, and test scripts. Related skills nuxt and vue cover framework basics and runtime patterns. Use when creating Nuxt modules, publishing to npm, setting up module CI/CD, or extending Nuxt with components, composables, plugins, API routes, or middleware.
- Covers published npm modules, local modules/ extensions, and inline nuxt.config.ts hooks.
- defineNuxtModule patterns with Nuxt Kit utilities, hooks, and runtime/server injection.
- Progressive reference loading: development, testing/publishing, and CI workflow files.
- E2E testing with playground and test/fixtures plus npm release automation guidance.
- Quick start via nuxi init -t module with dev, dev:build, and test scripts.
Nuxt Modules by the numbers
- 1,962 all-time installs (skills.sh)
- +40 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #246 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
nuxt-modules capabilities & compatibility
- Capabilities
- definenuxtmodule scaffolding · nuxt kit utilities and hooks · runtime and server extension patterns · e2e testing with playground fixtures · ci/cd workflow templates for modules
- Use cases
- frontend · api development · ci cd
What nuxt-modules says it does
Guide for creating Nuxt modules that extend framework functionality.
npx skills add https://github.com/onmax/nuxt-skills --skill nuxt-modulesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2k |
|---|---|
| repo stars | ★ 696 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | onmax/nuxt-skills ↗ |
How do I scaffold, test, and publish a Nuxt module with proper defineNuxtModule structure, Kit hooks, and npm release workflows?
Create published npm Nuxt modules, local project modules, runtime extensions, and server extensions with defineNuxtModule patterns, Kit utilities, hooks, E2E tests, and release automation.
Who is it for?
Developers building published @nuxtjs/ packages or local Nuxt extensions with components, composables, plugins, or server routes.
Skip if: Skip for general Nuxt app development without module authoring (use nuxt) or Vue runtime patterns alone (use vue).
When should I use this skill?
Use when creating Nuxt modules, publishing npm modules, setting up module CI/CD, or extending Nuxt with runtime or server features.
What you get
A working module scaffold with playground E2E tests, selective reference guidance, and CI templates ready for npm publishing.
- ci.yml workflow
- pkg.yml preview workflow
- release.yml publish workflow
By the numbers
- Bundles 3 GitHub Actions workflow templates: ci.yml, pkg.yml, release.yml
- Targets Node 22 with pnpm on ubuntu-latest runners
Files
Nuxt Module Development
Guide for creating Nuxt modules that extend framework functionality.
Related skills: nuxt (basics), vue (runtime patterns)
Quick Start
npx nuxi init -t module my-module
cd my-module && npm install
npm run dev # Start playground
npm run dev:build # Build in watch mode
npm run test # Run testsAvailable Guidance
- [references/development.md](references/development.md) - Module anatomy, defineNuxtModule, Kit utilities, hooks
- [references/testing-and-publishing.md](references/testing-and-publishing.md) - E2E testing, best practices, releasing, publishing
- [references/ci-workflows.md](references/ci-workflows.md) - Copy-paste CI/CD workflow templates
Loading Files
Consider loading these reference files based on your task:
- [ ] references/development.md - if building module features, using defineNuxtModule, or working with Kit utilities
- [ ] references/testing-and-publishing.md - if writing E2E tests, publishing to npm, or following best practices
- [ ] references/ci-workflows.md - if setting up CI/CD workflows for your module
DO NOT load all files at once. Load only what's relevant to your current task.
Module Types
| Type | Location | Use Case |
|---|---|---|
| Published | npm package | @nuxtjs/, nuxt- distribution |
| Local | modules/ dir | Project-specific extensions |
| Inline | nuxt.config.ts | Simple one-off hooks |
Project Structure
my-module/
├── src/
│ ├── module.ts # Entry point
│ └── runtime/ # Injected into user's app
│ ├── components/
│ ├── composables/
│ ├── plugins/
│ └── server/
├── playground/ # Dev testing
└── test/fixtures/ # E2E testsResources
CI Workflow Templates
Copy-paste templates for GitHub Actions.
Contents
- ci.yml - Lint, typecheck, test
- pkg.yml - Preview packages via pkg-pr-new
- release.yml - npm publish + GitHub release
- npm Trusted Publishing Setup
---
ci.yml
Runs lint, typecheck, and tests on every push/PR/tag.
name: ci
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install
- run: pnpm dev:prepare
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm testpkg.yml
Publishes preview packages for every PR via pkg-pr-new.
name: pkg.new
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
pkg:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install
- run: pnpm dev:prepare
- run: pnpm prepack
- run: pnpm dlx pkg-pr-new publishrelease.yml
Triggered by tag push. Waits for CI, then publishes to npm via OIDC + creates GitHub release.
name: release
permissions:
id-token: write
contents: write
actions: read
on:
push:
tags:
- 'v*'
jobs:
wait-for-ci:
runs-on: ubuntu-latest
steps:
- name: Wait for CI to complete
uses: lewagon/wait-on-check-action@v1.3.4
with:
ref: ${{ github.sha }}
check-name: ci
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
release:
needs: wait-for-ci
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
registry-url: 'https://registry.npmjs.org'
- run: pnpm install
- run: pnpm dev:prepare
- run: pnpm prepack
- name: GitHub Release
run: pnpm dlx changelogithub
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Publish to npm
run: npm publish --provenance --access publicnpm Trusted Publishing Setup (OIDC)
Preferred method - No NPM_TOKEN secret needed. Uses OIDC for secure, tokenless authentication.
See also: ts-library/ci-workflows.md for general TypeScript library CI patterns.
Requirements
1. Node.js 24+ (npm 11.5.1+ required for OIDC - Node 22 has npm 10.x which fails) 2. Workflow permissions: id-token: write 3. Publish command: must include --provenance flag 4. package.json: must have repository field for provenance verification 5. npm 2FA setting: "Require 2FA or granular access token" (first option, allows tokens)
package.json Requirements
Each package must have a repository field or provenance verification fails:
{
"name": "my-package",
"repository": { "type": "git", "url": "git+https://github.com/org/repo.git" }
}Setup Steps
1. Open package settings: https://www.npmjs.com/package/<PACKAGE_NAME>/access 2. Scroll to "Publishing access" section 3. Click "Add GitHub Actions" under Trusted Publishers 4. Fill in the form:
- Owner:
<github-org-or-username> - Repository:
<repo-name> - Workflow file:
release.yml - Environment: _(leave empty)_
5. Click "Add"
Repeat for each package in your monorepo.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| "Access token expired or revoked" E404 | npm version too old | Use Node.js 24 (npm 11.5.1+) |
| ENEEDAUTH | Missing registry-url | Add registry-url: 'https://registry.npmjs.org' to setup-node |
| "repository.url is empty" E422 | Missing repository field | Add repository to package.json |
| "npm/xyz not configured as trusted publisher" | Mismatch in config | Check owner, repo, workflow filename match exactly |
Verify Setup
The workflow uses OIDC when:
id-token: writepermission is set--provenanceflag is used- No
NODE_AUTH_TOKENenv var is set
npm automatically detects GitHub Actions and authenticates via OIDC.
Module Development
Module anatomy, Kit utilities, and common patterns.
defineNuxtModule
import { addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
export interface ModuleOptions {
apiKey?: string
prefix?: string
}
export default defineNuxtModule<ModuleOptions>({
meta: {
name: '@nuxtjs/example',
configKey: 'example',
compatibility: { nuxt: '>=3.0.0' }
},
defaults: {
apiKey: '',
prefix: 'My'
},
hooks: {
'app:error': err => console.error(err)
},
moduleDependencies: {
'@nuxtjs/tailwindcss': {
version: '>=6.0.0',
optional: true,
// Override nuxt.options for this module
overrides: {},
// Set defaults (lower priority than nuxt.options)
defaults: {}
}
},
// Or as async function (Nuxt 4.3+)
async moduleDependencies(nuxt) {
const needsSupport = nuxt.options.runtimeConfig.public?.feature
return {
'@nuxtjs/tailwindcss': needsSupport ? {} : { optional: true }
}
},
setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)
addPlugin(resolve('./runtime/plugin'))
}
})User configures via configKey:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/example'],
example: { apiKey: 'xxx' }
})Critical: #imports in Published Modules
Auto-imports don't work in node_modules. Runtime files must explicitly import:
// src/runtime/composables/useMyFeature.ts
// Wrong - won't work in published module
// Right - explicit import
import { useRoute } from '#imports'
const route = useRoute()
const route = useRoute()Adding Plugins
import { addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)
addPlugin(resolve('./runtime/plugin'))
}
})// src/runtime/plugin.ts
import { defineNuxtPlugin } from '#imports'
export default defineNuxtPlugin((nuxtApp) => {
return {
provide: { myHelper: (msg: string) => console.log(msg) }
}
})Async plugins (Nuxt 4.3+): Lazy-load build plugins:
import { addVitePlugin, addWebpackPlugin } from '@nuxt/kit'
export default defineNuxtModule({
async setup() {
// Lazy-load only the bundler plugin needed
addVitePlugin(() => import('my-plugin/vite').then(r => r.default()))
addWebpackPlugin(() => import('my-plugin/webpack').then(r => r.default()))
}
})Adding Components
import { addComponent, addComponentsDir, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)
// Single component
addComponent({
name: 'MyButton',
filePath: resolve('./runtime/components/MyButton.vue'),
// Custom declaration path (Nuxt 4.2+)
declarationPath: resolve('./runtime/types/components.d.ts')
})
// Or entire directory with prefix
addComponentsDir({
path: resolve('./runtime/components'),
prefix: 'My' // <MyButton>, <MyCard>
})
}
})Adding Composables
import { addImports, addImportsDir, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)
// Single or multiple
addImports([
{ name: 'useAuth', from: resolve('./runtime/composables/useAuth') },
{ name: 'useUser', from: resolve('./runtime/composables/useUser') }
])
// Or entire directory
addImportsDir(resolve('./runtime/composables'))
}
})Adding Server Routes
import { addServerHandler, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)
addServerHandler({
route: '/api/_my-module/status',
handler: resolve('./runtime/server/api/status.get')
})
}
})Always prefix routes: /api/_my-module/ avoids conflicts.
Server imports (Nuxt 4.3+): Use #server alias in server files:
// runtime/server/api/users.ts
import { helper } from '#server/utils/helper' // Clean importsRuntime Config
export default defineNuxtModule({
setup(options, nuxt) {
// Public (client + server)
nuxt.options.runtimeConfig.public.myModule = { apiUrl: options.apiUrl }
// Private (server only)
nuxt.options.runtimeConfig.myModule = { apiKey: options.apiKey }
}
})Lifecycle Hooks
export default defineNuxtModule({
hooks: {
'pages:extend': (pages) => {
pages.push({ name: 'custom', path: '/custom', file: resolve('./runtime/pages/custom.vue') })
}
},
setup(options, nuxt) {
nuxt.hook('nitro:config', (nitroConfig) => {
nitroConfig.prerender ||= {}
nitroConfig.prerender.routes ||= []
nitroConfig.prerender.routes.push('/my-route')
})
// Cleanup on close
nuxt.hook('close', async () => {
await cleanup()
})
}
})| Hook | When |
|---|---|
ready | Nuxt initialized |
modules:done | All modules loaded |
pages:extend | Modify pages array |
nitro:config | Configure Nitro |
close | Nuxt shutting down |
Custom Hooks
export interface ModuleHooks {
'my-module:init': (config: MyConfig) => void
}
declare module '#app' {
interface RuntimeNuxtHooks extends ModuleHooks {}
}
export default defineNuxtModule({
setup(options, nuxt) {
nuxt.hook('modules:done', async () => {
await nuxt.callHook('my-module:init', { foo: 'bar' })
})
}
})Virtual Files (Templates)
import { addTemplate, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
addTemplate({
filename: 'my-module/config.mjs',
getContents: () => `export const config = ${JSON.stringify(options)}`
})
}
})Import: import { config } from '#build/my-module/config.mjs'
Type Declarations
import { addTypeTemplate, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
addTypeTemplate({
filename: 'types/my-module.d.ts',
getContents: () => `
declare module '#app' {
interface NuxtApp { $myHelper: (msg: string) => void }
}
export {}
`
})
}
})Logging & Errors
Use consola.withTag for consistent module logging:
import { consola } from 'consola'
const logger = consola.withTag('my-module')
export default defineNuxtModule({
setup(options, nuxt) {
logger.info('Initializing...')
logger.warn('Deprecated option used')
// Errors must include tag manually - consola doesn't add it
if (!options.apiKey) {
throw new Error('[my-module] `apiKey` option is required')
}
}
})Disabling Modules
Set to `false` to disable (Nuxt 4.3+):
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/tailwindcss'],
tailwindcss: false // Disable the module
})Disable inherited layer modules:
// nuxt.config.ts
export default defineNuxtConfig({
extends: ['../base-layer'],
disabledModules: ['@nuxt/image', '@sentry/nuxt/module']
})Only works for modules from layers, not root project modules.
Local Modules
For project-specific modules:
// modules/my-local-module/index.ts
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
meta: { name: 'my-local-module' },
setup(options, nuxt) {
// Module logic
}
})// nuxt.config.ts
export default defineNuxtConfig({
modules: ['./modules/my-local-module']
})Head Management (Nuxt 4.2+)
For modules that need to set global head elements:
import { setGlobalHead } from '@nuxt/kit'
export default defineNuxtModule({
setup(options, nuxt) {
setGlobalHead({
title: 'My Module',
meta: [{ name: 'description', content: 'Description' }],
link: [{ rel: 'icon', href: '/favicon.ico' }]
})
}
})Module Resolution (Nuxt 4.2+)
Resolve modules with custom extensions:
import { resolveModule } from '@nuxt/kit'
export default defineNuxtModule({
async setup(options, nuxt) {
const modulePath = await resolveModule('my-module', {
extensions: ['.mjs', '.js', '.ts']
})
}
})Quick Reference
| Task | Kit Function |
|---|---|
| Add plugin | addPlugin() |
| Add component | addComponent() / addComponentsDir() |
| Add composable | addImports() / addImportsDir() |
| Add server route | addServerHandler() |
| Add server utils | addServerImports() |
| Virtual file | addTemplate() / addServerTemplate() |
| Add types | addTypeTemplate() |
| Add CSS | nuxt.options.css.push() |
| Set global head | setGlobalHead() |
| Resolve module | resolveModule() |
Resources
Testing & Publishing
E2E testing, best practices, and publishing modules.
E2E Testing Setup
npm install -D @nuxt/test-utils vitest// vitest.config.ts
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: { environment: 'nuxt' }
})Test Fixtures
Create a minimal Nuxt app that uses your module:
// test/fixtures/basic/nuxt.config.ts
import MyModule from '../../../src/module'
export default defineNuxtConfig({
modules: [MyModule],
myModule: { enabled: true }
})<!-- test/fixtures/basic/pages/index.vue -->
<template>
<MyButton>Click me</MyButton>
</template>Writing Tests
import { fileURLToPath } from 'node:url'
import { $fetch, setup } from '@nuxt/test-utils/e2e'
// test/basic.test.ts
import { describe, expect, it } from 'vitest'
describe('basic', async () => {
await setup({
rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url))
})
it('renders component', async () => {
const html = await $fetch('/')
expect(html).toContain('Click me')
})
it('api works', async () => {
const data = await $fetch('/api/_my-module/status')
expect(data).toEqual({ status: 'ok' })
})
})Manual Testing
# In module directory
npm pack
# In test project
npm install /path/to/my-module-1.0.0.tgz---
Best Practices
Async Setup
Keep setup fast. Nuxt warns if setup exceeds 1 second.
// Wrong - blocking
async setup(options, nuxt) {
const data = await fetchRemoteConfig() // Slow!
}
// Right - defer to hooks
setup(options, nuxt) {
nuxt.hook('ready', async () => {
const data = await fetchRemoteConfig()
})
}Prefix All Exports
Avoid naming conflicts:
| Type | Wrong | Right |
|---|---|---|
| Components | <Button> | <FooButton> |
| Composables | useData() | useFooData() |
| Server routes | /api/track | /api/_foo/track |
| Plugins | $helper | $fooHelper |
Lifecycle Hooks
For one-time setup tasks:
export default defineNuxtModule({
meta: { name: 'my-module', version: '2.0.0' },
async onInstall(nuxt) {
await generateInitialConfig(nuxt.options.rootDir)
},
async onUpgrade(options, nuxt, previousVersion) {
if (semver.lt(previousVersion, '2.0.0')) {
await migrateFromV1()
}
}
})TypeScript + ESM Only
// Always export typed options
// ESM only - no CommonJS
import { something } from 'package'
export interface ModuleOptions {
apiKey: string
debug?: boolean
} // Right
const { something } = require('package') // WrongError Messages
setup(options, nuxt) {
if (!options.apiKey) {
throw new Error('[my-module] `apiKey` option is required')
}
}---
Releasing
Two-step: local bump → CI publish. CI must pass before tag push.
Setup
pnpm add -D bumpp{
"scripts": {
"release": "bumpp && git push --follow-tags"
}
}Flow
pnpm release # Prompts version, commits, tags, pushes
# → CI release.yml triggers on v* tag → npm publish + GitHub releaseCommit Conventions
| Prefix | Bump |
|---|---|
feat: | minor |
fix:, chore:, docs: | patch |
feat!: or BREAKING CHANGE: | major |
CI Workflows
Three workflows for complete CI/CD:
| File | Trigger | Purpose |
|---|---|---|
ci.yml | push/PR | lint, typecheck, test |
pkg.yml | push/PR | preview packages via pkg-pr-new |
release.yml | tag v* | npm publish + GitHub release |
Copy templates from: references/ci-workflows.md
---
Publishing
Naming Conventions
| Scope | Example | Description |
|---|---|---|
@nuxtjs/ | @nuxtjs/tailwindcss | Community modules (nuxt-modules org) |
nuxt- | nuxt-my-module | Third-party modules |
@org/ | @myorg/nuxt-auth | Organization scoped |
Documentation Checklist
- [ ] Why - What problem does this solve?
- [ ] Installation - How to install and configure?
- [ ] Usage - Basic examples
- [ ] Options - All config options with types
- [ ] Demo - StackBlitz link
Version Compatibility
meta: {
compatibility: { nuxt: '>=3.0.0' }
}Use "X for Nuxt" naming, not "X for Nuxt 3" — let meta.compatibility handle versions.
Resources
Related skills
How it compares
Pick nuxt-modules over generic CI skills when you need Nuxt-module-specific pkg-pr-new preview and npm Trusted Publishing release templates.
FAQ
Who is nuxt-modules for?
Developers and software engineers authoring Nuxt module packages or local extensions who need defineNuxtModule, Kit utilities, testing, and publishing guidance.
When should I use nuxt-modules?
When creating published npm modules, local modules/ extensions, runtime components or composables, server API routes, or CI/CD for Nuxt module releases.
Is nuxt-modules safe to install?
Review the Security Audits panel on this page before installing in production.