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

headroom-cms

v0.1.7

Published

Headroom CMS — Serverless headless CMS for AWS, deployed with SST

Readme

Headroom CMS

Serverless headless CMS for AWS, deployed with SST.

Headroom provisions a complete CMS stack — DynamoDB, S3, Lambda, CloudFront, Cognito, and an admin UI — from a single SST component. No Go toolchain or build step required; pre-compiled binaries and a pre-built admin UI are included in the package.

Quick Start

npm create headroom my-cms
cd my-cms
npx sst deploy --stage production
./scripts/create-admin.sh [email protected] 'YourPassword123!'

After deploying, the CLI prints three URLs:

  • API — the Lambda function URL for the content API
  • CDN — the CloudFront distribution (cached, edge-authenticated)
  • Admin — the admin UI

Open the Admin URL and sign in with the credentials you just created.

Prerequisites

  • Node.js 22+ and pnpm (or npm)
  • AWS credentials configured (aws configure or environment variables)
  • SST v3 (installed automatically as a dev dependency)
  • AWS CLI (for the create-admin.sh and get-token.sh helper scripts)
  • jq (used by helper scripts to read SST outputs)

Configuration Reference

Your sst.config.ts imports HeadroomCMS and passes a configuration object:

/// <reference path="./.sst/platform/config.d.ts" />
import { HeadroomCMS } from "headroom-cms";

export default $config({
  app(input) {
    return {
      name: "my-cms",
      removal: input?.stage === "production" ? "retain" : "remove",
      protect: input?.stage === "production",
      home: "aws",
    };
  },
  async run() {
    const cms = new HeadroomCMS("CMS", {
      senderEmail: "[email protected]",

      // All options below are optional
      domain: {
        name: "api.mycompany.com",
        certificateArn: "arn:aws:acm:us-east-1:...:certificate/...",
      },
      adminDomain: {
        name: "cms.mycompany.com",
        certificateArn: "arn:aws:acm:us-east-1:...:certificate/...",
      },
      priceClass: "PriceClass_100",
      apiCacheTtl: 3600,
      passwordPolicy: {
        minimumLength: 12,
        requireLowercase: true,
        requireUppercase: true,
        requireNumbers: true,
        requireSymbols: true,
      },
    });

    return cms.outputs;
  },
});

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | senderEmail | string | (required) | Email for admin invitations and password resets. Must be verified in SES (see Troubleshooting). | | domain | { name, certificateArn } | CloudFront default | Custom domain for the CDN / API endpoint. | | adminDomain | { name, certificateArn } | CloudFront default | Custom domain for the admin UI. | | priceClass | "PriceClass_100" | "PriceClass_200" | "PriceClass_All" | "PriceClass_100" | CloudFront price class. 100 = US/Canada/Europe, 200 adds Asia/Africa, All = global. | | apiCacheTtl | number | 3600 | API response cache TTL in seconds at the CloudFront edge. | | passwordPolicy | object | See below | Cognito password policy overrides. |

Password Policy Defaults

| Field | Default | |-------|---------| | minimumLength | 12 | | requireLowercase | true | | requireUppercase | true | | requireNumbers | true | | requireSymbols | true |

Outputs

The component returns these outputs (available in .sst/outputs.json after deploy):

| Output | Description | |--------|-------------| | api | Lambda function URL for the Go API | | cdn | CloudFront distribution URL (cached, edge-authenticated) | | admin | Admin UI URL | | userPoolId | Cognito User Pool ID | | userPoolClientId | Cognito User Pool Client ID |

Custom Domains

To use custom domains for the CDN or admin UI:

  1. Request an ACM certificate in us-east-1 (required for CloudFront):

    aws acm request-certificate \
      --domain-name api.mycompany.com \
      --validation-method DNS \
      --region us-east-1
  2. Validate the certificate by adding the DNS records ACM provides (check the AWS Console or use aws acm describe-certificate).

  3. Add the domain config to your sst.config.ts:

    const cms = new HeadroomCMS("CMS", {
      senderEmail: "[email protected]",
      domain: {
        name: "api.mycompany.com",
        certificateArn: "arn:aws:acm:us-east-1:123456789:certificate/abc-123",
      },
    });
  4. Deploy, then create a CNAME (or alias) DNS record pointing your domain to the CloudFront distribution domain shown in the deploy output.

Repeat for adminDomain if you want a custom domain for the admin UI.

Updating

pnpm update headroom-cms
npx sst deploy --stage production

SST detects changes in the component and updates Lambda code, admin UI assets, and any infrastructure changes automatically.

Version Policy

Headroom follows semantic versioning:

  • Patch (0.1.x) — Bug fixes, no infrastructure changes
  • Minor (0.x.0) — New features, backward-compatible infrastructure additions
  • Major (x.0.0) — Breaking changes to config interface or infrastructure that may require manual steps

Troubleshooting

SES Email Verification

Headroom creates an SES email identity for your senderEmail automatically, but AWS requires you to verify it. Check the inbox for that address and click the verification link. Until verified, admin invitation emails won't send.

If your AWS account is still in the SES sandbox, you can only send to verified email addresses. Request production access in the AWS Console under SES > Account dashboard.

IAM Permissions

The deploying user/role needs broad permissions to create DynamoDB tables, S3 buckets, Lambda functions, CloudFront distributions, Cognito user pools, SQS queues, and IAM roles. If you get AccessDenied errors during deploy, ensure your credentials have AdministratorAccess or an equivalent policy.

Deploy Errors

  • "Resource already exists" — You may have resources from a previous deployment in the same AWS account/region. Use a different SST stage name or clean up the old resources.
  • "CREATE_FAILED ... custom-message Lambda" — This usually means the Node.js runtime version isn't available in your region. Ensure you're deploying to a standard region.
  • Timeout during deploy — CloudFront distribution creation can take 5-15 minutes on first deploy. This is normal.

Helper Scripts

The scaffolded project includes two helper scripts in scripts/:

  • create-admin.sh <email> <password> — Creates a Cognito admin user. Password must be 12+ characters with uppercase, lowercase, numbers, and symbols.
  • get-token.sh <email> <password> — Returns a JWT access token for testing admin API endpoints.

Both scripts read from .sst/outputs.json, so you must deploy before running them.

What Gets Deployed

A single HeadroomCMS component creates:

  • 11 DynamoDB tables (sites, content, collections, block types, media, API keys, tags, audit, relationships, webhooks, webhook deliveries)
  • 1 S3 bucket (media files and content bodies)
  • 1 CloudFront KVS (edge auth cache and site version tracking)
  • 1 Cognito User Pool + Client (admin authentication)
  • 1 SES Email Identity (admin invitations)
  • 4 Lambda functions (API, webhook worker, image transform, custom message)
  • 1 CloudFront Distribution with 2 CloudFront Functions (edge auth, media URL rewrite)
  • 1 Admin UI (static site on CloudFront)
  • 2 SQS queues (webhook delivery + dead letter queue)

Advanced: Forking

If you need to modify Go API handlers, add custom admin UI pages, change DynamoDB schemas, or make other changes that the configuration options don't cover, you can eject to a full source checkout.

This is a one-way migration: once ejected, updates come from merging upstream changes rather than pnpm update.

When to Eject

The packaged component covers most use cases. Eject only when you need to:

  • Add custom API endpoints or middleware in the Go Lambda
  • Add custom admin UI pages or block editor plugins
  • Change DynamoDB table schemas
  • Wire additional Lambda functions into the same infrastructure
  • Replace a subsystem entirely (e.g., swap Cognito for a different auth provider)

Ejection Steps

  1. Clone at your current version:

    git clone --branch v0.1.0 https://github.com/cykod/headroom.git my-cms-custom
    cd my-cms-custom
    pnpm install
  2. Copy your SST state so SST recognizes existing AWS resources:

    cp -r ../my-cms/.sst ./
  3. Update sst.config.ts to use local source with the dev option:

    /// <reference path="./.sst/platform/config.d.ts" />
    import { HeadroomCMS } from "./packages/headroom-cms/src/index.js";
    
    export default $config({
      app(input) {
        return {
          name: "my-cms", // must match your previous app name
          removal: input?.stage === "production" ? "retain" : "remove",
          protect: input?.stage === "production",
          home: "aws",
        };
      },
      async run() {
        const cms = new HeadroomCMS("CMS", {
          senderEmail: "[email protected]",
          dev: {
            apiHandler: "packages/api",
            webhookWorkerHandler: "packages/webhook-worker",
            customMessageHandler: "packages/functions/custom-message.handler",
            imageLambdaHandler: "packages/image-lambda/index.handler",
            adminPath: "packages/admin",
          },
        });
    
        return cms.outputs;
      },
    });
  4. Deploy to verify the state transfer (should be a no-op if config matches):

    npx sst deploy --stage production
  5. Start customizing. Key directories:

    | Directory | What it is | |-----------|-----------| | packages/api/ | Go Lambda API — add routes in main.go, handlers in internal/handler/ | | packages/admin/ | React admin UI — add pages in src/pages/, components in src/components/ | | packages/headroom-cms/src/ | SST infrastructure component — modify DynamoDB schemas, add resources | | packages/image-lambda/ | Sharp image transform Lambda | | packages/webhook-worker/ | Webhook delivery worker |

License

PolyForm Noncommercial 1.0.0