Content Client SDK

The Content Client (ContentClient from @uniformdev/canvas) provides programmatic access to headless content entries stored in Uniform. Use it for server-side data fetching, search indexing, custom API routes, or any scenario where you need to query content outside of the standard composition rendering pipeline.

note

Within the App Router SDK's composition rendering flow, content is resolved automatically. The Content Client is for use cases where you need to access content directly -- such as building sitemaps, search indexes, custom API endpoints, or fetching content that isn't part of a composition.


The Content Client is part of the @uniformdev/canvas package:

npm install @uniformdev/canvas

When using the Content Client outside the App Router SDK (e.g., in scripts or API routes):

import { ContentClient } from "@uniformdev/canvas"; const contentClient = new ContentClient({ apiKey: process.env.UNIFORM_API_KEY, apiHost: process.env.UNIFORM_CLI_BASE_URL || "https://uniform.global", projectId: process.env.UNIFORM_PROJECT_ID, });

When using the App Router SDK, you can use the pre-configured getCanvasClient for composition access:

import { getCanvasClient } from "@uniformdev/next-app-router"; const canvasClient = getCanvasClient({ cache: { type: "no-cache" }, });

const response = await contentClient.getEntries({ type: "blogPost", }); // response.entries is an array of content entries for (const entry of response.entries) { console.log(entry.entry.name, entry.entry.fields); }
const response = await contentClient.getEntries({ type: "blogPost", limit: 20, offset: 0, }); console.log(`Total entries: ${response.totalCount}`);

The Content Client supports advanced filtering using a structured query syntax. Filters are passed as key-value pairs where the key includes the field path and operator.

filters.<field>[<operator>]=<value>
  • field: The field to filter by. Use type for content type, fields.<fieldName> for custom fields, or system properties like name, slug, _created, _modified.
  • operator: The comparison operator.
  • value: The literal value to compare against.
const response = await contentClient.getEntries({ filters: { "type[eq]": "brand", "fields.brandName[match]": "adidas", }, });
OperatorDescription
eqEquals
neqNot equal
matchContains substring
startsStarts with
inMatches any value in a comma-separated list
ninDoes not match any value in a list
OperatorDescription
eqEqual to
neqNot equal to
gtGreater than
gteGreater than or equal to
ltLess than
lteLess than or equal to
OperatorDescription
eqEquals a specific date
gtAfter a specific date
gteOn or after
ltBefore a specific date
lteOn or before
OperatorDescription
eqEquals
neqNot equal
defField is defined (has a value)

For reference fields, you can filter by the referenced entry's properties:

const response = await contentClient.getEntries({ filters: { "type[eq]": "eventSession", "fields.speaker.slug[eq]": "jane-doe", }, });

Filterable reference properties: name, slug, uiStatus, type.

Combine multiple filters to narrow results:

const response = await contentClient.getEntries({ filters: { "type[eq]": "product", "fields.category[eq]": "electronics", "fields.price[gte]": 50, "fields.price[lte]": 500, }, });

The Canvas Client supports the same filtering syntax for compositions, but uses parameters instead of fields:

import { getCanvasClient } from "@uniformdev/next-app-router"; const canvasClient = getCanvasClient({ cache: { type: "no-cache" }, }); const response = await canvasClient.getCompositions({ filters: { "type[eq]": "landingPage", "parameters.heroTitle[match]": "summer", }, });

A common use case for the Content Client is building a search index. Here is a pattern that retrieves all compositions via the project map and extracts text content for indexing:

import { RouteClient } from "@uniformdev/canvas"; import { ProjectMapClient } from "@uniformdev/project-map"; const projectMapClient = new ProjectMapClient({ apiKey: process.env.UNIFORM_API_KEY, projectId: process.env.UNIFORM_PROJECT_ID, }); const routeClient = new RouteClient({ apiKey: process.env.UNIFORM_API_KEY, projectId: process.env.UNIFORM_PROJECT_ID, edgeApiHost: "https://uniform.global", }); async function buildSearchIndex() { // 1. Get all project map nodes with compositions const { nodes } = await projectMapClient.getNodes({ projectMapId: "your-project-map-id", }); const compositionNodes = (nodes ?? []).filter((node) => node.compositionId); // 2. Fetch each composition and extract text const indexData = await Promise.all( compositionNodes.map(async (node) => { const route = await routeClient.getRoute({ path: node.path, projectMapId: "your-project-map-id", state: 0, // published state }); if (route.type !== "composition") return null; const { composition } = route.compositionApiResponse; const title = composition.parameters?.pageTitle?.value as string; return { path: node.path, title, compositionId: node.compositionId, }; }) ); return indexData.filter(Boolean); }

note

For large sites, consider running index rebuilds outside the Next.js build process to avoid build timeout limits. You can trigger rebuilds via a webhook when content is published.


  • Nested object search is not supported: You cannot filter by a parameter of a component within a composition, or by a field of a block within an entry.
  • Faceting is available for numeric and short text fields, but requires specifying a single content type filter.
ExportPackageDescription
ContentClient@uniformdev/canvasClient for fetching content entries
CanvasClient@uniformdev/canvasClient for fetching compositions
RouteClient@uniformdev/canvasClient for route resolution
getCanvasClient@uniformdev/next-app-routerPre-configured canvas client (server-only)
getRouteClient@uniformdev/next-app-routerPre-configured route client (server-only)