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

@contentforge/sdk

v0.1.0

Published

TypeScript SDK for ContentForge headless CMS

Readme

@contentforge/sdk

TypeScript SDK for ContentForge headless CMS.

Zero dependencies. SSR-ready. TypeScript-first.

Installation

npm install @contentforge/sdk

Requires Node.js 18+ (native fetch) or any environment with globalThis.fetch.

Quick Start

import { ContentForge } from '@contentforge/sdk'

const cms = new ContentForge({
  url: 'https://cms.yourdomain.com',
  apiKey: 'pk_live_xxx',
  defaultLocale: 'en',
})

// List blog posts
const posts = await cms.entries('blog-post').list({
  sort: '-published_at',
  limit: 10,
})

// Get a single entry by slug
const post = await cms.entries('blog-post').getBySlug('hello-world', {
  locale: 'en',
})

Framework Examples

SvelteKit

// src/routes/blog/[slug]/+page.server.ts
import { ContentForge } from '@contentforge/sdk'
import { CMS_URL, CMS_API_KEY } from '$env/static/private'

const cms = new ContentForge({ url: CMS_URL, apiKey: CMS_API_KEY })

export async function load({ params, request }) {
  const [post, seo] = await Promise.all([
    cms.entries('blog-post').getBySlug(params.slug, { locale: 'en' }),
    cms.entries('blog-post').seo(params.slug, { locale: 'en' }),
  ])

  return { post, seo }
}
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
  import { seoToJsonLd } from '@contentforge/sdk'
  export let data
</script>

<svelte:head>
  <title>{data.seo.meta_title}</title>
  <meta name="description" content={data.seo.meta_description} />
  <link rel="canonical" href={data.seo.canonical_url} />
  {#each data.seo.hreflang as h}
    <link rel="alternate" hreflang={h.locale} href={h.url} />
  {/each}
  {#if seoToJsonLd(data.seo)}
    {@html `<script type="application/ld+json">${seoToJsonLd(data.seo)}</script>`}
  {/if}
</svelte:head>

<article>
  <h1>{data.post.title}</h1>
  {@html data.post.data.body}
</article>

Next.js (App Router)

// app/blog/[slug]/page.tsx
import { ContentForge, seoToJsonLd } from '@contentforge/sdk'

const cms = new ContentForge({
  url: process.env.CMS_URL!,
  apiKey: process.env.CMS_API_KEY!,
})

export async function generateMetadata({ params }) {
  const seo = await cms.entries('blog-post').seo(params.slug, { locale: 'en' })
  return {
    title: seo.meta_title,
    description: seo.meta_description,
    alternates: {
      canonical: seo.canonical_url,
      languages: Object.fromEntries(
        seo.hreflang.map((h) => [h.locale, h.url])
      ),
    },
    openGraph: {
      title: seo.og_title,
      description: seo.og_description,
      images: seo.og_image ? [seo.og_image] : [],
    },
  }
}

export default async function BlogPost({ params }) {
  const post = await cms.entries('blog-post').getBySlug(params.slug)
  const seo = await cms.entries('blog-post').seo(params.slug)
  const jsonLd = seoToJsonLd(seo)

  return (
    <>
      {jsonLd && (
        <script
          type="application/ld+json"
          dangerouslySetInnerHTML={{ __html: jsonLd }}
        />
      )}
      <article dangerouslySetInnerHTML={{ __html: post.data.body }} />
    </>
  )
}

Astro

---
// src/pages/blog/[slug].astro
import { ContentForge, seoToMetaTags } from '@contentforge/sdk'

const cms = new ContentForge({
  url: import.meta.env.CMS_URL,
  apiKey: import.meta.env.CMS_API_KEY,
})

const { slug } = Astro.params
const post = await cms.entries('blog-post').getBySlug(slug!, { locale: 'en' })
const seo = await cms.entries('blog-post').seo(slug!, { locale: 'en' })
const metaTags = seoToMetaTags(seo)
---

<html>
  <head>
    {metaTags.map((tag) => {
      if (tag.tag === 'title') return <title>{tag.content}</title>
      if (tag.tag === 'link') return <link rel={tag.rel} href={tag.href} hreflang={tag.hreflang} />
      return <meta name={tag.name} property={tag.property} content={tag.content} />
    })}
  </head>
  <body>
    <article set:html={post.data.body} />
  </body>
</html>

SEO Helpers

import { seoToMetaTags, seoToJsonLd, seoToHtml } from '@contentforge/sdk'

// Get structured meta tag objects (framework-agnostic)
const tags = seoToMetaTags(seo)
// [{ tag: 'title', content: '...' }, { tag: 'meta', name: 'description', content: '...' }, ...]

// Get JSON-LD string for structured data
const jsonLd = seoToJsonLd(seo)
// '{"@context":"https://schema.org","@type":"Article",...}'

// Get a complete HTML string for <head> injection
const headHtml = seoToHtml(seo)
// '<title>...</title>\n<meta name="description" content="..." />\n...'

Media Helpers

// Get full URL for a media asset
const url = cms.media.getURL(post.data.cover)

// Get a specific variant
const thumb = cms.media.getURL(post.data.cover, { variant: 'thumb' })

// Build responsive srcset
const srcset = cms.media.getSrcSet(post.data.cover, {
  thumb: '400w',
  md: '800w',
  lg: '1200w',
})

// Get blurhash placeholder
const blurhash = cms.media.getBlurhash(post.data.cover)

Locale Detection

import { LocaleClient } from '@contentforge/sdk'

// Detect best locale from Accept-Language header
const locale = LocaleClient.detect(
  request.headers.get('accept-language') ?? '',
  ['en', 'ru', 'de'],
  'en', // fallback
)

// List project locales
const locales = await cms.locales.list()

Caching

The SDK includes an in-memory cache with stale-while-revalidate semantics:

const cms = new ContentForge({
  url: 'https://cms.yourdomain.com',
  apiKey: 'pk_live_xxx',
  cache: {
    maxAge: 60,                // Serve fresh data for 60 seconds
    staleWhileRevalidate: 300, // Serve stale for up to 5 minutes while refreshing
    maxEntries: 100,           // Maximum cached entries
  },
})

// Disable caching
const cms = new ContentForge({
  url: '...',
  apiKey: '...',
  cache: false,
})

// Invalidate cache (e.g. on CMS webhook)
cms.clearCache()

Type Generation (Codegen)

Generate TypeScript types from your content type schemas:

npx contentforge codegen \
  --url https://cms.yourdomain.com \
  --apiKey pk_live_xxx \
  --output src/lib/cms-types.ts

Add to your package.json:

{
  "scripts": {
    "cms:types": "contentforge codegen --url $CMS_URL --apiKey $CMS_API_KEY --output src/lib/cms-types.ts"
  }
}

Use the generated types:

import type { BlogPost } from '$lib/cms-types'

const posts = await cms.entries<BlogPost>('blog-post').list({ locale: 'en' })
// posts.data[0].data.title   → string (autocomplete works)
// posts.data[0].data.category → 'tech' | 'design' | 'business'

Error Handling

import {
  ContentForgeError,
  NotFoundError,
  UnauthorizedError,
} from '@contentforge/sdk'

try {
  const post = await cms.entries('blog-post').getBySlug('missing')
} catch (error) {
  if (error instanceof NotFoundError) {
    // Handle 404
  } else if (error instanceof UnauthorizedError) {
    // Handle 401 — check your API key
  } else if (error instanceof ContentForgeError) {
    console.error(`API error ${error.status}: ${error.message}`)
  }
}

// Or use the global error handler
const cms = new ContentForge({
  url: '...',
  apiKey: '...',
  onError: (error) => {
    console.error(`ContentForge error: ${error.code} — ${error.message}`)
  },
})

SSR / Custom Fetch

Pass a custom fetch function for SSR environments:

// Node.js / Edge runtime
const cms = new ContentForge({
  url: '...',
  apiKey: '...',
  fetch: customFetch, // e.g. undici fetch, or SvelteKit's event.fetch
})

API Reference

ContentForge

| Method | Description | |--------|-------------| | entries<T>(type) | Create a typed entry client for a content type | | seo(type, slug, opts?) | Get SEO metadata for an entry | | navigation(opts?) | Get navigation/menu items | | sitemap(opts?) | Get sitemap data for static generation | | entryLocales(type, slug) | Get available locales for an entry | | clearCache() | Invalidate the in-memory cache |

EntryClient<T>

| Method | Description | |--------|-------------| | list(opts?) | List entries with filtering, sorting, pagination | | getBySlug(slug, opts?) | Get a single entry by slug | | getById(id, opts?) | Get a single entry by ID | | seo(slug, opts?) | Get SEO metadata for an entry | | locales(slug) | Get available locales for an entry |

MediaClient

| Method | Description | |--------|-------------| | getURL(asset, opts?) | Resolve full URL for a media asset | | getVariants(asset) | Get all variant URLs | | getSrcSet(asset, widthMap) | Build responsive srcset string | | getBlurhash(asset) | Get blurhash placeholder string |

LocaleClient

| Method | Description | |--------|-------------| | list() | Get all project locales | | getDefault() | Get the default locale | | detect(header, supported, fallback?) | Detect locale from Accept-Language (static) |

License

MIT