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.md
source 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"
});
  • token
    -- API key token generated in Webiny Admin (Settings > API Keys)
  • endpoint
    -- The base API URL, without a trailing slash. Run
    yarn webiny info
    in your Webiny project to find the API URL. For Website Builder projects use
    NEXT_PUBLIC_WEBSITE_BUILDER_API_HOST
    .
  • tenant
    -- Tenant ID, defaults to
    "root"

IMPORTANT: Never add a trailing slash to

endpoint
. The SDK appends
/graphql
to the endpoint internally, so
https://xxx.cloudfront.net/
will break all requests.

The
fields
Parameter (Required)

Every SDK method requires a

fields
array that specifies which fields to return. Omitting it will cause a runtime error.

  • Use
    "values.<fieldId>"
    for content fields:
    "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:

preview
ReturnsUse For
false
(default)
Published entries onlyPublic-facing apps, SSG
true
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

OperatorDescriptionExample
_eq
Equals (default)
"values.status": "active"
_not
Not equals
"values.status_not": "archived"
_contains
Contains substring
"values.name_contains": "Pro"
_startsWith
Starts with
"values.name_startsWith": "Web"
_gt
/
_gte
Greater than / >=
"values.price_gte": 100
_lt
/
_lte
Less than / <=
"values.price_lt": 500
_in
In array
"values.status_in": ["active", "featured"]

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

values
key in the
data
object. Passing fields directly (without
values
) will result in an empty or malformed entry.

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

.ts
file extension in the
src
prop
— omitting it will cause a build failure):

<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

ModuleWebiny AppWhat You Can Do
webiny.cms
Headless CMSList, get, create, update, publish, unpublish, delete entry revisions
webiny.fileManager
File ManagerList, upload, and manage files and folders
webiny.tenantManager
Multi-tenancyCreate, install, enable, disable tenants
webiny.languages
LanguagesList enabled languages (id, code, name, direction, isDefault)
webiny.tasks
Background TasksTrigger, abort, list task runs, definitions, and logs

Common Mistakes

MistakeCorrect
data: { name: "..." }
data: { values: { name: "..." } }
updateEntry(...)
updateEntryRevision(...)
publishEntry(...)
publishEntryRevision(...)
unpublishEntry(...)
unpublishEntryRevision(...)
sort: ["values.name_ASC"]
sort: { "values.name": "asc" }
getEntry({ id: "..." })
getEntry({ where: { id: "..." } })
Omitting
fields
Always provide
fields: [...]
Trailing slash in endpointRemove trailing slash from endpoint URL
triggerTask
with unknown
definition
string
Use an ID returned by
listDefinitions()
— the GQL schema validates it against
WebinyBackgroundTaskDefinitionEnum!

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

  • webiny-api-cms-content-models
    -- Define the models you query with the SDK
  • webiny-website-builder
    -- Use the SDK inside Website Builder components to fetch CMS data