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

@content-island/api-client

v0.23.0

Published

Content Island - REST API Client

Readme

@content-island/api-client

Installation

npm install @content-island/api-client

Authentication

The client is configured with a single accessToken option that is sent on every request as Authorization: Bearer <accessToken>. Content Island supports two kinds of tokens:

  • Read token — grants read-only access to the project. Use it for any get* method (fetching content, listing, sizing, project metadata).
  • Write token — grants both read and write access. Required for any method that creates, updates, or uploads data.

A write token is a strict superset of a read token: it works for everything. A read token only works for the read methods. Calling a write method with a read token returns 403 Forbidden.

import { createClient } from '@content-island/api-client';

// Read-only consumer (e.g. SSG/SSR site fetching content):
const reader = createClient({ accessToken: '<your-read-token>' });

// Authoring tools, ingestion pipelines, admin scripts:
const writer = createClient({ accessToken: '<your-write-token>' });

Token required per method

| Method | Token required | | ------------------------- | ----------------- | | getProject | Read or Write | | getContentList | Read or Write | | getContent | Read or Write | | getRawContentList | Read or Write | | getRawContent | Read or Write | | getContentListSize | Read or Write | | createContent | Write | | updateContentFieldValue | Write | | uploadMedia | Write |

Examples

Basic Usage

// Your model
interface Post {
  id: string;
  title: string;
  body: string;
  order: number;
  language: 'es' | 'en';
}
import { createClient } from '@content-island/api-client';

const client = createClient({ accessToken: <your-token>});

const postsWithDefaultLanguage = client.getContentList<Post>({ contentType: 'post'}); // Retrieve the list of contents in the project filtered by content type, for example 'post' in the
const englishPosts = client.getContentList<Post>({ contentType: 'post', language: 'en'}); // Get english posts
const spanishPosts = client.getContentList<Post>({ contentType: 'post', language: 'es'}); // Get spanish posts

// Or you can retrieve a content by id
const postById = client.getContent<Post>({ id: 'content-id', language: 'es' }); // Retrieve a content by id
const postBySomeField = client.getContent<Post>({ 'fields.title': 'post-title', language: 'es' }); // Retrieve a content by field value

Snapshot mode

The client can serve content reads from a content snapshot — a single JSON document exported from your project — instead of hitting the network on every request. This is the recommended way to consume content from a static site generator (Astro, Next.js, Gatsby, …): you export the snapshot once at build time and the build reads from it with zero API round-trips.

There are two modes:

  • 'api' (default) — every read is a network request to the B2B API, exactly as before.
  • 'snapshot' — reads are served from a snapshot file loaded from disk, with no network request. The snapshot engine reproduces the live API's filtering, sorting, pagination, language fallback, and related-content resolution, so the results are identical to api mode over the same data.

Configuration

import { createClient } from '@content-island/api-client';

const client = createClient({
  accessToken: '<your-read-token>',
  mode: 'snapshot',
  // snapshotPath omitted — defaults to DEFAULT_SNAPSHOT_PATH ('./content-island-snapshot.json')
});
  • accessToken is required in all modes. (In snapshot mode it still identifies the client; reads do not use the network, but the token is part of the client contract.)
  • mode defaults to 'api'. Set it to 'snapshot' to serve reads from the snapshot.
  • snapshotPath is client-level and independent of mode: it points at the snapshot file. It is optional — when omitted it defaults to DEFAULT_SNAPSHOT_PATH ('./content-island-snapshot.json'), a constant exported from the package and shared with the CLI's --snapshot-path default. An 'api'-mode client may still set snapshotPath (for example, to expose getSnapshotInfo() while reading live).
import { createClient, DEFAULT_SNAPSHOT_PATH } from '@content-island/api-client';

// Zero-config: export to the default location, then read it back with no path.
//   npx content-island export --access-token <your-read-token>
// writes ./content-island-snapshot.json, which this client resolves automatically:
const client = createClient({ accessToken: '<your-read-token>', mode: 'snapshot' });

snapshotPath (and the DEFAULT_SNAPSHOT_PATH default) is cwd-relative — it is resolved against the process working directory at read time. In an SSR/serverless runtime where the cwd is not the snapshot's location, pass an absolute path, e.g. snapshotPath: path.resolve(process.cwd(), 'content-island-snapshot.json').

If a read resolves to 'snapshot' mode on a client created without snapshotPath, it loads DEFAULT_SNAPSHOT_PATH — there is no "snapshotPath required" error. If no readable, valid snapshot exists at the resolved path, the load rejects with an ApiClientError whose message names that path.

Per-query mode override

The five content reads — getContentList, getContent, getRawContentList, getRawContent, and getContentListSize — accept a per-query mode that takes precedence over the client-level mode for that call only:

effective mode = per-query mode ?? client-level mode ?? 'api'
// An api-mode client that occasionally reads from a snapshot:
const client = createClient({
  accessToken: '<your-read-token>',
  snapshotPath: './content-island-snapshot.json',
});

const live = await client.getContentList<Post>({ contentType: 'post' }); // network
const fromSnapshot = await client.getContentList<Post>({
  contentType: 'post',
  mode: 'snapshot', // this call only — served from the snapshot, no network
});
// A snapshot-mode client that occasionally needs fresh data:
const client = createClient({
  accessToken: '<your-read-token>',
  mode: 'snapshot',
  snapshotPath: './content-island-snapshot.json',
});

const fromSnapshot = await client.getContentList<Post>({ contentType: 'post' }); // snapshot
const live = await client.getContentList<Post>({
  contentType: 'post',
  mode: 'api', // this call only — hits the network
});

getProject is routed by the client-level mode only — it takes no query params, so there is no per-query override.

mode is a client-only key: it is never serialized into the request query string in api mode.

Related-content metadata: onRelatedContentMeta

The five content reads also accept an optional per-query callback, onRelatedContentMeta, invoked exactly once with the related-content resolution metadata for that call:

await client.getContentList<Post>({
  contentType: 'post',
  includeRelatedContent: 'all',
  onRelatedContentMeta: ({ resolvedDepth, partial }) => {
    // resolvedDepth: how deep the related-content BFS actually resolved
    // partial: true when a depth or budget cap left part of the graph unresolved
    console.log({ resolvedDepth, partial });
  },
});

It works in both modes — sourced from the X-Related-Content-Resolved-Depth / X-Related-Content-Partial response headers in api mode, and from the engine's BFS result in snapshot mode, with identical values for the same data and query. When the callback is omitted, behavior and return shapes are unchanged. Like mode, it is never serialized into the request query string.

Writes are not available in snapshot mode

A snapshot-mode client serves reads only. Every write/management method (createContent, publishContent, updateContentFieldValue, uploadMedia, createModel, updateModel, deleteModel, createEnum, updateEnum, deleteEnum) rejects with an ApiClientError (code SNAPSHOT_MODE) and performs no network call. There is no per-query override for writes — on a snapshot-mode client they always reject. If you need to read from a snapshot and write, create a second, separate api-mode client for the writes:

const reader = createClient({
  accessToken: '<your-read-token>',
  mode: 'snapshot',
  snapshotPath: './content-island-snapshot.json',
});

const writer = createClient({ accessToken: '<your-write-token>' }); // api mode

getSnapshotInfo()

getSnapshotInfo() resolves with the snapshot's meta block — useful for freshness checks (e.g. logging when the snapshot was produced during a build):

const meta = await client.getSnapshotInfo();
// {
//   schemaVersion: 1,
//   exportedAt: '2026-06-12T10:00:00.000Z', // ISO-8601
//   projectId: '...',
//   view: 'published' | 'preview',
// }

It works on any client, regardless of the client-level mode (so an api-mode client can report on the snapshot it has on disk). On a client without snapshotPath, it loads DEFAULT_SNAPSHOT_PATH; if no readable, valid snapshot exists at the resolved path, it rejects with an ApiClientError whose message names that path.

Exporting a snapshot

CLI: content-island export

The package ships a content-island binary that downloads a snapshot and writes it to disk as plain JSON:

npx content-island export --access-token <your-read-token> --snapshot-path ./content-island-snapshot.json

The flags map 1:1 (kebab-case) onto the matching exportSnapshot option:

| Flag | exportSnapshot option | Type | Description | Default | | ---------------------- | ----------------------- | ------- | ---------------------------------------------------------------------------------------------- | -------------------------------- | | --access-token | accessToken | string | Access token (required). Falls back to the CONTENT_ISLAND_ACCESS_TOKEN environment variable. | — | | --snapshot-path | snapshotPath | string | Path to write the snapshot JSON. | ./content-island-snapshot.json | | --domain | domain | string | Target domain (self-hosted/staging). Respects the client's default-domain resolution. | client default | | --no-secure-protocol | secureProtocol | boolean | Use HTTP instead of HTTPS (for local/self-hosted http targets). | HTTPS (secure) | | --secure-protocol | secureProtocol | boolean | Use HTTPS explicitly (redundant — this is already the default). | HTTPS (secure) | | --api-version | apiVersion | string | API version segment to target. | client default |

The request is HTTPS by default. Pass --no-secure-protocol to use plain HTTP when targeting a local or self-hosted http endpoint:

# Export from a local http instance (e.g. a dev server on localhost):
npx content-island export \
  --access-token <your-read-token> \
  --domain localhost:8082 \
  --no-secure-protocol \
  --snapshot-path ./content-island-snapshot.json

The exported view is token-driven: a PREVIEW_-prefixed token exports the preview (draft) view, any other token exports the published view. There is no view flag.

On success the CLI prints a summary and exits 0:

Content snapshot exported successfully.
  Output:     ./content-island-snapshot.json
  Size:       1.42 MB
  Exported:   2026-06-12T10:00:00.000Z
  View:       published

If the token is missing, or the request/validation fails, the CLI writes the error to stderr and exits non-zero — and because the write goes through a temp-file-then-rename, no partial or invalid file is left at --snapshot-path.

Programmatic: exportSnapshot

exportSnapshot is the function the CLI is built on; use it directly from a Node script when you need more control:

import { exportSnapshot } from '@content-island/api-client';

const snapshot = await exportSnapshot({
  accessToken: process.env.CONTENT_ISLAND_ACCESS_TOKEN!,
  snapshotPath: './content-island-snapshot.json', // optional — omit to just get the parsed snapshot
  // domain, secureProtocol, apiVersion are also accepted
});

console.log(snapshot.meta.exportedAt, snapshot.contents.length);

It fetches the snapshot, validates its shape and schema version, optionally writes it to snapshotPath (same safe temp-then-rename strategy as the CLI), and resolves with the parsed ContentSnapshot. A non-2xx response (including 429, which maps to code RATE_LIMITED) rejects with the standard ApiClientError.

Size guidance

The snapshot is a single JSON document loaded fully into memory by the snapshot-mode client. The practical limit is around 20 MB of uncompressed JSON on disk. Above that threshold the CLI prints a warning (it still exits 0): snapshots this large are loaded entirely into memory and may approach upstream request/body and timeout limits. If you cross it, consider narrowing the exported content or keeping the snapshot well under the threshold.

Recommended workflow

Use mode: 'api' in local development (always fresh, no export step) and mode: 'snapshot' in production builds (fast, no per-request network calls). Switch between them with an environment variable so the same code runs in both:

import { createClient } from '@content-island/api-client';

const client = createClient({
  accessToken: process.env.CONTENT_ISLAND_ACCESS_TOKEN!,
  mode: process.env.NODE_ENV === 'production' ? 'snapshot' : 'api',
  snapshotPath: './content-island-snapshot.json',
});

In production builds, generate the snapshot first (e.g. as a build step) and then run the build, which reads from it in snapshot mode.

GitHub Action snippet

This step exports the snapshot with the CLI before building the site:

name: Build site

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 24

      - name: Install dependencies
        run: npm ci

      - name: Export content snapshot
        env:
          CONTENT_ISLAND_ACCESS_TOKEN: ${{ secrets.CONTENT_ISLAND_ACCESS_TOKEN }}
        run: npx content-island export

      - name: Build (snapshot mode)
        env:
          NODE_ENV: production
        run: npm run build

The token is read from CONTENT_ISLAND_ACCESS_TOKEN, so it never appears in the command line or logs. The build then reads from ./content-island-snapshot.json in snapshot mode.

Documentation

For more detailed documentation, please refer to the Content Island API Client documentation.