> [Agent navigation index](/llms.txt)

# Contentful Clients

Use typed Delivery and Management clients for published or draft reads, content-model inspection, caching, retries, and incremental synchronization.

## Configure Delivery or Preview reads

Create a request-derived service so credentials and the configured server cache follow the request context:

```ts
import { createContentfulClient } from "brix/contentful";
import { brixServer } from "#brix/server";
import type { ContentTypeMap } from "#/contentful-types.ts";

export const getContentful = brixServer.createService((c) =>
  createContentfulClient<ContentTypeMap>({
    spaceId: c.env.CONTENTFUL_SPACE_ID,
    accessToken: c.env.CONTENTFUL_ACCESS_TOKEN,
    environment: c.env.CONTENTFUL_ENVIRONMENT,
    cache: brixServer.dataCache(c).public("1h"),
  }),
);

```

`environment` defaults to `master`; `host` defaults to `cdn.contentful.com`. Set `host: "preview.contentful.com"` for draft reads. The equivalent EU Delivery and Preview hosts are supported. Non-loopback custom hosts must use HTTPS and require `allowCustomHostWithAccessToken: true`; reserve that opt-in for a trusted proxy. Loopback HTTP URLs are accepted for local mocks.

Invalid host or token policy throws while creating the client. Request methods return typed `ResultPromise` values, so use `.unwrap()`, `.unwrapOr()`, `.map()`, or `.match()` for expected outcomes.

## Query entries and assets

```ts
const contentful = getContentful(c);
const page = await contentful.getEntry("page", pageId, { locale: "en-GB" }).unwrap();
const hotel = await contentful
  .findEntry("hotel", { "fields.slug": slug, locale: "en-GB" })
  .unwrap();
const hotels = await contentful
  .getEntries("hotel", {
    locale: "en-GB",
    limit: 20,
    skip: 0,
    order: "-sys.createdAt",
    include: 3,
    "fields.city": "stockholm",
    "fields.stars[gte]": 4,
    "fields.amenities[in]": ["pool"],
  })
  .unwrap();
const asset = await contentful.getAsset("asset-id").unwrap();

```

`getEntry` and `findEntry` return `ContentfulEntryNotFoundError` when absent. Collections preserve `total`, `skip`, `limit`, and includes metadata. Typed options cover locale, pagination, selection, ordering, full-text search, include depth, equality, range, list, match, existence, and known linked-field filters.

Entry reads automatically resolve included entry and asset links while preserving metadata. Missing links become `null`, and cycles are handled. Sync remains unresolved by design.

Delivery calls retry 429, 500, 502, 503, and 504 by default, making three total attempts and honoring Contentful retry headers. Override the client `retry` option when the application's latency budget requires a different policy.

## Cache Delivery reads

The `cache` option covers entry, collection, find, and asset reads. Cache identity includes the host, space, environment, credentials, locale, and complete query. Bypass an individual read with `{ cache: false }`.

Preview, sync, and Management calls never use this cache. `invalidateCache()` invalidates all native Delivery reads for that host, space, and environment. Use a shared store for replicas and [verified webhooks](/docs/contentful-webhooks) to trigger invalidation. This does not purge edge pages, browser data, or Contentful's CDN; see [caching](/docs/caching).

## Synchronize content incrementally

```ts
const initial = await contentful.sync(undefined, { type: "Entry", limit: 100 }).unwrap();
await saveSyncToken(initial.nextSyncToken);

const delta = await contentful.sync(initial.nextSyncToken, { limit: 100 }).unwrap();

```

`sync` follows all continuation pages and returns raw entries, assets, deletion stubs, and `nextSyncToken`. `type` applies only to the initial sync; a continuation token already carries its scope. The page `limit` is preserved across requests.

Use `syncPage` only when the caller must own pagination:

```ts
let page = await contentful.syncPage({ type: "Entry", limit: 100 }).unwrap();
while (page.nextPageToken) {
  page = await contentful.syncPage({ syncToken: page.nextPageToken, limit: 100 }).unwrap();
}

```

Persist only the final `nextSyncToken`. Narrow mixed resources with the guards described in [type generation](/docs/contentful-typegen).

## Inspect models with the Management client

Use the CMA client in server-side scripts, never in browser code:

```ts
import { createContentfulCmaClient } from "brix/contentful";
import { loadEnv } from "#brix/env/script";

const env = await loadEnv();
const cma = createContentfulCmaClient({
  spaceId: env.CONTENTFUL_SPACE_ID,
  accessToken: env.CONTENTFUL_CMA_TOKEN,
  environment: "master",
  includeDrafts: true,
});

const contentTypes = await cma.getContentTypes().unwrap();

```

Pagination is automatic. Omit `includeDrafts` to read published models; set it to `true` to include unpublished content types.
