Webiny-js webiny-sdk
install
source · Clone the upstream repo
git clone https://github.com/webiny/webiny-js
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/webiny/webiny-js "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/user-skills/webiny-sdk" ~/.claude/skills/webiny-webiny-js-webiny-sdk && rm -rf "$T"
manifest:
skills/user-skills/webiny-sdk/SKILL.mdsource content
Webiny SDK
TL;DR
The
@webiny/sdk package provides a TypeScript interface for external apps (Next.js, Vue, Node.js) to interact with Webiny's Headless CMS and File Manager. Every method returns a Result object (checked with isOk()). Supports listing, getting, creating, updating, publishing, and unpublishing entries with filtering, sorting, pagination, and TypeScript generics for type safety.
Installation & Setup
npm install @webiny/sdk
Initialize once and reuse:
// lib/webiny.ts import { Webiny } from "@webiny/sdk"; export const webiny = new Webiny({ token: process.env.WEBINY_API_TOKEN!, endpoint: process.env.WEBINY_API_ENDPOINT!, tenant: process.env.WEBINY_API_TENANT || "root" });
-- API key token generated in Webiny Admin (Settings > API Keys)token
-- The base API URL, without a trailing slash. Runendpoint
in your Webiny project to find the API URL. For Website Builder projects useyarn webiny info
.NEXT_PUBLIC_WEBSITE_BUILDER_API_HOST
-- Tenant ID, defaults totenant"root"
IMPORTANT: Never add a trailing slash to
. The SDK appendsendpointto the endpoint internally, so/graphqlwill break all requests.https://xxx.cloudfront.net/
The fields
Parameter (Required)
fieldsEvery SDK method requires a
fields array that specifies which fields to return. Omitting it will cause a runtime error.
- Use
for content fields:"values.<fieldId>"
,"values.name""values.price" - Use top-level field names for metadata:
,"id"
,"entryId"
,"createdOn""status" - Use dot notation for nested fields:
"values.author.name"
// Minimal fields -- just IDs fields: ["id", "entryId"]; // Content fields fields: ["id", "entryId", "values.name", "values.price", "values.description"]; // Nested reference fields fields: ["id", "values.title", "values.author.name", "values.author.email"];
CMS: Read vs Preview Mode
webiny.cms.listEntries and webiny.cms.getEntry accept a preview parameter to control which revisions are returned:
| Returns | Use For |
|---|---|---|
(default) | Published entries only | Public-facing apps, SSG |
| Latest revision (draft or published) | Content preview, editorial tools |
Write operations (
createEntry, updateEntryRevision, etc.) are not affected by preview.
The Result Pattern
Every SDK method returns a
Result object -- it never throws:
const result = await webiny.cms.listEntries({ modelId: "product", fields: ["id", "values.name"] }); if (result.isOk()) { console.log(result.value.data); // success -- typed data } else { console.error(result.error.message); // failure -- error info }
TypeScript Generics
Pass a type parameter for full type safety on
values:
import type { CmsEntryData } from "@webiny/sdk"; interface Product { name: string; price: number; sku: string; description: string; category?: CmsEntryData<ProductCategory>; } interface ProductCategory { name: string; slug: string; } const result = await webiny.cms.listEntries<Product>({ modelId: "product", fields: ["id", "entryId", "values.name", "values.price", "values.sku"] }); if (result.isOk()) { // result.value.data is CmsEntryData<Product>[] const products = result.value.data; // products[0].values.name -- fully typed }
Reference fields like
category are typed as CmsEntryData<T>, which wraps referenced entries with id, entryId, and values.
Reading Data
List Entries
const result = await webiny.cms.listEntries<Product>({ modelId: "product", fields: ["id", "entryId", "values.name", "values.price"], sort: { "values.name": "asc" }, limit: 10 });
List with Filters
const result = await webiny.cms.listEntries<Product>({ modelId: "product", fields: ["id", "entryId", "values.name", "values.price"], where: { "values.price_gte": 100, "values.name_contains": "Pro" }, sort: { "values.price": "desc" } });
Filter Operators
| Operator | Description | Example |
|---|---|---|
| Equals (default) | |
| Not equals | |
| Contains substring | |
| Starts with | |
/ | Greater than / >= | |
/ | Less than / <= | |
| In array | |
Sort Format
Sort is a
Record<string, "asc" | "desc"> object:
sort: { "values.name": "asc" } // alphabetical sort: { "values.price": "desc" } // highest price first sort: { "values.createdOn": "desc" } // newest first
Get Single Entry
Use
where with either id (revision ID) or entryId:
// By revision ID const result = await webiny.cms.getEntry<Product>({ modelId: "product", where: { id: "abc123#0001" }, fields: ["id", "entryId", "values.name", "values.price"] }); // By entry ID (gets latest published revision) const result = await webiny.cms.getEntry<Product>({ modelId: "product", where: { entryId: "abc123" }, fields: ["id", "entryId", "values.name"] });
Preview Mode (Drafts)
Pass
preview: true to listEntries or getEntry to access unpublished/draft content:
const result = await webiny.cms.listEntries<Product>({ modelId: "product", fields: ["id", "entryId", "values.name"], preview: true // returns drafts + published });
Writing Data
CRITICAL: Content fields MUST be wrapped inside a
key in thevaluesobject. Passing fields directly (withoutdata) will result in an empty or malformed entry.values
Create an Entry
const result = await webiny.cms.createEntry({ modelId: "contactSubmission", data: { values: { // ← REQUIRED: wrap all content fields in `values` name: "John Doe", email: "john@example.com", message: "Hello from the contact form!" } }, fields: ["id", "entryId"] });
Update an Entry Revision
The method is
updateEntryRevision, not updateEntry. Use revisionId (the full entryId#revisionNumber, e.g. "abc123#0001"):
const result = await webiny.cms.updateEntryRevision({ modelId: "product", revisionId: "abc123#0001", // ← note: revisionId, not id data: { values: { // ← REQUIRED: wrap fields in `values` price: 29.99 } }, fields: ["id", "entryId", "values.price"] });
Publish / Unpublish
The methods are
publishEntryRevision and unpublishEntryRevision, not publishEntry/unpublishEntry. Both require revisionId and fields:
await webiny.cms.publishEntryRevision({ modelId: "product", revisionId: "abc123#0001", fields: ["id", "entryId", "status"] }); await webiny.cms.unpublishEntryRevision({ modelId: "product", revisionId: "abc123#0001", fields: ["id", "entryId", "status"] });
Delete an Entry Revision
await webiny.cms.deleteEntryRevision({ modelId: "product", revisionId: "abc123#0001", fields: [] });
Languages
webiny.languages.listLanguages() returns all enabled languages — disabled languages are always filtered out server-side, so no filter parameter is needed.
import type { Language } from "@webiny/sdk"; const result = await webiny.languages.listLanguages(); if (result.isOk()) { const languages: Language[] = result.value; // languages[0].code, .name, .direction, .isDefault }
The
Language type:
interface Language { id: string; code: string; // e.g. "en-US" name: string; // e.g. "English (US)" direction?: "ltr" | "rtl"; isDefault?: boolean; }
File Manager
// List files const files = await webiny.fileManager.listFiles({ limit: 20, fields: ["id", "key", "name", "size", "type", "src"] }); // Upload a file (returns presigned URL for direct S3 upload) const uploaded = await webiny.fileManager.uploadFile({ file: myFile });
Creating API Keys via Code
For programmatic access, create API keys as an extension:
// extensions/MyApiKey.ts import { ApiKeyFactory } from "webiny/api/security"; class MyApiKeyImpl implements ApiKeyFactory.Interface { execute(): ApiKeyFactory.Return { return [ { name: "Universal API Key", slug: "universal-key", token: "wat_12345678", permissions: [{ name: "*" }] } ]; } } export default ApiKeyFactory.createImplementation({ implementation: MyApiKeyImpl, dependencies: [] });
Register (YOU MUST include the
file extension in the .ts
prop — omitting it will cause a build failure):src
<Api.Extension src={"/extensions/MyApiKey.ts"} />
Background Tasks
webiny.tasks wraps the Background Tasks GraphQL API. All methods return a Result and never throw.
List Task Definitions
Returns all registered task definitions — use this to discover valid
definition IDs before triggering.
const result = await webiny.tasks.listDefinitions(); if (result.isOk()) { // result.value: TaskDefinition[] for (const def of result.value) { console.log(def.id, def.title, def.description); } }
List Task Runs
const result = await webiny.tasks.listTasks(); if (result.isOk()) { // result.value: TaskRun[] for (const task of result.value) { console.log(task.id, task.taskStatus, task.definitionId); } }
List Task Logs
Optionally filter by a specific task run ID:
// All logs const result = await webiny.tasks.listLogs(); // Logs for a specific task run const result = await webiny.tasks.listLogs({ where: { task: "yourTaskRunId" } }); if (result.isOk()) { for (const log of result.value) { for (const item of log.items) { console.log(`[${item.type}] ${item.message}`); } } }
Trigger a Task
const result = await webiny.tasks.triggerTask({ definition: "myTaskDefinitionId", input: { someVariable: "someValue", anotherVariable: 42 } }); if (result.isOk()) { const task = result.value; // TaskRun console.log(task.id, task.taskStatus, task.executionName); }
Abort a Task
The task stops at its next safe checkpoint.
const result = await webiny.tasks.abortTask({ id: "yourTaskRunId", message: "Stopped by user request" // optional }); if (result.isOk()) { console.log(result.value.taskStatus); // "aborted" }
Background Task Types
import type { TaskDefinition, TaskRun, TaskLog, TaskLogItem, TaskStatus } from "@webiny/sdk"; type TaskStatus = "pending" | "running" | "completed" | "failed" | "aborted" | "stopped"; interface TaskDefinition { id: string; title: string; description?: string; } interface TaskRun { id: string; definitionId: string; taskStatus: TaskStatus; input?: unknown; output?: unknown; startedOn?: string; finishedOn?: string; executionName?: string; iterations?: number; parentId?: string; }
SDK Modules Reference
| Module | Webiny App | What You Can Do |
|---|---|---|
| Headless CMS | List, get, create, update, publish, unpublish, delete entry revisions |
| File Manager | List, upload, and manage files and folders |
| Multi-tenancy | Create, install, enable, disable tenants |
| Languages | List enabled languages (id, code, name, direction, isDefault) |
| Background Tasks | Trigger, abort, list task runs, definitions, and logs |
Common Mistakes
| Mistake | Correct |
|---|---|
| |
| |
| |
| |
| |
| |
Omitting | Always provide |
| Trailing slash in endpoint | Remove trailing slash from endpoint URL |
with unknown string | Use an ID returned by — the GQL schema validates it against |
Quick Reference
Install: npm install @webiny/sdk Import: import { Webiny } from "@webiny/sdk"; Type import: import type { CmsEntryData, TaskRun } from "@webiny/sdk"; Initialize: new Webiny({ token, endpoint, tenant }) Result check: result.isOk() -> result.value / result.error.message API endpoint: yarn webiny info (in your Webiny project) -- NO trailing slash Preview mode: pass preview: true to listEntries / getEntry fields required: every CMS method needs a fields: string[] array values wrapper: createEntry/updateEntryRevision data must use { values: { ... } } Background tasks: webiny.tasks.triggerTask({ definition, input }) Abort task: webiny.tasks.abortTask({ id, message? }) Filter logs by task: webiny.tasks.listLogs({ where: { task: "id" } })
Related Skills
-- Define the models you query with the SDKwebiny-api-cms-content-models
-- Use the SDK inside Website Builder components to fetch CMS datawebiny-website-builder