@neskeep/nuxt-cms
v1.0.2
Published
A developer-friendly CMS module for Nuxt 4 with multi-database support and i18n
Maintainers
Readme
Overview
@neskeep/nuxt-cms is a full-featured, self-hosted headless CMS that integrates seamlessly into your Nuxt application. It provides a beautiful admin panel, flexible content modeling, built-in i18n support, and a powerful API — all with zero external dependencies.
npm install @neskeep/nuxt-cms✨ Features
Content Management
- Collections — Create dynamic content types (posts, products, etc.)
- Singletons — Manage unique pages (homepage, settings)
- 27 Field Types — Text, richtext, images, icons, relations, repeaters, and more
- Media Library — Upload and manage images, videos, and files
Developer Experience
- Type Safe — Full TypeScript support with auto-completion
- Vue Composables — Easy data fetching with
useCmsCollection,useCmsSingleton - Public API — Ready-to-use endpoints for frontend consumption
- Zero Config — Works out of the box with sensible defaults
Internationalization
- Multi-language — Built-in i18n support with per-field translations
- Admin UI i18n — Complete admin interface in English and Spanish
- User Preferences — Per-user language settings with instant application
- Visual Indicators — Translation badges in the admin UI
- Locale Switcher — Easy language switching in forms and profile
Customization
- Custom Branding — Logo, colors, login page customization
- Role-Based Access — Users and roles management
- Flexible Layouts — Control field widths (full, half, third, quarter)
- Multiple Databases — SQLite (default) or PostgreSQL
🚀 Quick Start
Installation
# npm
npm install @neskeep/nuxt-cms
# pnpm
pnpm add @neskeep/nuxt-cms
# yarn
yarn add @neskeep/nuxt-cmsAutomatic Setup (Recommended)
Run the init command after installation:
npx nuxt-cms-initThis will:
- Add
@neskeep/nuxt-cmsto yournuxt.config.ts - Create a
cms.config.tswith example collections - Set up default admin credentials
Default credentials:
admin/admin123⚠️ Change the password before deploying to production!
Manual Setup
1. Add module to nuxt.config.ts
export default defineNuxtConfig({
modules: ['@neskeep/nuxt-cms'],
cms: {
database: {
provider: 'sqlite',
filename: '.cms/data.db'
},
admin: {
enabled: true,
path: '/admin',
credentials: {
username: 'admin',
password: 'your-secure-password'
}
}
}
})2. Create cms.config.ts
import { defineCmsConfig } from '@neskeep/nuxt-cms'
export default defineCmsConfig({
locales: ['en', 'es'],
defaultLocale: 'en',
collections: {
posts: {
label: 'Post',
labelPlural: 'Posts',
icon: 'heroicons:document-text',
titleField: 'title',
fields: {
title: {
type: 'text',
label: 'Title',
required: true,
translatable: true
},
slug: {
type: 'slug',
label: 'Slug',
from: 'title'
},
content: {
type: 'richtext',
label: 'Content',
translatable: true
}
}
}
},
singletons: {
homepage: {
label: 'Homepage',
icon: 'heroicons:home',
fields: {
heroTitle: {
type: 'text',
label: 'Hero Title',
translatable: true
}
}
}
}
})Access the Admin Panel
Start your app and navigate to /admin:
npm run dev
# Open http://localhost:3000/admin⚙️ Configuration
Module Options
Configure the CMS in your nuxt.config.ts:
export default defineNuxtConfig({
cms: {
// Database configuration
database: {
provider: 'sqlite', // 'sqlite' | 'postgresql'
filename: '.cms/data.db', // SQLite file path
url: process.env.DATABASE_URL // PostgreSQL connection URL
},
// Admin panel configuration
admin: {
enabled: true,
path: '/admin',
credentials: {
username: 'admin',
password: 'secure-password'
},
branding: { /* See Branding section */ }
},
// Upload configuration
uploads: {
path: '.cms/uploads',
maxSize: 10 * 1024 * 1024, // 10MB
allowedTypes: [
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml',
'application/pdf',
'video/mp4', 'video/webm'
]
},
// JWT configuration
jwt: {
secret: process.env.CMS_JWT_SECRET
}
}
})Configuration Reference
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| database.provider | 'sqlite' | 'postgresql' | 'sqlite' | Database backend |
| database.filename | string | '.cms/data.db' | SQLite database file path |
| database.url | string | - | PostgreSQL connection URL |
| admin.enabled | boolean | true | Enable admin panel |
| admin.path | string | '/admin' | Admin panel route path |
| admin.credentials | object | - | Initial admin user credentials |
| admin.branding | object | - | Customize admin appearance |
| uploads.path | string | '.cms/uploads' | Upload directory |
| uploads.maxSize | number | 10485760 | Max file size in bytes |
| uploads.allowedTypes | string[] | See above | Allowed MIME types |
PostgreSQL Setup
export default defineNuxtConfig({
cms: {
database: {
provider: 'postgresql',
url: process.env.DATABASE_URL
}
}
})DATABASE_URL=postgresql://user:password@localhost:5432/mydb🎨 Branding
Customize the admin panel appearance by configuring branding options in your nuxt.config.ts.
Configuration
Add branding customization to your Nuxt config:
export default defineNuxtConfig({
modules: ['@neskeep/nuxt-cms'],
cms: {
admin: {
enabled: true,
path: '/admin',
branding: {
// CMS name shown in sidebar and page titles
name: 'My CMS',
// Custom logo image URL (replaces the icon in sidebar)
// Recommended: SVG format, max height 40px
logo: '/assets/logo.svg',
// Primary theme color (hex format)
primaryColor: '#2563eb',
// Powered by attribution in footer
poweredBy: {
name: 'Your Company',
url: 'https://yourcompany.com'
},
// Login page customization
login: {
title: 'Welcome Back',
description: 'Manage your content with ease',
backgroundImage: '/assets/login-bg.jpg'
}
}
}
}
})Available Branding Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| name | string | 'CMS' | Name shown in sidebar and page titles |
| logo | string | - | Custom logo image URL (SVG recommended, max height 40px) |
| primaryColor | string | '#2563eb' | Primary theme color for navigation, buttons, and links (hex format) |
| poweredBy.name | string | 'Neskeep' | Brand name shown in footer |
| poweredBy.url | string | 'https://neskeep.com' | Optional URL for footer brand link |
| login.title | string | 'Content Management System' | Heading shown on login page |
| login.description | string | 'Manage your content...' | Description text on login page |
| login.backgroundImage | string | - | Background image URL for login page |
Full Example
export default defineNuxtConfig({
cms: {
admin: {
branding: {
name: 'Acme CMS',
logo: '/logo.svg',
primaryColor: '#7c3aed',
poweredBy: {
name: 'Acme Corp',
url: 'https://acme.com'
},
login: {
title: 'Acme Content Manager',
description: 'Access your content management dashboard',
backgroundImage: '/images/login-background.jpg'
}
}
}
}
})Using Public Assets
Place your logo and images in the public/ directory:
public/
├── logo.svg → Accessible as '/logo.svg'
├── favicon.ico → Automatically used by Nuxt
└── images/
└── login-bg.jpg → Accessible as '/images/login-bg.jpg'📝 Field Types
Basic Fields
// Text input
title: { type: 'text', label: 'Title', maxLength: 200 }
// Textarea
description: { type: 'textarea', label: 'Description', rows: 5 }
// Number
price: { type: 'number', label: 'Price', min: 0, step: 0.01 }
// Email
email: { type: 'email', label: 'Email' }
// URL
website: { type: 'url', label: 'Website' }
// Password
password: { type: 'password', label: 'Password', minLength: 8 }Selection Fields
// Select dropdown
category: {
type: 'select',
label: 'Category',
options: [
{ label: 'Technology', value: 'tech' },
{ label: 'Business', value: 'business' }
],
multiple: true
}
// Radio buttons
status: {
type: 'radio',
label: 'Status',
options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Published', value: 'published' }
],
inline: true
}
// Checkboxes
tags: {
type: 'checkbox',
label: 'Tags',
options: [
{ label: 'Featured', value: 'featured' },
{ label: 'Popular', value: 'popular' }
]
}
// Boolean toggle
isActive: { type: 'boolean', label: 'Active' }Content Fields
// Rich text editor
content: {
type: 'richtext',
label: 'Content',
toolbar: ['bold', 'italic', 'heading', 'link', 'image', 'list']
}
// Markdown editor
readme: { type: 'markdown', label: 'README', preview: true }
// Code editor
snippet: { type: 'code', label: 'Code', language: 'javascript' }Media Fields
// Single image
avatar: { type: 'image', label: 'Avatar' }
// File upload
document: { type: 'file', label: 'Document', accept: ['application/pdf'] }
// Image gallery
gallery: { type: 'gallery', label: 'Gallery', maxItems: 10 }Date & Time Fields
birthday: { type: 'date', label: 'Birthday' }
publishedAt: { type: 'datetime', label: 'Publish Date' }
openingTime: { type: 'time', label: 'Opening Time' }Relation Fields
// One-to-one relation
author: {
type: 'relation',
label: 'Author',
collection: 'users',
relationship: 'one-to-one',
displayField: 'name'
}
// Many-to-many relation
categories: {
type: 'relation',
label: 'Categories',
collection: 'categories',
relationship: 'many-to-many'
}Layout Fields
// Repeater (array of items)
features: {
type: 'repeater',
label: 'Features',
min: 1,
max: 10,
sortable: true,
fields: {
icon: { type: 'text', label: 'Icon', width: 'half' },
title: { type: 'text', label: 'Title', width: 'half' },
description: { type: 'textarea', label: 'Description' }
}
}
// Group (nested object)
seo: {
type: 'group',
label: 'SEO Settings',
fields: {
metaTitle: { type: 'text', label: 'Meta Title' },
metaDescription: { type: 'textarea', label: 'Meta Description' }
}
}Special Fields
// Color picker
brandColor: { type: 'color', label: 'Brand Color' }
// Auto-generated slug
slug: { type: 'slug', label: 'URL Slug', from: 'title' }
// Icon picker (Heroicons)
icon: {
type: 'icon',
label: 'Icon',
variants: ['outline', 'solid'], // Available variants
defaultVariant: 'outline', // Default variant
clearable: true // Allow clearing
}
// JSON editor
metadata: { type: 'json', label: 'Metadata' }Common Field Options
All fields support these options:
{
type: 'text',
label: 'Field Label', // Display label
required: true, // Make field required
default: 'Default value', // Default value
placeholder: 'Enter text', // Placeholder text
help: 'Help text below', // Help text
translatable: true, // Enable i18n translations
hidden: false, // Hide from admin
readonly: false, // Read-only field
width: 'half' // 'full' | 'half' | 'third' | 'quarter'
}Field Width Options
| Width | Columns | Description |
|-------|---------|-------------|
| 'full' | 12/12 | Full width (default) |
| 'half' | 6/12 | Half width — 2 fields per row |
| 'third' | 4/12 | One third — 3 fields per row |
| 'quarter' | 3/12 | One quarter — 4 fields per row |
Example:
fields: {
firstName: { type: 'text', label: 'First Name', width: 'half' },
lastName: { type: 'text', label: 'Last Name', width: 'half' },
email: { type: 'email', label: 'Email' } // full width
}🌐 Internationalization (i18n)
Configuration
export default defineCmsConfig({
locales: ['en', 'es', 'fr'], // Available languages
defaultLocale: 'en',
collections: {
posts: {
fields: {
title: {
type: 'text',
label: 'Title',
translatable: true // This field can be translated
},
slug: {
type: 'slug',
from: 'title'
// Not translatable — same slug for all languages
}
}
}
}
})Admin Panel UX
When multiple locales are configured:
- Language Switcher — Appears at the top of forms
- Translation Badges — Translatable fields show an indicator icon
- Visual Feedback — Easy to see which language you're editing
Fetching Translations
// Via composable
const { data } = useCmsSingleton('homepage', { locale: 'es' })
// Via API
const posts = await $fetch('/api/cms/public/collections/posts?locale=es')🔌 Composables
useCmsCollection
Fetch and manage collection items:
<script setup lang="ts">
const {
items,
pending,
total,
refresh,
fetchById,
create,
update,
remove
} = useCmsCollection('posts', {
limit: 10,
orderBy: { publishedAt: 'desc' },
locale: 'en'
})
// Fetch single item
const post = await fetchById('post-id')
// Create
await create({ title: 'New Post', content: '...' })
// Update
await update('post-id', { title: 'Updated' })
// Delete
await remove('post-id')
</script>
<template>
<div v-if="pending">Loading...</div>
<article v-for="post in items" :key="post.id">
<h2>{{ post.title }}</h2>
</article>
</template>useCmsSingleton
Fetch and update singleton data:
<script setup lang="ts">
const { data, pending, update } = useCmsSingleton('homepage', {
locale: 'en'
})
await update({ heroTitle: 'New Title' })
</script>useCmsMedia
Manage media library:
<script setup lang="ts">
const {
items,
upload,
remove,
uploading,
getUrl
} = useCmsMedia({ type: 'image' })
const handleUpload = async (file: File) => {
const media = await upload(file, 'Alt text')
console.log('Uploaded:', getUrl(media))
}
</script>🔗 API Endpoints
Public API (No Auth Required)
Perfect for frontend consumption:
GET /api/cms/public/collections/:name
GET /api/cms/public/singletons/:nameQuery Parameters:
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| page | number | 1 | Page number |
| limit | number | 20 | Items per page (max 100) |
| locale | string | - | Language code |
| sort | string | -createdAt | Sort field (prefix - for desc) |
| status | string | published | published, draft, or all |
Example:
const { items, total } = await $fetch('/api/cms/public/collections/posts', {
query: { locale: 'es', sort: '-publishedAt', limit: 10 }
})Admin API (Auth Required)
Authentication:
POST /api/cms/auth/loginPOST /api/cms/auth/logoutGET /api/cms/auth/me
Collections:
GET /api/cms/collectionsGET /api/cms/collections/:namePOST /api/cms/collections/:nameGET /api/cms/collections/:name/:idPUT /api/cms/collections/:name/:idDELETE /api/cms/collections/:name/:id
Singletons:
GET /api/cms/singletonsGET /api/cms/singletons/:namePUT /api/cms/singletons/:name
Media:
GET /api/cms/mediaPOST /api/cms/media/uploadGET /api/cms/media/file/:filenameDELETE /api/cms/media/:id
Users & Roles:
GET /api/cms/usersPOST /api/cms/usersGET /api/cms/users/:idPUT /api/cms/users/:idDELETE /api/cms/users/:idGET /api/cms/rolesPOST /api/cms/rolesPUT /api/cms/roles/:idDELETE /api/cms/roles/:id
🔐 Security
Role-Based Access Control
The CMS includes a full RBAC system with 4 built-in roles:
- Super Administrator — Full access to all features including role management
- Administrator — Manage content and users (cannot manage roles)
- Editor — Create and edit content (no user management)
- Author — Create and edit own content only
- Custom Roles — Define granular permissions for specific needs
Security Features
- JWT-based authentication
- Rate limiting on API endpoints
- Input validation and sanitization
- Secure password hashing (bcrypt)
- CSRF protection
- XSS prevention
Environment Variables
# Required for production
CMS_JWT_SECRET=your-very-secure-secret-at-least-32-characters
# PostgreSQL (if used)
DATABASE_URL=postgresql://user:password@localhost:5432/mydb📦 Collection Options
collections: {
posts: {
label: 'Post', // Singular label
labelPlural: 'Posts', // Plural label
icon: 'heroicons:document-text', // Heroicon name
description: 'Blog posts',
titleField: 'title', // Field to show in lists
slugField: 'slug', // Field for URL slugs
timestamps: true, // Add created_at, updated_at
softDelete: false, // Enable soft delete
publishable: true, // Enable draft/published
sortable: false, // Enable manual sorting
defaultSort: {
field: 'createdAt',
direction: 'desc'
},
fields: { /* ... */ }
}
}🛠 TypeScript Support
Full TypeScript support with type inference:
import type {
CmsModuleOptions,
CmsConfig,
CollectionConfig,
FieldDefinition,
BrandingConfig
} from '@neskeep/nuxt-cms'
// Type your content
interface Post {
id: string
title: string
content: string
publishedAt: string
}
const { items } = useCmsCollection<Post>('posts')🗑 Uninstallation
Automatic Cleanup
npx nuxt-cms-uninstallThis removes:
- Module from
nuxt.config.ts cms.config.tsfile.cmsfolder (optional)
Then uninstall the package:
npm remove @neskeep/nuxt-cmsManual Cleanup
- Remove
'@neskeep/nuxt-cms'frommodulesinnuxt.config.ts - Remove the
cms: { ... }configuration block - Delete
cms.config.ts - Delete
.cmsfolder - Run
rm -rf .nuxt && npm run dev
🧩 Compatibility
| Requirement | Version | |-------------|---------| | Nuxt | 3.16+ or 4.x | | Node.js | 18+ | | TypeScript | 5.0+ (optional) |
Works with Tailwind CSS v3 and v4 — admin styles are fully isolated.
📚 Documentation
Getting Started
- Quick Start - Installation and basic setup
- Configuration - Module configuration options
- Field Types - Complete field reference
- Branding - Customize the admin panel
Additional Resources
- 📋 Roadmap - Future development plans
- 📊 Project Status - Current project status and features
- 📝 Changelog - Version history and updates
Community
- GitHub Issues - Bug reports and feature requests
- GitHub Discussions - Questions and community support
🤝 Contributing
We welcome contributions! Check out our roadmap to see what we're working on.
Ways to contribute:
- 🐛 Report bugs
- 💡 Suggest features
- 📖 Improve documentation
- 🌍 Add translations
- 💻 Submit pull requests
📖 Development
# Install dependencies
pnpm install
# Run playground
pnpm dev
# Build module
pnpm build
# Run tests
pnpm test💖 Support
If this project has been helpful to you, consider supporting its development:
Your support helps:
- 🚀 Maintain and improve the CMS
- ✨ Add new features based on community feedback
- 📚 Provide better documentation and support
- 🌟 Keep the project 100% open source
Currently working on:
- Login page redesign and UX improvements
- Additional language support (French, Portuguese, German)
- Advanced content features (versioning, scheduled publishing)
- Comprehensive documentation and video tutorials
