Content API clients
The Content API clients in @uniformdev/canvas let you read and write Uniform content (compositions, entries, component definitions, content types, and routes). The most common use case is reading content for frontend applications such as Next.js apps. You can also call them from any server-side context: an automation handler, a CLI, a script, or a backend service.
Pick a delivery or a management client based on what you need to do:
- Delivery clients fetch content from Uniform's Edge Delivery APIs. Use them to render or serve compositions, entries, and routes. They are read-only.
- Management clients create, update, publish, and delete content through Uniform's Management APIs. Use them from automations, CLIs, and backend services.
Minimum SDK version
The clients on this page require @uniformdev/canvas version 20.74.7 or later. If you pass them to helpers from other Uniform packages (for example @uniformdev/tms-sdk), upgrade those packages to the same version.
The clients#
| Client | Mode | Methods | Host / shape |
|---|---|---|---|
CompositionDeliveryClient | delivery | get list | edge, resolved |
CompositionManagementClient | management | get list save saveAndPublish unpublish remove history | origin, canonical |
EntryDeliveryClient | delivery | get list | edge, resolved |
EntryManagementClient | management | get list save saveAndPublish unpublish remove history | origin, canonical |
ComponentDefinitionClient | management | get list save remove | origin |
ContentTypeClient | management | get list save remove | origin |
RouteClient | delivery | get | edge, resolved |
Constructing a client#
Supply projectId and either apiKey or bearerToken:
Delivery clients additionally accept edgeApiHost (defaults to https://uniform.global) and disableSWR, which sends x-disable-swr to skip stale-while-revalidate on data-resource caches. Management clients accept apiHost (defaults to https://uniform.app). For EU projects, see Configure SDKs for EU.
Reading#
Select what to read with a selector, then layer read options on top.
stateis optional and has a per-mode default. Delivery defaults to published, management defaults to draft.- Editions are derived, not asked for. An edition is a locale-targeted variant of a composition or entry. On a management
get, a bare id returns that composition or entry (raw). Passing alocaleresolves the locale-active edition (auto). Passing aneditionIdfetches that edition. That way a later save targets the same entity you just read. You can forceeditions: 'raw' | 'auto'if you need to. Onlist, pass the fulleditions: 'auto' | 'all' | 'raw'enum.
Filtering#
list on the entry and composition clients supports structured filtering. Filters are passed as key-value pairs where the key includes the field path and operator.
Filter syntax#
field: The field to filter by. Usetypefor content type,fields.<fieldName>for custom fields on entries,parameters.<parameterName>for composition parameters, or system properties likename,slug,created,modified.operator: The comparison operator.value: The literal value to compare against.
Example: filter by content type and field value#
The SDK accepts each filter in two equivalent forms: a flat key that includes the operator, or a nested object keyed by operator. Use whichever reads better; the examples on this page use the flat form.
Available operators#
| Operator | Description |
|---|---|
eq | Equals |
neq | Not equal |
match | Contains (text search) match |
starts | Starts with. Value limited to letters, numbers, _, ., -, and spaces |
lt / lte | Less than / less than or equal to |
gt / gte | Greater than / greater than or equal to |
in | Matches any value in a comma-separated list (OR) |
nin | Does not match any value in a list |
all | List-valued fields (multi-select fields, labels, labelGroups) must contain every value in a comma-separated list (AND) |
def | true or false; whether the field has a value at all |
Supported operators by field#
Not every operator is valid for every field; the allowed set depends on the field's type. An unsupported combination returns a 400 error listing the supported operators.
Metadata fields (entries and compositions)#
| Field | Supported operators |
|---|---|
entityId, type, uiStatus, locale, creatorSubject, authorSubject | eq, neq, in, nin |
editionId, releaseId, patternId, workflowId, workflowStageId, categoryId | eq, neq, in, nin, def |
created, modified | eq, neq, lt, lte, gt, gte, in, nin |
name, slug | match, starts, eq, neq, in, nin, def |
creator, author | match, starts, eq, neq, in, nin |
projectMapId, projectMapNodeId (compositions) | eq, neq, in, nin, def |
labels, labelGroups | eq, neq, in, nin, all, def |
Content fields (fields.*)#
| Field type | Supported operators |
|---|---|
| Text, select | match, starts, eq, neq, in, nin, def |
| Number, date, datetime | eq, neq, lt, lte, gt, gte, in, nin, def |
| Checkbox | eq, neq, def |
| Multi-select | eq, neq, in, nin, all, def |
| Rich text | match, starts, def |
| Reference, asset (by ID) | eq, neq, in, nin, def |
Sub-properties of reference, asset, and link fields#
Reference, asset, and link fields can also be filtered by sub-properties of the item they point to (for example fields.speaker.slug). Text-valued sub-properties take the text operators; ID-valued sub-properties take the same operators as reference fields:
| Sub-property | Supported operators |
|---|---|
Reference .name, .slug; asset .url, .title, .description; link path | match, starts, eq, neq, in, nin, def |
Reference .type, .uiStatus; link .type, .projectMapNodeId; asset .mediaType | eq, neq, in, nin, def |
Filtering reference fields#
For reference fields, you can filter by the referenced entry's properties:
Combining multiple filters#
Combine multiple filters to narrow results:
Filtering compositions#
Composition list uses the same operators, with parameters in place of fields:
Selecting fields with data projection#
The select option fetches a subset of each entry or composition instead of the whole thing. The API prunes fields, field types, and slots before values are resolved, so pruned content skips asset resolution, rich text reference expansion, and data resource fetches — reducing both payload size and response time.
select accepts a ProjectionSpec object, which the client serializes into select.* query parameters (mirroring the filters.* syntax). The full wire-level projection syntax is documented in the entries endpoint OpenAPI specification.
Every article in the response contains only its title and coverImage fields. All other fields (for example body, tags, and author references) are removed. System properties such as _id, _name, and type stay in the response.
Projection buckets#
A projection spec has three buckets:
| Bucket | Selects by | Example |
|---|---|---|
fields | Field name | fields: { only: ["title", "slug"] } |
fieldTypes | Field type ID (e.g. text, richText, asset) | fieldTypes: { except: ["richText"] } |
slots | Slot name (compositions only) | slots: { only: ["hero"] } |
fields operators#
| Operator | Type | Description |
|---|---|---|
only | string[] | Keep only fields whose name matches one of these patterns |
except | string[] | Drop fields whose name matches one of these patterns |
locales | string[] | For matching fields that survive filtering, return the full per-locale value map instead of only the requested locale's value |
blockDepth | number or "preserveAll" | Limit how many levels of block field children are kept. 0 removes all block fields; "preserveAll" prevents projection from trimming fields inside block children |
fieldTypes operators#
| Operator | Type | Description |
|---|---|---|
only | string[] | Keep only fields of the named types |
except | string[] | Drop fields of the named types |
slots operators#
| Operator | Type | Description |
|---|---|---|
only | string[] | Keep only the named slots |
except | string[] | Drop the named slots |
depth | number | Limit how many slot levels are kept, counted from the root. 0 removes all slots on the root; 1 keeps the root's slots but not the slots of their children |
named | { [slotName]: { depth } } | Per-slot depth caps; override the container-wide depth for that slot |
Projection behavior#
- Wildcards: values accept a single
*wildcard matching zero or more characters —seo_*,*Title, andmeta*Publishedare all legal. - Recursive by default: projection applies at every component and block in the returned tree, not just the root, and is forwarded into entries resolved through reference fields.
- Exclusion wins: when operators combine, all
onlysets are intersected first, thenexceptsets are subtracted. If rules contradict, the exclusion applies. - Unknown names are silent no-ops: asking for a field a node doesn't have produces an empty field bag, not an error. The tree shape is preserved; non-matching content is simply absent.
- Empty
onlystrips everything:fields: { only: [] }removes every field;slots: { only: [] }flattens the component tree.except: ["*"]is equivalent. - Depth resets across references:
depthandblockDepthcount nesting within a single fetched tree and reset inside referenced entries.
Example: strip rich text everywhere#
Drop rich text fields you can't render, without needing to know their names — including inside entries resolved through reference fields:
Example: per-locale slugs for hreflang tags#
Fetch a lean payload but keep every locale's value on the slug field:
The select option is available on get and list of the entry and composition clients (on compositions, the slots bucket controls the component tree), and on RouteClient.get.
Writing#
Writing is available on the management clients only.
Use an AI coding assistant
The uniform-sdk agent skill teaches coding assistants such as Claude Code, Codex, Cursor, and Copilot how to authenticate with Uniform, configure the CLI, resolve routes, and read and write compositions, entries, and content types with the content API clients. To install it, see Uniform agent skills.
Migrating from the old clients#
The first migration decision for every call site is delivery or management? If the code reads to render or serve, use delivery. If it reads in order to mutate and save, or writes at all, use management.
| Old | New |
|---|---|
CanvasClient (compositions) | CompositionDeliveryClient + CompositionManagementClient |
CanvasClient (component definitions) | ComponentDefinitionClient |
ContentClient (entries) | EntryDeliveryClient + EntryManagementClient |
ContentClient (content types) | ContentTypeClient |
Uncached*Client | bypassCache: true |
RouteClient.getRoute | RouteClient.get (same client, renamed method) |
Delivery read#
Read, modify, write#
For example, an automation reacting to a draft event. The old approach needed six options to avoid losing data:
Content types and component definitions#
Renamed methods on the other clients#
The remaining clients keep their class but standardized their verbs on the same list, save, and remove vocabulary. Only the method names changed, and the old names remain as @deprecated aliases.
| Client | Deprecated → new |
|---|---|
CategoryClient | getCategories → list, upsertCategories → save, removeCategory → remove |
LabelClient | getLabels → list, upsertLabel → save, removeLabel → remove |
ProjectClient | getProjects → list, upsert → save, delete → remove |
DataSourceClient | getList → list, upsert → save |
DataTypeClient | get → list, upsert → save |
LocaleClient | get → list, upsert → save |
WorkflowClient | get → list, upsert → save |
ReleaseClient | get → list, upsert → save |
RelationshipClient | get → list |
ReleaseContentsClient | get → list |
EntityReleasesClient | get → list |
Note the list-returning get → list renames. get now consistently means a single fetch.
Using the clients from an automation#
An automation can call these clients as its own Uniform identity. Pass context.uniformCredentials to the client constructor. It carries the projectId and bearerToken the clients expect: