Atlas CMS
TypeScript SDK

TypeScript SDK

Typed delivery client for Atlas CMS — read entries, pages, and media with full type safety instead of hand-writing fetch calls.

TypeScript SDK

@latellu/atlas-sdk is a small, typed client for the Atlas Public API (/api/v1/public/*). It does the repetitive work the Quickstart does by hand:

  • builds the request URL and attaches your X-API-Key header,
  • unwraps the { success, data, meta } response envelope,
  • parses each entry's data field (the API sends it as a JSON string) into a real object,
  • merges the requested locale's translation over the base content,
  • turns 404s into null and every other failure into a typed AtlasError.

Paired with the generated types from @latellu/atlas-cli, your content types, slugs, and entry data are all type-checked at compile time. See Type Generation.

Two packages, one workflow

@latellu/atlas-sdk is the runtime client. The types it uses come from @latellu/atlas-cli, which generates an atlas.types.ts file from your workspace schema. You can use the SDK without the CLI, but then slugs are unconstrained and data is unknown. See Type Generation.

Install

Terminal
npm install @latellu/atlas-sdk
# Generate the matching types for your workspace (recommended):
npx @latellu/atlas-cli generate --api-key=atlas_live_abc123xyz --output=./src

Requires Node 18+ (the SDK uses the global fetch). For older runtimes, pass your own fetch implementation via the fetchImpl option below.

Create a client

src/atlas.ts
import { createClient } from '@latellu/atlas-sdk';
import type { AtlasContentTypes } from './atlas.types'; // generated by @latellu/atlas-cli

export const atlas = createClient<AtlasContentTypes>({
  url: 'https://api.atlas.latellu.com',
  apiKey: process.env.ATLAS_API_KEY!,
});

createClient<TSchema>(config) returns a client whose entries() method is constrained to the content-type slugs in TSchema. Omit the type parameter and the client still works, but slugs accept any string and entry data is typed as unknown.

Config options

OptionTypeRequiredDescription
urlstringYesAtlas backend base URL, e.g. https://api.atlas.latellu.com. Trailing slashes are trimmed; do not include /api/v1/public — the SDK adds it.
apiKeystringYesWorkspace API key. The workspace is derived from the key, so you never pass a workspace id.
fetchImpltypeof fetchNoCustom fetch, useful for tests or non-standard runtimes. Defaults to the global fetch.

createClient throws AtlasError immediately if url or apiKey is missing.

Entries

atlas.entries(type) returns a resource scoped to one content type. When you pass AtlasContentTypes, the data of every entry is typed as that content type's interface.

List entries

List articles
const { items, total, page, pageSize } = await atlas
  .entries('article')
  .list({ locale: 'en', page: 1, limit: 20, sort: 'published_at:desc' });

for (const entry of items) {
  console.log(entry.slug, entry.data.title); // entry.data is typed as Article
}

list(options?) returns a ListResult:

FieldTypeDescription
itemsAtlasEntry<T>[]The entries on this page, with data already parsed.
totalnumberTotal matching entries across all pages.
pagenumberThe current page number.
pageSizenumberNumber of entries per page.

List options:

OptionTypeDescription
localestringLocale to resolve content into, e.g. "en" or "id".
pagenumber1-based page number.
limitnumberEntries per page.
sortstringSort expression field:direction, e.g. "published_at:desc" or "title:asc".

Get one entry

Get an article by slug
const post = await atlas
  .entries('article')
  .get('getting-started-with-headless-cms', { locale: 'id' });

if (post === null) {
  // Not found (HTTP 404)
} else {
  console.log(post.data.title); // Indonesian title merged over the base
}

get(slug, options?) returns a single AtlasEntry<T> or null when the entry does not exist. When you pass locale, the matching translation is merged field-by-field over the base data: translated fields win, untranslated fields fall back to the base value.

Every entry has this shape:

FieldTypeDescription
idstringEntry id.
slugstringURL slug.
statusstringpublished, draft, or archived. The Public API returns published entries.
published_atstring | nullISO timestamp, or null if never published.
dataTThe parsed entry data, typed as your content type.

Pages

Pages are layout documents made of ordered blocks plus SEO metadata — see the Pages guide for the concept.

List and render a page
// Lightweight rows — no blocks, good for building a sitemap or nav.
const { items } = await atlas.pages.list({ locale: 'en' });

// Full page with SEO + blocks resolved for the locale.
const home = await atlas.pages.get('home', { locale: 'en' });
if (home) {
  document.title = home.seo.title ?? 'Home';
  for (const block of home.blocks) {
    // blocks are sorted by `position`
    renderBlock(block.type, block.data);
  }
}
  • pages.list(options?)ListResult<AtlasPageSummary> (id, slug, status — no blocks). Same locale / page / limit options as entries.
  • pages.get(slug, options?)AtlasPage | null. Resolves seo (base overlaid with the locale's seo_translations) and blocks (each block's data overlaid with its locale translation), and returns blocks sorted by position.

An AtlasPage has id, slug, status, seo (AtlasPageSEO: title, description, keywords, og_image, canonical, …), and blocks (AtlasBlock[]: id, type, position, data).

Media

Resolve a media asset
const asset = await atlas.media.get(mediaId);
if (asset) {
  console.log(asset.id);
}

media.get(id) returns a MediaAsset ({ id, ...rest }) or null on 404. The asset is returned as-is from the delivery API.

Error handling

There are two distinct "not found" behaviors, by design:

  • get() methods (entries().get, pages.get, media.get) return null for a 404, so a missing-by-slug lookup is a normal value, not an exception.
  • Everything else — network failures, auth errors (401/403), rate limits (429), validation errors, and 5xx — throws an AtlasError.
Handling errors
import { AtlasError } from '@latellu/atlas-sdk';

try {
  const { items } = await atlas.entries('article').list();
  // ...
} catch (err) {
  if (err instanceof AtlasError) {
    console.error(err.status, err.code, err.message); // e.g. 401, "unauthorized", "..."
  } else {
    throw err;
  }
}

AtlasError exposes:

PropertyTypeDescription
messagestringThe API's message, or a transport-level description.
statusnumber | undefinedHTTP status code. Absent for network/transport errors.
codestring | undefinedThe API's machine-readable code, when present.

See Errors for the full list of API status codes and their meanings.

Escape hatch: raw

For public delivery endpoints not yet wrapped by a resource, use the low-level requester. It applies the same base URL, API key, envelope unwrapping, and error handling:

Low-level request
const { data, meta } = await atlas.raw.get<MyShape>('/some/public/path', { locale: 'en' });

The path is relative to /api/v1/public, so '/entries' hits /api/v1/public/entries.

Current limitations

Draft preview is not wrapped yet

The SDK reads published content. Previewing a draft entry requires a preview token from the admin/dashboard side, which the SDK does not mint or consume yet. Until then, call the GET /public/preview endpoint directly (see the Entry API Reference).

  • Localizable fields are typed as their base value in this release. The locale merge still happens at runtime; only the type does not yet distinguish per-locale shapes.

On this page