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 🙏

© 2026 – Pkg Stats / Ryan Hefner

domma-cms

v0.3.0

Published

File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.

Readme

Domma CMS

Node ≥18 License: MIT npm

A flat-file CMS with no database. Pages are Markdown files, config is JSON, and the admin panel is a full SPA. Powered by Fastify on the backend and Domma on the frontend.


Table of Contents


Quick Start

npx domma-cms my-site
cd my-site
npm run dev

Open http://localhost:3050/admin and log in with the credentials you created during setup.

The setup wizard runs automatically after npm install. To run it again manually: npm run setup.


Requirements

  • Node.js 18 or higher
  • npm 7 or higher
  • pm2 (for production) — npm install -g pm2

Project Structure

After scaffolding, your project looks like this:

my-site/
├── admin/              # Admin SPA (Domma frontend)
│   ├── index.html
│   └── js/
│       ├── app.js      # Router, auth, sidebar init
│       ├── api.js      # Authenticated API client (auto token refresh)
│       ├── views/      # Admin view modules
│       └── templates/  # Companion HTML templates
│
├── config/             # All site configuration (JSON)
│   ├── site.json       # Title, tagline, theme, SEO, footer
│   ├── navigation.json # Navbar brand, items, variant, position
│   ├── plugins.json    # Plugin enabled/disabled state + settings
│   ├── auth.json       # JWT expiry, roles, bcrypt rounds
│   ├── content.json    # Content directory paths and page defaults
│   └── server.json     # Port, host, CORS, upload limits
│
├── content/            # All user content (created on first run)
│   ├── pages/          # Markdown pages (maps to public URLs)
│   ├── media/          # Uploaded files
│   └── users/          # User accounts ({uuid}.json)
│
├── plugins/            # CMS plugins
│   ├── domma-effects/
│   ├── example-analytics/
│   └── form-builder/
│
├── public/             # Public frontend assets (CSS, JS)
├── scripts/            # CLI utilities (setup, reset, seed, etc.)
├── server/             # Fastify backend
│   ├── server.js       # Entry point
│   ├── config.js       # Config loader (getConfig / saveConfig)
│   ├── middleware/     # Auth (JWT, roles)
│   ├── routes/         # API + public SSR routes
│   ├── services/       # Content, users, plugins, markdown, renderer
│   └── templates/      # Public HTML shell (page.html)
│
├── .env                # Secrets — JWT_SECRET, NODE_ENV
├── .env.example        # Template for .env
└── package.json

Configuration

All configuration lives in config/ as JSON files. They are editable directly or via the admin panel.

config/site.json

Controls the public-facing site identity.

{
    "title": "My Site",
    "tagline": "Powered by Domma CMS",
    "theme": "charcoal-dark",
    "seo": {
        "defaultTitle": "My Site",
        "titleSeparator": " | ",
        "defaultDescription": "A site built with Domma CMS"
    },
    "footer": {
        "copyright": "© 2026 My Site. All rights reserved.",
        "links": [
            { "text": "Privacy Policy", "url": "/privacy" },
            { "text": "Contact", "url": "/contact" }
        ]
    }
}

Available themes: charcoal-dark, charcoal-light, ocean-dark, ocean-light, forest-dark, forest-light, sunset-dark, sunset-light, royal-dark, royal-light, lemon-dark, lemon-light, silver-dark, silver-light, grayve, christmas-dark, christmas-light

config/navigation.json

Controls the public navbar.

{
    "brand": { "text": "My Site", "logo": null, "url": "/" },
    "items": [
        { "text": "Home",    "url": "/",        "icon": "home" },
        { "text": "About",   "url": "/about",   "icon": "info" },
        { "text": "Contact", "url": "/contact", "icon": "mail" }
    ],
    "variant": "dark",
    "position": "sticky"
}

Dropdown menus are supported via nested items arrays on any item.

config/plugins.json

Enables or disables plugins and stores their settings.

{
    "form-builder": {
        "enabled": true,
        "settings": { "smtp": { "host": "localhost" } }
    }
}

.env

Secrets only — never commit this file.

JWT_SECRET=your-64-char-random-string
NODE_ENV=development

Generate a strong secret with: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"


Content

Pages are Markdown files in content/pages/. The filename determines the public URL.

| File path | Public URL | |-------------------------------------|---------------------| | content/pages/index.md | / | | content/pages/about.md | /about | | content/pages/blog/hello-world.md | /blog/hello-world | | content/pages/services/index.md | /services |

Frontmatter

Every page starts with a YAML frontmatter block:

---
title: My Page
slug: my-page
description: A short description for SEO
layout: default
status: published
sortOrder: 1
showInNav: false
sidebar: false
seo:
  title: My Page | My Site
  description: A short description for SEO
createdAt: '2026-01-01T00:00:00.000Z'
updatedAt: '2026-01-01T00:00:00.000Z'
---

# My Page

Page content goes here in Markdown.

| Field | Description | |-------------------|---------------------------------------------------------------------------| | title | Page title (used in <title> tag and admin listing) | | slug | URL slug (usually matches the filename) | | description | Short description for SEO | | layout | Layout preset to use (default or any preset from config/presets.json) | | status | published or draft — draft pages return 404 on the public site | | sortOrder | Integer used to order pages in admin listings | | showInNav | Whether to include this page in auto-generated nav menus | | sidebar | Whether to show a sidebar on this page | | seo.title | Overrides the <title> tag | | seo.description | Overrides the meta description |

Plugin Shortcodes

Some plugins inject content via HTML attributes:

<!-- Form Builder plugin — embed any form by slug -->
<div data-form="contact"></div>
<div data-form="feedback"></div>

Forms are created in the admin panel under Plugins → Forms.


Admin Panel

Access the admin panel at http://localhost:3050/admin.

First Login

Run npm run setup (or make setup) to create your admin account, set a site title, and choose a theme. The wizard is safe to re-run — it skips any steps already completed.

Roles

| Role | Level | Can do | |--------------|-------|-------------------------------------------------------------| | admin | 0 | Everything, including user management and plugin config | | manager | 1 | Manage pages, media, navigation, layouts, and site settings | | editor | 2 | Create and edit pages and media | | subscriber | 3 | Read-only access |

Permissions per resource are defined in config/auth.json.

Navigation

The sidebar groups content by role:

  • Overview — Dashboard
  • Structure — Navigation, Layouts (admin and manager only)
  • Content — Pages, Media
  • Configuration — Users, Site Settings (admin and manager only)
  • Plugins — Plugin manager and plugin settings (admin only)

Plugins

Plugins extend Domma CMS with backend routes, public page injections, and admin panel views.

Bundled Plugins

| Plugin | Description | |--------------------|---------------------------------------------------------------------------------------------------| | Form Builder | Visual form builder — create arbitrary forms, store submissions, trigger email and webhook actions | | Analytics | Basic page view tracking stored as a flat JSON file | | Back to Top | Configurable scroll-to-top button injected into every public page | | Cookie Consent | GDPR cookie consent banner with per-category toggles |

Enable or disable any plugin in the admin panel under Plugins, or directly in config/plugins.json.


Building a Plugin

A plugin is a directory under plugins/ containing three mandatory files:

plugins/my-plugin/
├── plugin.json     # Manifest
├── plugin.js       # Fastify plugin (backend)
└── config.js       # Default settings

plugin.json

{
    "name": "my-plugin",
    "displayName": "My Plugin",
    "version": "1.0.0",
    "description": "What this plugin does.",
    "author": "Your Name",
    "date": "2026-01-01",
    "icon": "package",

    "admin": {
        "sidebar": [
            {
                "id": "my-plugin",
                "text": "My Plugin",
                "icon": "package",
                "url": "#/plugins/my-plugin",
                "section": "#/plugins/my-plugin"
            }
        ],
        "routes": [
            {
                "path": "/plugins/my-plugin",
                "view": "plugin-my-plugin-settings",
                "title": "My Plugin - Domma CMS"
            }
        ],
        "views": {
            "plugin-my-plugin-settings": {
                "entry": "my-plugin/admin/views/my-plugin-settings.js",
                "exportName": "myPluginSettingsView"
            }
        }
    },

    "inject": {
        "head": "public/inject-head.html",
        "bodyEnd": "public/inject-body.html"
    }
}

All fields except admin and inject are required. If mandatory fields are missing, the plugin is skipped at startup with a warning — the server never crashes.

plugin.js

A standard Fastify plugin. Use getPluginSettings to read merged settings (defaults + user overrides):

import { getPluginSettings } from '../../server/services/plugins.js';

export default async function myPlugin(fastify, opts) {
    fastify.get('/api/plugins/my-plugin/hello', async (req, reply) => {
        const settings = getPluginSettings('my-plugin');
        return { message: `Hello from ${settings.greeting}` };
    });
}

config.js

Export a plain object with default settings. These are deep-merged with any user overrides stored in config/plugins.json:

export default {
    greeting: 'My Plugin',
    enabled:  true
};

Admin View

Admin views are ES modules served as static files from /plugins/. Import apiRequest from the core API client for authenticated requests with automatic token refresh:

import { apiRequest } from '/admin/js/api.js';

export const myPluginSettingsView = {
    templateUrl: '/plugins/my-plugin/admin/templates/my-plugin-settings.html',

    async onMount($container) {
        const settings = await apiRequest('/plugins/my-plugin/settings');
        // populate fields from settings…

        $container.find('#save-btn').on('click', async () => {
            await apiRequest('/plugins/my-plugin/settings', {
                method: 'PUT',
                body: JSON.stringify({ /* data */ })
            });
            E.toast('Settings saved.', { type: 'success' });
        });
    }
};

apiRequest(endpoint, options) prepends /api automatically, handles Bearer tokens, and retries once with a refreshed token on 401 before redirecting to login.

Public Injection

HTML files listed under inject in plugin.json are concatenated into {{headInject}} and {{bodyEndInject}} slots in server/templates/page.html when the plugin is enabled.


Scripts Reference

| npm script | make target | Description | |----------------------|-------------------|---------------------------------------------------------------------| | npm run dev | make dev | Start server with --watch (auto-restart on save) | | npm start | make start | Start server via pm2 in cluster mode (one process per CPU core) | | — | make stop | Stop the pm2 process | | — | make restart | Restart the pm2 process | | — | make logs | Tail pm2 logs | | npm install | make install | Install dependencies | | npm run setup | make setup | Interactive first-run wizard (admin account, theme, title) | | npm run reset | make reset | Factory reset — wipes content, users, resets config to defaults | | npm run seed | make seed | Seed sample pages and content | | npm run fresh | make fresh | Reset + seed in one step | | npm run copy-domma | make copy-domma | Copy Domma dist from node_modules to public/ | | — | make check | Syntax-check bin/cli.js | | — | make scaffold | Dry-run the scaffolder, creates test-site/ locally |


Contributing

Running Locally

git clone https://github.com/pinpointzero73/domma-cms.git
cd domma-cms
npm install
npm run setup
npm run dev

The server runs on http://localhost:3050 by default. Change the port in config/server.json.

Conventions

  • Backend — Fastify 5, ESM ("type": "module"). Routes in server/routes/, services in server/services/.
  • Frontend — Domma SPA in admin/. Views export { templateUrl, async onMount($container) }. Templates live alongside views in admin/js/templates/.
  • HTTP — Always use apiRequest from admin/js/api.js — never raw fetch() — so token refresh is handled automatically.
  • Storage — Config changes go through getConfig() / saveConfig() from server/config.js. Never write config files directly from routes.
  • Plugins — Follow the three-file structure. Use getPluginSettings(name) to read settings — never read config/plugins.json directly.

Adding a New Admin View

  1. Create admin/js/views/my-view.js exporting { templateUrl, onMount }
  2. Create admin/js/templates/my-view.html
  3. Register the route in admin/js/app.js
  4. Add a sidebar entry in admin/js/config/sidebar-config.js with appropriate role guard

Licence

MIT © Darryl Waterhouse