TypeScript SDK
Use Ridu’s generated, Fetch-based client from browsers, server frameworks, and tooling.
@riducms/sdk is a small Fetch client with ordinary Promise<T> methods. Ridu’s generator binds it
to your application so collection slugs, input shapes, filters, selection, population, and
capability-specific methods are checked by TypeScript.
Install and create a client
Generated applications already include generated/ridu.generated.ts. Prefer that module in
application code: it carries the config type produced from the same manifest as the server. When an
external frontend or tool needs the framework-neutral SDK directly, install it with that project’s
package manager:
Examples below use ~/generated as a readable project alias. Configure that alias in your
frontend, or replace it with the relative path to the CMS project’s generated/ directory.
npm install @riducms/sdkbun add @riducms/sdkpnpm add @riducms/sdkyarn add @riducms/sdkimport { createClient } from '~/generated/ridu.generated';
export const ridu = createClient({
baseURL: 'https://cms.example.com'
});If this code runs in a browser on another origin, configure the API before testing requests. The CORS guide covers exact origins, credentialed cookies, custom headers, proxy trust, and preflight failures. Server-side SDK use does not need CORS.
You can also import createClient from @riducms/sdk. A generated config augments the SDK’s
GeneratedRiduConfigRegistry; when exactly one generated application is visible, inference selects
it automatically. Tooling that loads no generated config—or several applications—should bind the
config explicitly or import each application’s generated wrapper. This avoids silently choosing the
wrong schema.
import { createClient } from '@riducms/sdk';
import type { RiduConfig } from '~/generated/ridu.generated';
const cms = createClient<RiduConfig>({ baseURL: process.env.RIDU_URL! });When ridu dev is running, a Go config change regenerates this module automatically. Commit the
result. See Generated contracts for one-shot generation and drift
checks, and the complete SDK reference for every type and signature.
Read content
list, find, and count cover ordinary collection reads. global reads a singleton. Filters,
selects, and populations are generated from the resource instead of accepting an untyped query bag.
const page = await ridu.list('posts', {
page: 1,
limit: 24,
where: {
and: [{ status: { equals: 'published' } }, { title: { contains: 'ridu' } }]
},
sort: ['-createdAt', 'title'],
select: { title: true, status: true, author: true },
populate: { author: { depth: 1, select: { name: true } } }
});
const post = await ridu.find('posts', page.docs[0].id, {
select: { title: true, author: true }
});
const { totalDocs } = await ridu.count('posts', {
where: { status: { equals: 'published' } }
});
const site = await ridu.global('site-settings');A group, array, or blocks container supports an exists filter. Nested group, array-row, and block
filters use canonical dotted keys such as "seo.description", "sections.reviewer", and
"layout.quote.source". Generated where contracts do not model these paths as nested objects
because the REST API decodes the same dotted path vocabulary used by the operation engine.
Use either depth for uniform relationship expansion or populate for explicit paths, never both
in one request. Locale-aware reads accept locale, including "all", and
fallbackLocale: string | string[] | false. The generated output changes to locale-keyed fields
when locale: "all" is statically known. Locale-aware mutations accept one generated locale and
reject "all".
Create and change content
The core mutation family is create, update, and delete. Globals use updateGlobal.
Optimistic concurrency is opt-in on revision-aware methods: pass the document revision and the SDK
sends If-Match. Methods without a revision contract reject a revision-bearing options variable
instead of silently ignoring it.
const post = await ridu.create('posts', {
title: 'SDK guide',
status: 'draft'
});
const published = await ridu.update(
'posts',
post.id,
{ status: 'published' },
{ revision: post._revision }
);
await ridu.copyLocale(
'posts',
published.id,
{ from: 'en', to: 'fr' },
{
revision: published._revision
}
);
await ridu.duplicate('posts', published.id);Related task methods are grouped by capability:
mutateJoinmutates a writable inverse join.bulkUpdate,bulkPublish,bulkUnpublish,bulkDelete,bulkRestoreDeleted, andbulkDeletePermanentoperate on explicit document IDs. A request is limited to 100 unique IDs.restoreDeleted,deletePermanent, andemptyTrashexist only for trash-enabled collections.copyGlobalLocalecopies localized global values.
The SDK exposes three precise option families: MutationLocaleOptions for single-locale mutations
without a revision fence, MutationOptions for localized revision-aware mutations, and
RevisionOptions for revision-aware operations that do not consume a locale query. copyLocale
and copyGlobalLocale take their locale scope from from and to; updateUploadImage and
schedulePublish likewise use revision-only options.
The type system removes collection slugs from capability-specific methods when the generated manifest says the capability is absent; the server remains the authorization authority.
Drafts, versions, and publishing
For versioned collections, use versions and version to inspect snapshots, then publish,
publishChanges, unpublish, or restore. publishChanges submits edited values and the status
transition as one publish operation, so publish access and hooks cannot be bypassed by an ordinary
update. schedulePublish, scheduledPublishes, and cancelScheduledPublish manage durable
collection publication jobs. Versioned globals use globalVersions, globalVersion,
publishGlobal, publishGlobalChanges, unpublishGlobal, and restoreGlobal; scheduled
publishing is collection-only today.
const history = await ridu.versions('posts', post.id);
const restored = await ridu.restore('posts', post.id, history[0].Revision, {
draft: true,
revision: post._revision
});
const job = await ridu.schedulePublish('posts', restored.id, new Date('2027-01-02T09:00:00Z'), {
revision: restored._revision
});Uploads and media
Upload-enabled collections add three creation paths and one metadata operation:
const asset = await ridu.upload('media', file, {
data: { alt: 'Team gathered outside the studio' }
});
const imported = await ridu.uploadFromURL('media', 'https://assets.example.com/photo.jpg', {
data: { alt: 'Imported photo' }
});
await ridu.updateUploadImage(
'media',
asset.id,
{
focalX: 0.5,
focalY: 0.35,
cropX: 0,
cropY: 0,
cropWidth: 1200,
cropHeight: 630
},
{ revision: asset._revision }
);upload sends multipart data and uploadFromURL asks the configured backend to fetch a URL.
Stored object delivery is an access-checked REST GET rather than a document SDK method. File-size,
MIME, image, remote-host, and storage limits come from server config, not the SDK.
Authentication and account tasks
login, session, refreshSession, logout, and logoutAll implement the ordinary browser
session flow. The client defaults credentials to "include", so the server’s ridu_session
HttpOnly cookie is sent on same-origin or correctly configured cross-origin requests.
Auth collections also enable createAuthUser. Configured recovery, verification, API-key, and
login-attempt-lock features add requestPasswordReset, resetPassword, requestVerification,
verifyEmail, createAPIKey, apiKeys, revokeAPIKey, and forceUnlock. Account operations include
changePassword, sessions, and revokeSession.
For a service client, supply an API key as a Bearer credential. For a raw session token, use the
Session scheme (JWT remains accepted for compatibility). Creating an API key requires a cookie
session, and its secret is returned only by createAPIKey; later listings expose metadata only.
const service = createClient({
baseURL: 'https://cms.example.com',
headers: async () => ({
Authorization: `Bearer ${await loadAPIKey()}`
})
});Preview reads use separate methods: preview and previewGlobal place the short-lived
preview token in Authorization: Bearer … for that request. Use createPreviewToken,
createGlobalPreviewToken, and revokePreviewToken to manage those credentials. For iframe
updates, pair them with connectLivePreview; see Live preview.
Access, locks, and preferences
The admin-facing coordination APIs are public and useful in custom tools:
collectionAccessandglobalAccessresolve operation and field capabilities for the proposed document context.resolveFilteredSelectionconverts an access-checked filter into at most 100 explicit IDs for safe bulk work.documentLock,acquireDocumentLock, andreleaseDocumentLockcoordinate editors. Passingtakeover: truedoes not bypass the server’s access rules.preference,setPreference,deletePreference, andresetPreferencesstore actor-scoped UI state.
Treat capability results as presentation guidance, not a replacement for handling authorization failure: access can depend on the actor, request, proposed data, or stored row.
Plugin endpoints
Use requestPlugin for a compiled plugin’s declared namespaced endpoint:
const generated = await ridu.requestPlugin<{ result: string }>(
'seo',
'generate-title',
{
collection: 'pages',
locale: 'en',
document: { title: 'About Acme' }
},
{ signal }
);The method sends POST /api/plugins/{plugin}/{path} through the same base URL, credentials,
headers, middleware, cancellation, and RiduError handling as the typed content methods. It rejects
invalid plugin keys and empty, whitespace, traversal, query, fragment, or wildcard path segments
before dispatch. The result type is caller-supplied because each compiled plugin owns its endpoint
contract; use that plugin’s guide and API reference rather than treating the route as generic
content CRUD.
Raw custom endpoint requests
Application custom endpoints can return JSON, text, empty responses, or streams. Use request to
retain Fetch semantics instead of forcing an arbitrary route through a typed content envelope:
const response = await ridu.request('/api/revalidate/storefront', {
method: 'POST',
body: JSON.stringify({ paths: ['/products'] }),
headers: { 'Content-Type': 'application/json' }
});
if (!response.ok) throw new Error(`revalidation failed: ${response.status}`);The path must begin with one /, stay on the configured origin, and omit a fragment. The method
returns the raw Response for every HTTP status, does not parse JSON, and does not supply a default
content type. Client credentials, default/per-call headers, middleware, cancellation, and
keepalive still apply. See Custom endpoints for the Go handler contract.
Fetch, middleware, and cancellation
Pass a request-local fetch in SvelteKit or another server framework. headers may be static or an
async function, and request-specific headers override client defaults. Middleware wraps a concrete
Request, which makes tracing, retries, logging, and test interception possible without a separate
transport adapter.
const cms = createClient({
baseURL: 'https://cms.example.com',
fetch,
credentials: 'include',
headers: () => ({ 'X-App': 'storefront' }),
middleware: [
async (request, next) => {
const response = await next(request);
console.debug(request.method, request.url, response.headers.get('X-Request-ID'));
return response;
}
]
});
const controller = new AbortController();
const pending = cms.list('posts', { signal: controller.signal });
controller.abort();
await pending;Every request option accepts signal; mutation requests also accept keepalive. Cancellation is
standard Fetch cancellation and rejects with the runtime’s abort error, not RiduError.
Structured errors
Successful calls return the useful value rather than the wire envelope. Non-success HTTP responses
reject with RiduError, which preserves code, status, message, optional requestId, field
issues, and details.
import { RiduError } from '@riducms/sdk';
try {
await ridu.update('posts', id, { title: '' }, { revision });
} catch (error) {
if (error instanceof RiduError && error.code === 'validation') {
for (const issue of error.issues) console.error(issue.path, issue.message);
return;
}
if (error instanceof RiduError && error.code === 'conflict') {
// Reload: another writer changed this revision.
return;
}
throw error;
}If a proxy returns a non-Ridu error page, the SDK still produces a fallback RiduError from the
HTTP status. It validates Ridu’s success envelopes, but does not runtime-validate every application
document against the generated TypeScript type. Validation and authorization remain server-side.
Current type boundaries
Generated where, select, and populate inputs are resource-specific, but a selected or populated
read currently retains the collection’s complete output type rather than computing a projected
return type. Sort terms are checked as strings rather than a generated union of sortable paths.
The client does not cache, coalesce, or retry automatically; add policy in middleware only when the
operation is safe to repeat. There is no required result-wrapper library and no Node-only transport.
Use the REST API guide for the underlying transport, the protocol reference for shared envelopes and error codes, and Capability status for wider product limits.