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/api

v0.1.7

Published

TypeScript SDK for building sites with [Headroom CMS](https://github.com/headroom-cms). Provides a type-safe API client, block renderers for React and Astro, and an Astro content loader integration.

Readme

@headroom-cms/api

TypeScript SDK for building sites with Headroom CMS. Provides a type-safe API client, block renderers for React and Astro, and an Astro content loader integration.

Installation

npm install @headroom-cms/api

Peer dependencies (optional — install only what you use):

| Peer | When needed | |------|-------------| | react + react-dom >= 18 | React block rendering (@headroom-cms/api/react) | | astro >= 5 | Astro content loader + dev refresh (@headroom-cms/api/astro) |

Quick Start

import { HeadroomClient } from "@headroom-cms/api";

const client = new HeadroomClient({
  url: "https://headroom.example.com",
  site: "mysite.com",
  apiKey: "headroom_xxxxx",
});

const { items } = await client.listContent("posts");

API Client

Configuration

interface HeadroomConfig {
  url: string;               // Headroom CDN URL (used for both API calls and media)
  site: string;              // Site host identifier
  apiKey: string;            // Public API key
  imageSigningSecret?: string; // HMAC secret for image transforms
}

Methods

| Method | Returns | Description | |--------|---------|-------------| | listContent(collection, opts?) | ContentListResult | List published content with pagination and filtering | | getContent(contentId) | ContentItem | Get a single content item with body and relationships | | getContentBySlug(collection, slug) | ContentItem \| undefined | Look up content by slug (returns undefined on 404) | | getSingleton(collection) | ContentItem | Get singleton content (e.g. site settings) | | getBatchContent(ids) | BatchContentResult | Fetch up to 50 items with bodies in one request | | listCollections() | CollectionListResult | List all collections | | getCollection(name) | Collection | Get collection schema with fields and relationships | | listBlockTypes() | BlockTypeListResult | List block type definitions | | getVersion() | number | Content version (for cache busting) | | mediaUrl(path) | string | Prepend base URL to a stored media path | | transformUrl(path, opts?) | string | Build a signed image transform URL |

Query Options for listContent

const { items, cursor, hasMore } = await client.listContent("posts", {
  limit: 10,
  cursor: "...",              // Pagination cursor from previous response
  sort: "published_desc",     // "published_desc" | "published_asc" | "title_asc" | "title_desc"
  before: 1700000000,         // Unix timestamp — only items published before
  after: 1690000000,          // Unix timestamp — only items published after
  relatedTo: "01ABC",         // Reverse relationship: items pointing to this content ID
  relField: "author",         // Filter reverse query to a specific relationship field
});

Error Handling

API errors throw a HeadroomError with status and code properties:

import { HeadroomClient, HeadroomError } from "@headroom-cms/api";

try {
  const post = await client.getContent("nonexistent");
} catch (e) {
  if (e instanceof HeadroomError) {
    console.log(e.status); // 404
    console.log(e.code);   // "CONTENT_NOT_FOUND"
  }
}

Block Rendering

Content bodies from Headroom contain an array of BlockNote blocks. The SDK provides renderers for both Astro and React.

Astro (Zero JS)

Import .astro components directly from @headroom-cms/api/blocks/*. These ship as source files compiled by your Astro build — no client-side JavaScript is emitted.

---
import BlockRenderer from "@headroom-cms/api/blocks/BlockRenderer.astro";
import type { Block, RefsMap } from "@headroom-cms/api";

const post = await client.getContentBySlug("posts", slug);
const blocks = (post.body?.content || []) as Block[];
const refs = (post._refs || {}) as RefsMap;
---

<BlockRenderer
  blocks={blocks}
  refs={refs}
  resolveContentLink={(ref) => `/${ref.collection}/${ref.slug}`}
  transformImage={(path) => client.transformUrl(path, { width: 1200, format: "webp" })}
/>

Props:

| Prop | Type | Description | |------|------|-------------| | blocks | Block[] | Block content array | | baseUrl | string? | Base URL for media (defaults to HEADROOM_URL env var) | | refs | RefsMap? | Content reference map for resolving headroom:// links | | resolveContentLink | (ref: PublicContentRef) => string | Custom URL builder for content links | | transformImage | (path: string) => string | Custom image URL transform (e.g. for responsive images) | | class | string? | CSS class for the wrapper <div> |

Available components (importable individually from @headroom-cms/api/blocks/*):

BlockRenderer, Paragraph, Heading, Image, CodeBlock, BulletList, NumberedList, CheckList, Table, InlineContent, Fallback

React

import { BlockRenderer } from "@headroom-cms/api/react";
import "@headroom-cms/api/react/headroom-blocks.css";

function PostBody({ blocks, refs }) {
  return (
    <BlockRenderer
      blocks={blocks}
      baseUrl="https://headroom.example.com"
      refs={refs}
      resolveContentLink={(ref) => `/${ref.collection}/${ref.slug}`}
    />
  );
}

Props:

| Prop | Type | Description | |------|------|-------------| | blocks | Block[] | Block content array | | baseUrl | string? | Base URL for media | | refs | RefsMap? | Content reference map | | resolveContentLink | (ref: PublicContentRef) => string | Custom URL builder | | components | BlockComponentMap? | Override or extend block components (see below) | | fallback | ComponentType \| null | Custom fallback for unknown blocks (null to suppress) | | className | string? | CSS class for the wrapper <div> |

Available block types: paragraph, heading, image, codeBlock, bulletListItem, numberedListItem, checkListItem, table

Content Links

Rich text can contain headroom://content/{collection}/{contentId} links that reference other content. The _refs map returned with each content item resolves these to metadata:

const post = await client.getContent("01ABC");

// post._refs = {
//   "01DEF": { contentId: "01DEF", collection: "posts", slug: "hello-world", title: "Hello World", published: true }
// }

The block renderer resolves these links automatically:

  • Default: headroom://content/posts/01DEF/{collection}/{slug} (i.e. /posts/hello-world)
  • Custom resolver: Pass resolveContentLink to map to your site's URL structure
  • Broken links: Unpublished or missing references render as #

You can also resolve links manually:

import type { PublicContentRef } from "@headroom-cms/api";

function resolveContentLink(ref: PublicContentRef): string {
  if (!ref.published) return "#";
  switch (ref.collection) {
    case "posts": return `/blog/${ref.slug}`;
    case "projects": return `/projects/${ref.slug}`;
    default: return `/${ref.slug}`;
  }
}

Custom Block Components

React

Pass a components map to override built-in blocks or render custom block types:

import { BlockRenderer } from "@headroom-cms/api/react";
import type { BlockComponentProps } from "@headroom-cms/api/react";

function CallToAction({ block }: BlockComponentProps) {
  return (
    <div className="cta-banner">
      <p>{block.props?.text as string}</p>
      <a href={block.props?.url as string}>Learn more</a>
    </div>
  );
}

<BlockRenderer
  blocks={blocks}
  components={{ callToAction: CallToAction }}
/>

Astro

For custom Astro blocks, create your own wrapper around the individual block components. Import and render the built-in components alongside your custom ones:

---
import Paragraph from "@headroom-cms/api/blocks/Paragraph.astro";
import Heading from "@headroom-cms/api/blocks/Heading.astro";
import Image from "@headroom-cms/api/blocks/Image.astro";
// ... other built-in imports
import MyCustomBlock from "../components/MyCustomBlock.astro";

const { blocks, refs, resolveContentLink } = Astro.props;
---

{blocks.map((block) => {
  if (block.type === "myCustomBlock") return <MyCustomBlock block={block} />;
  if (block.type === "paragraph") return <Paragraph block={block} refs={refs} resolveContentLink={resolveContentLink} />;
  if (block.type === "heading") return <Heading block={block} refs={refs} resolveContentLink={resolveContentLink} />;
  if (block.type === "image") return <Image block={block} />;
  // ... handle remaining types
})}

Relationships

Collections can define relationships to other collections. These are populated on single-content responses:

// Forward relationships (e.g. a project's "artists")
const project = await client.getContent("01ABC");
const artists = project.relationships?.artists; // ContentRef[]

// Reverse query: find all projects for an artist
const { items } = await client.listContent("projects", {
  relatedTo: "01ARTIST",
  relField: "artists",
});

Media URLs

Media paths in content responses (block image URLs, cover images, field values) are stored as relative paths like /media/{site}/{mediaId}/original.jpg.

const client = new HeadroomClient({
  url: "https://headroom.example.com",
  site: "mysite.com",
  apiKey: "headroom_xxxxx",
  imageSigningSecret: "your-secret",
});

// Full URL for the original
client.mediaUrl(post.coverUrl);
// → "https://headroom.example.com/media/mysite.com/01ABC/original.jpg"

// Signed transform URL (resized, converted to webp)
client.transformUrl(post.coverUrl, { width: 800, format: "webp" });
// → "https://headroom.example.com/img/mysite.com/01ABC/original.jpg?format=webp&w=800&sig=abc123..."

Transform Options

interface TransformOptions {
  width?: number;                            // Target width in pixels
  height?: number;                           // Target height in pixels
  fit?: "cover" | "contain" | "fill" | "inside" | "outside";
  format?: "webp" | "avif" | "jpeg" | "png"; // Output format
  quality?: number;                          // 1-100
}

Transforms require imageSigningSecret in the client config. Without it, transformUrl() falls back to mediaUrl().

Astro Integration

Content Loader

Use headroomLoader() to load Headroom content into Astro's content layer:

// src/content.config.ts
import { defineCollection } from "astro:content";
import { headroomLoader } from "@headroom-cms/api/astro";

export const collections = {
  posts: defineCollection({
    loader: headroomLoader({ collection: "posts" }),
  }),
  pages: defineCollection({
    loader: headroomLoader({ collection: "pages", bodies: true }),
  }),
};

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | collection | string | — | Headroom collection name | | bodies | boolean | false | Fetch full content bodies (not just metadata) | | config | HeadroomConfig? | from env | Override client config | | schema | ZodType? | — | Zod schema for type-safe data access |

The loader reads config from environment variables by default:

HEADROOM_URL=https://headroom.example.com
HEADROOM_SITE=mysite.com
HEADROOM_API_KEY=headroom_xxxxx
HEADROOM_IMAGE_SIGNING_SECRET=your-secret           # optional

Dev Refresh

Add headroomDevRefresh() to your Astro config for automatic content reloading during development:

// astro.config.mjs
import { headroomDevRefresh } from "@headroom-cms/api/astro";

export default defineConfig({
  integrations: [headroomDevRefresh()],
});

Polls the Headroom API for version changes (default: every 5 seconds) and triggers a content sync when content is updated in the admin UI.

Zod Schema Codegen

Generate type-safe Zod schemas from your Headroom collection definitions:

import { HeadroomClient } from "@headroom-cms/api";
import { generateZodSchemas } from "@headroom-cms/api/codegen";

const client = new HeadroomClient({ /* ... */ });
const code = await generateZodSchemas(client);
// Write `code` to a file (e.g. src/lib/schemas.ts)

This generates a TypeScript file with Zod schemas for each collection, ready to pass to headroomLoader({ schema }). See the sample site for a working example with a generate-schemas.sh script.

Styling

React

Import the default stylesheet:

import "@headroom-cms/api/react/headroom-blocks.css";

Styles use low-specificity :where() selectors, making them easy to override. Customize via CSS custom properties:

| Property | Default | Used by | |----------|---------|---------| | --hr-code-bg | #f3f4f6 | Inline code background | | --hr-link-color | #2563eb | Link color | | --hr-image-radius | 0.5rem | Image border radius | | --hr-caption-color | #6b7280 | Image caption color | | --hr-code-block-bg | #1e1e1e | Code block background | | --hr-code-block-color | #d4d4d4 | Code block text color | | --hr-accent | #2563eb | Checkbox accent color | | --hr-table-header-bg | #f9fafb | Table header background |

Astro

Astro block components render semantic HTML with no built-in styles. Use Tailwind or your own CSS to style the output. The components use standard HTML elements (<p>, <h1><h6>, <ul>, <ol>, <figure>, <table>, etc.) that work naturally with Tailwind's prose class.

TypeScript Types

All types are exported from the main entry point:

import type {
  // Config
  HeadroomConfig,
  TransformOptions,

  // Content
  ContentItem,
  ContentMetadata,
  ContentListResult,
  BatchContentResult,

  // Blocks
  Block,
  InlineContent,
  TextContent,
  LinkContent,
  TextStyles,
  TableContent,
  TableRow,

  // References
  ContentRef,
  PublicContentRef,
  RefsMap,

  // Collections
  Collection,
  CollectionSummary,
  CollectionListResult,
  FieldDef,
  RelationshipDef,

  // Block Types
  BlockTypeDef,
  BlockTypeListResult,
} from "@headroom-cms/api";

React-specific types:

import type { BlockRendererProps, BlockComponentProps, BlockComponentMap } from "@headroom-cms/api/react";

Building & Publishing

pnpm build         # Build all entry points (ESM + CJS + types)
pnpm test          # Run tests
pnpm test:watch    # Run tests in watch mode
pnpm typecheck     # TypeScript type checking
pnpm dev           # Watch mode build

Package Entry Points

| Import path | Format | Description | |-------------|--------|-------------| | @headroom-cms/api | ESM + CJS | API client and types | | @headroom-cms/api/react | ESM + CJS | React block renderer components | | @headroom-cms/api/react/headroom-blocks.css | CSS | Default block styles | | @headroom-cms/api/blocks/* | Astro source | Astro block components (compiled by consumer) | | @headroom-cms/api/astro | ESM | Astro content loader + dev refresh | | @headroom-cms/api/codegen | ESM + CJS | Zod schema generation |

npm Publish

pnpm build
npm publish --access public

The files field in package.json includes only dist/ and blocks/ directories.

License

PolyForm Noncommercial 1.0.0