Skip to content
better-i18n.com
On this page

createClient(config) #

Creates a content client for fetching models and entries.

TypeScript
import { createClient } from "@better-i18n/sdk";

const client = createClient({
  projectId: "acme/web-app",
  apiKey: "bi-your-api-key",
});

Parameters:

FieldTypeRequiredDescription
projectIdstringYesProject ID — org/project slug or canonical UUID
apiKeystringYesAPI key (prefix: bi-)
apiBasestringNoAPI URL. Default: https://content.better-i18n.com
debugbooleanNoLog 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.

TypeScript
const builder = client.from("blog-posts"); // [!code highlight]

Parameters:

ParameterTypeRequiredDescription
modelSlugstringYesContent 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.

MethodDescription
.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.

TypeScript
const { data } = await client
  .from("blog-posts")
  .select("title", "body", "category");
ParameterTypeDescription
...fieldsstring[]Field names to include

.eq(field, value) #

Filter entries by exact field value. Works for the built-in status field and any custom field.

TypeScript
const { data } = await client
  .from("blog-posts")
  .eq("status", "published");
ParameterTypeDescription
fieldstringField name ("status" or custom field name)
valuestringValue to match

.filter(field, value) #

Filter by a custom field value. Equivalent to .eq() for custom fields.

TypeScript
const { data } = await client
  .from("blog-posts")
  .filter("category", "engineering");
ParameterTypeDescription
fieldstringCustom field name
valuestringValue to match

.search(term) #

Full-text search on entry titles.

TypeScript
const { data } = await client
  .from("blog-posts")
  .search("kubernetes");
ParameterTypeDescription
termstringSearch term

.language(code) #

Set the language code for localized content. Falls back to the source language when the requested language has no translation.

TypeScript
const { data } = await client
  .from("blog-posts")
  .language("fr")
  .single("hello-world");
ParameterTypeDescription
codestringBCP 47 language code (e.g. "en", "fr", "tr")

.order(field, options?) #

Set the sort field and direction.

TypeScript
const { data } = await client
  .from("blog-posts")
  .order("publishedAt", { ascending: false });
ParameterTypeDescription
fieldContentEntrySortField"publishedAt", "createdAt", "updatedAt", or "title"
options.ascendingbooleantrue for ascending, false for descending. Default: false

.limit(n) #

Limit the number of entries returned per page.

TypeScript
const { data } = await client.from("blog-posts").limit(20);
ParameterTypeDescription
nnumberEntries per page (1–100). Default: 50

.page(n) #

Set the page number for pagination (1-based).

TypeScript
const { data } = await client.from("blog-posts").limit(20).page(3);
ParameterTypeDescription
nnumberPage 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.

TypeScript
const { data } = await client
  .from("blog-posts")
  .expand("author", "category");
ParameterTypeDescription
...fieldsstring[]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.

TypeScript
const { data: post, error } = await client // [!code highlight]
  .from("blog-posts") // [!code highlight]
  .language("en") // [!code highlight]
  .single("hello-world"); // [!code highlight]
ParameterTypeDescription
slugstringEntry 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.

TypeScript
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(...)).

FieldTypeDescription
dataT | nullQuery results, or null on error
errorError | nullError if the request failed, otherwise null
totalnumberTotal matching entries across all pages
hasMorebooleanWhether more pages exist beyond the current page

SingleQueryResult<T> #

Returned when awaiting a single query (await client.from(...).single(...)).

FieldTypeDescription
dataT | nullEntry data, or null if not found or on error
errorError | nullError if the request failed, otherwise null

client.getModels() #

List all content models in the project.

TypeScript
const models = await client.getModels();

Returns: Promise<ContentModel[]>

FieldTypeDescription
slugstringURL-safe identifier
displayNamestringHuman-readable name
descriptionstring | nullModel description
kindstring"collection" or "single"
entryCountnumberNumber of entries

Legacy Methods #

client.getEntries(modelSlug, options?) — deprecated #

Use client.from(modelSlug) instead.

TypeScript
// 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.

TypeScript
// 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");

REST API Endpoints #

The SDK calls these REST endpoints under the hood:

MethodEndpointSDK
GET/v1/content/{org}/{project}/modelsgetModels()
GET/v1/content/{org}/{project}/models/{model}/entriesfrom(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):

FieldTypeDescription
idstringUnique entry ID
slugstringURL-safe identifier
statusContentEntryStatusEntry status
publishedAtstring | nullISO 8601 publish date
sourceLanguagestringProject's source language code
availableLanguagesstring[]Language codes with translations
titlestringLocalized title
bodystring | nullLocalized body as Markdown
relationsRecord<string, RelationValue | null>Expanded relations (only when expand is used)

Additional fields (present on single-entry responses):

FieldTypeDescription
availableLanguageDetailsContentEntryLanguage[] | undefinedRich language descriptors with display name and country code — useful for language pickers
translationStatusRecord<string, "draft" | "published"> | undefinedPer-language translation publish status
bodyHtmlstring | undefinedBody rendered as an HTML string
bodyMarkdownstring | undefinedBody 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.

FieldTypeDescription
slugstringURL-safe identifier (always included)
publishedAtstring | nullISO 8601 publish date (always included)
titlestringEntry title
bodystring | nullMarkdown body (only when requested via .select())
relationsRecord<string, RelationValue | null>Expanded relations (only when expand is used)

Custom fields from CF are spread flat onto this object.

FieldTypeDescription
slugstringURL-safe identifier
displayNamestringHuman-readable name
descriptionstring | nullModel description
kind"collection" | "single"Model kind
entryCountnumberNumber of entries
includeBodybooleanWhether the model has a rich-text body field
fieldsContentModelField[]Custom field definitions

Field definition returned inside ContentModel.fields.

FieldTypeDescription
namestringField identifier (snake_case)
displayNamestringHuman-readable field label
typestringField type (text, textarea, richtext, number, boolean, date, datetime, enum, media, relation)
requiredbooleanWhether the field is required
localizedbooleanWhether the field is translated per language
enumValuesContentModelEnumValue[] | undefinedEnum options — only present when type is "enum"
fieldConfig.targetModelstring | undefinedTarget model slug — only present when type is "relation"

">

FieldTypeDescription
dataT | nullQuery results
errorError | nullError if request failed
totalnumberTotal matching entries
hasMorebooleanWhether more pages exist

">

FieldTypeDescription
dataT | nullEntry data
errorError | nullError 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.

FieldTypeDescription
idstringUnique ID of the referenced entry
slugstringURL-safe slug of the referenced entry
titlestringDisplay title of the referenced entry
modelSlugstringContent model the referenced entry belongs to

Additional string | null keys represent the referenced entry's custom fields.

TypeScript
type ContentEntryStatus = "draft" | "published" | "archived";
TypeScript
type ContentEntrySortField = "publishedAt" | "createdAt" | "updatedAt" | "title";