Skip to content
better-i18n.com
इस पेज पर

The Content SDK is built with TypeScript and provides full type safety for all responses, including support for typed custom fields via generics.

Generic Custom Fields #

Content models can define custom fields (e.g., readingTime, category). Custom fields are spread flat onto the entry object — there is no nested customFields wrapper. By default, additional fields are typed as Record<string, string | null>. Use the generic type parameter on .single<CF>() to get exact typing:

TypeScript
// Define your custom fields interface
interface BlogFields {
  readingTime: string | null;
  category: string | null;
  featured: string | null;
}

// Pass it to single()
const { data: post } = await client
  .from("blog-posts")
  .single<BlogFields>("hello-world");

post.readingTime; // string | null (typed!)
post.category;    // string | null (typed!)
post.featured;    // string | null (typed!)
post.unknown;     // TypeScript error!

Creating a Typed Client Wrapper #

For repeated use, create wrapper functions with your custom field types baked in:

TypeScript
import { createClient, type ContentEntry, type QueryResult, type ContentEntryListItem, type SingleQueryResult } from "@better-i18n/sdk";

interface BlogFields {
  readingTime: string | null;
  category: string | null;
}

const client = createClient({
  projectId: "acme/web-app",
  apiKey: process.env.BETTER_I18N_API_KEY!,
});

export async function getBlogPost(slug: string, language?: string): Promise<SingleQueryResult<ContentEntry<BlogFields>>> {
  return client
    .from("blog-posts")
    .language(language ?? "en")
    .single<BlogFields>(slug);
}

export async function listBlogPosts(page = 1): Promise<QueryResult<ContentEntryListItem<BlogFields>[]>> {
  return client
    .from("blog-posts")
    .eq("status", "published")
    .order("publishedAt", { ascending: false })
    .limit(10)
    .page(page);
}

Usage stays clean and fully typed:

TypeScript
const { data: post } = await getBlogPost("hello-world", "en");
post?.readingTime; // string | null
post?.category;    // string | null

const { data: posts, total } = await listBlogPosts(1);
posts?.[0].readingTime; // string | null

Status Type Narrowing #

The status field is a union type, enabling exhaustive checks:

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

function getStatusLabel(status: ContentEntryStatus): string {
  switch (status) {
    case "draft":
      return "Draft";
    case "published":
      return "Published";
    case "archived":
      return "Archived";
  }
}

Exported Types #

All types are re-exported from the package root:

TypeScript
import type {
  // Configuration
  ClientConfig,
  ContentClient,

  // Query builder classes
  ContentQueryBuilder,
  SingleQueryBuilder,

  // Content types
  ContentModel,
  ContentEntry,
  ContentEntryListItem,
  ContentEntryStatus,
  ContentEntrySortField,
  RelationValue,

  // Response types
  QueryResult,
  SingleQueryResult,
} from "@better-i18n/sdk";