Content SDK API Reference
En esta página
- createClient(config)
- client.from(modelSlug)
- Query Builder Methods
- .select(...fields)
- .eq(field, value)
- .filter(field, value)
- .search(term)
- .language(code)
- .order(field, options?)
- .limit(n)
- .page(n)
- .expand(...fields)
- Terminal Methods
- .single<CF>(slug)
- await builder (list query)
- Return Types
- QueryResult<T>
- SingleQueryResult<T>
- client.getModels()
- Legacy Methods
- client.getEntries(modelSlug, options?) — deprecated
- client.getEntry(modelSlug, entrySlug, options?) — deprecated
- REST API Endpoints
- Types
createClient(config) #
Creates a content client for fetching models and entries.
import { createClient } from "@better-i18n/sdk";
const client = createClient({
projectId: "acme/web-app",
apiKey: "bi-your-api-key",
});Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string | Yes | Project ID — org/project slug or canonical UUID |
apiKey | string | Yes | API key (prefix: bi-) |
apiBase | string | No | API URL. Default: https://content.better-i18n.com |
debug | boolean | No | Log request URLs and responses to console |
Returns: ContentClient
client.from(modelSlug) #
Start a chainable query builder for a content model. This is the primary API for fetching entries.
const builder = client.from("blog-posts"); // [!code highlight]Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
modelSlug | string | Yes | Content model slug |
Returns: ContentQueryBuilder
The builder is immutable — every chained method returns a new builder instance. The builder is thenable: await it directly to execute a list query.
Query Builder Methods #
All methods return a new ContentQueryBuilder instance and can be chained in any order.
| Method | Description |
|---|---|
.select(...fields) | Choose which fields to include in the response |
.eq(field, value) | Filter by field value ("status" or any custom field) |
.filter(field, value) | Filter by custom field value |
.search(term) | Full-text search on entry titles |
.language(code) | Set language code for localized content |
.order(field, opts?) | Set sort field and direction |
.limit(n) | Limit results per page (1–100) |
.page(n) | Set page number (1-based) |
.expand(...fields) | Expand relation fields inline |
.select(...fields) #
Choose which fields to include. slug and publishedAt are always returned. When omitted, all fields are returned.
const { data } = await client
.from("blog-posts")
.select("title", "body", "category");| Parameter | Type | Description |
|---|---|---|
...fields | string[] | Field names to include |
.eq(field, value) #
Filter entries by exact field value. Works for the built-in status field and any custom field.
const { data } = await client
.from("blog-posts")
.eq("status", "published");| Parameter | Type | Description |
|---|---|---|
field | string | Field name ("status" or custom field name) |
value | string | Value to match |
.filter(field, value) #
Filter by a custom field value. Equivalent to .eq() for custom fields.
const { data } = await client
.from("blog-posts")
.filter("category", "engineering");| Parameter | Type | Description |
|---|---|---|
field | string | Custom field name |
value | string | Value to match |
.search(term) #
Full-text search on entry titles.
const { data } = await client
.from("blog-posts")
.search("kubernetes");| Parameter | Type | Description |
|---|---|---|
term | string | Search term |
.language(code) #
Set the language code for localized content. Falls back to the source language when the requested language has no translation.
const { data } = await client
.from("blog-posts")
.language("fr")
.single("hello-world");| Parameter | Type | Description |
|---|---|---|
code | string | BCP 47 language code (e.g. "en", "fr", "tr") |
.order(field, options?) #
Set the sort field and direction.
const { data } = await client
.from("blog-posts")
.order("publishedAt", { ascending: false });| Parameter | Type | Description |
|---|---|---|
field | ContentEntrySortField | "publishedAt", "createdAt", "updatedAt", or "title" |
options.ascending | boolean | true for ascending, false for descending. Default: false |
.limit(n) #
Limit the number of entries returned per page.
const { data } = await client.from("blog-posts").limit(20);| Parameter | Type | Description |
|---|---|---|
n | number | Entries per page (1–100). Default: 50 |
.page(n) #
Set the page number for pagination (1-based).
const { data } = await client.from("blog-posts").limit(20).page(3);| Parameter | Type | Description |
|---|---|---|
n | number | Page number starting at 1. Default: 1 |
.expand(...fields) #
Expand relation fields, resolving referenced entries inline. Expanded relations appear in a relations key on each entry. The relation object's custom fields are flat on the relation object itself.
const { data } = await client
.from("blog-posts")
.expand("author", "category");| Parameter | Type | Description |
|---|---|---|
...fields | string[] | Relation field names to expand |
Terminal Methods #
.single<CF>(slug) #
Fetch a single entry by slug. Returns a SingleQueryBuilder<CF> which is thenable — await it directly.
const { data: post, error } = await client // [!code highlight]
.from("blog-posts") // [!code highlight]
.language("en") // [!code highlight]
.single("hello-world"); // [!code highlight]| Parameter | Type | Description |
|---|---|---|
slug | string | Entry slug |
Type Parameter: CF extends Record<string, string | null> — Custom fields shape. Defaults to Record<string, string | null>. See TypeScript guide.
Returns: SingleQueryBuilder<CF> (thenable → SingleQueryResult<ContentEntry<CF>>)
await builder (list query) #
Awaiting a ContentQueryBuilder directly executes a list query.
const { data, error, total, hasMore } = await client
.from("blog-posts")
.eq("status", "published");Returns: QueryResult<ContentEntryListItem[]>
Return Types #
QueryResult<T> #
Returned when awaiting a list query (await client.from(...)).
| Field | Type | Description |
|---|---|---|
data | T | null | Query results, or null on error |
error | Error | null | Error if the request failed, otherwise null |
total | number | Total matching entries across all pages |
hasMore | boolean | Whether more pages exist beyond the current page |
SingleQueryResult<T> #
Returned when awaiting a single query (await client.from(...).single(...)).
| Field | Type | Description |
|---|---|---|
data | T | null | Entry data, or null if not found or on error |
error | Error | null | Error if the request failed, otherwise null |
const models = await client.getModels();Returns: Promise<ContentModel[]>
| Field | Type | Description |
|---|---|---|
slug | string | URL-safe identifier |
displayName | string | Human-readable name |
description | string | null | Model description |
kind | string | "collection" or "single" |
entryCount | number | Number of entries |
Legacy Methods #
client.getEntries(modelSlug, options?) — deprecated #
Use client.from(modelSlug) instead.
// Deprecated
const { items, total, hasMore } = await client.getEntries("blog-posts", {
status: "published",
});
// Preferred
const { data, total, hasMore } = await client
.from("blog-posts")
.eq("status", "published");client.getEntry(modelSlug, entrySlug, options?) — deprecated #
Use client.from(modelSlug).single(entrySlug) instead.
// Deprecated
const post = await client.getEntry("blog-posts", "hello-world", {
language: "fr",
});
// Preferred
const { data: post } = await client
.from("blog-posts")
.language("fr")
.single("hello-world");| Method | Endpoint | SDK |
|---|---|---|
GET | /v1/content/{org}/{project}/models | getModels() |
GET | /v1/content/{org}/{project}/models/{model}/entries | from(model) |
GET | /v1/content/{org}/{project}/models/{model}/entries/{slug} | from(model).single(slug) |
All requests use the x-api-key header for authentication.
Types #
">
Returned by .single(). Custom fields are spread directly onto the entry via & CF — there is no nested customFields wrapper.
Base fields (always included):
| Field | Type | Description |
|---|---|---|
id | string | Unique entry ID |
slug | string | URL-safe identifier |
status | ContentEntryStatus | Entry status |
publishedAt | string | null | ISO 8601 publish date |
sourceLanguage | string | Project's source language code |
availableLanguages | string[] | Language codes with translations |
title | string | Localized title |
body | string | null | Localized body as Markdown |
relations | Record<string, RelationValue | null> | Expanded relations (only when expand is used) |
Additional fields (present on single-entry responses):
| Field | Type | Description |
|---|---|---|
availableLanguageDetails | ContentEntryLanguage[] | undefined | Rich language descriptors with display name and country code — useful for language pickers |
translationStatus | Record<string, "draft" | "published"> | undefined | Per-language translation publish status |
bodyHtml | string | undefined | Body rendered as an HTML string |
bodyMarkdown | string | undefined | Body as a plain Markdown string (alias for body) |
Custom fields from CF are spread flat onto this object.
">
Returned in list query results. Custom fields are spread directly onto the item via & CF — there is no nested customFields wrapper.
| Field | Type | Description |
|---|---|---|
slug | string | URL-safe identifier (always included) |
publishedAt | string | null | ISO 8601 publish date (always included) |
title | string | Entry title |
body | string | null | Markdown body (only when requested via .select()) |
relations | Record<string, RelationValue | null> | Expanded relations (only when expand is used) |
Custom fields from CF are spread flat onto this object.
| Field | Type | Description |
|---|---|---|
slug | string | URL-safe identifier |
displayName | string | Human-readable name |
description | string | null | Model description |
kind | "collection" | "single" | Model kind |
entryCount | number | Number of entries |
includeBody | boolean | Whether the model has a rich-text body field |
fields | ContentModelField[] | Custom field definitions |
Field definition returned inside ContentModel.fields.
| Field | Type | Description |
|---|---|---|
name | string | Field identifier (snake_case) |
displayName | string | Human-readable field label |
type | string | Field type (text, textarea, richtext, number, boolean, date, datetime, enum, media, relation) |
required | boolean | Whether the field is required |
localized | boolean | Whether the field is translated per language |
enumValues | ContentModelEnumValue[] | undefined | Enum options — only present when type is "enum" |
fieldConfig.targetModel | string | undefined | Target model slug — only present when type is "relation" |
">
| Field | Type | Description |
|---|---|---|
data | T | null | Query results |
error | Error | null | Error if request failed |
total | number | Total matching entries |
hasMore | boolean | Whether more pages exist |
">
| Field | Type | Description |
|---|---|---|
data | T | null | Entry data |
error | Error | null | Error if request failed |
Returned for each key in relations when .expand() is used. Custom fields of the referenced entry are spread flat onto this object — no nested customFields wrapper.
| Field | Type | Description |
|---|---|---|
id | string | Unique ID of the referenced entry |
slug | string | URL-safe slug of the referenced entry |
title | string | Display title of the referenced entry |
modelSlug | string | Content model the referenced entry belongs to |
Additional string | null keys represent the referenced entry's custom fields.
type ContentEntryStatus = "draft" | "published" | "archived";type ContentEntrySortField = "publishedAt" | "createdAt" | "updatedAt" | "title";
Better I18N