npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@neskeep/nuxt-cms

v1.0.2

Published

A developer-friendly CMS module for Nuxt 4 with multi-database support and i18n

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-cms

Automatic Setup (Recommended)

Run the init command after installation:

npx nuxt-cms-init

This will:

  • Add @neskeep/nuxt-cms to your nuxt.config.ts
  • Create a cms.config.ts with 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:

  1. Language Switcher — Appears at the top of forms
  2. Translation Badges — Translatable fields show an indicator icon
  3. 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/:name

Query 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/login
  • POST /api/cms/auth/logout
  • GET /api/cms/auth/me

Collections:

  • GET /api/cms/collections
  • GET /api/cms/collections/:name
  • POST /api/cms/collections/:name
  • GET /api/cms/collections/:name/:id
  • PUT /api/cms/collections/:name/:id
  • DELETE /api/cms/collections/:name/:id

Singletons:

  • GET /api/cms/singletons
  • GET /api/cms/singletons/:name
  • PUT /api/cms/singletons/:name

Media:

  • GET /api/cms/media
  • POST /api/cms/media/upload
  • GET /api/cms/media/file/:filename
  • DELETE /api/cms/media/:id

Users & Roles:

  • GET /api/cms/users
  • POST /api/cms/users
  • GET /api/cms/users/:id
  • PUT /api/cms/users/:id
  • DELETE /api/cms/users/:id
  • GET /api/cms/roles
  • POST /api/cms/roles
  • PUT /api/cms/roles/:id
  • DELETE /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-uninstall

This removes:

  • Module from nuxt.config.ts
  • cms.config.ts file
  • .cms folder (optional)

Then uninstall the package:

npm remove @neskeep/nuxt-cms

Manual Cleanup

  1. Remove '@neskeep/nuxt-cms' from modules in nuxt.config.ts
  2. Remove the cms: { ... } configuration block
  3. Delete cms.config.ts
  4. Delete .cms folder
  5. 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

Additional Resources

Community


🤝 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

📄 License

MIT License © Neskeep