Payload CMS SDK: CLI Toolkit for Faster Migrations

Set up an authenticated Payload CMS SDK client and run CLI scripts with tsx to speed migrations, content cleanup, and…

·Updated on:·Matija Žiberna·
Payload CMS SDK: CLI Toolkit for Faster Migrations

📚 Comprehensive Payload CMS Guides

Detailed Payload guides with field configuration examples, custom components, and workflow optimization tips to speed up your CMS development process.

No spam. Unsubscribe anytime.

During a recent migration from WordPress to Payload CMS, I ran into a familiar problem. Getting the data across was only half the work. The real time sink was all the small fixes afterward. Tweaking titles, correcting fields, cleaning up content. Doing all of that through the Admin UI quickly became slow and error prone.

Payload gives you several ways to input and modify data, but what helped me most during this phase was the SDK. Especially for targeted, one off changes, having a way to run authenticated scripts from the terminal was a huge productivity boost.

In this article, I will show how I set up a small SDK based toolkit for CLI scripts. It lets you run find, create, update, and delete operations directly against Payload without opening the Admin UI. This approach works particularly well during migrations, cleanups, and ongoing maintenance.

Step 1: Install dependencies and confirm the SDK is available

The toolkit relies on @payloadcms/sdk and runs scripts with tsx, so you need dependencies installed before anything else.

pnpm install

Once that completes, you’re ready to configure the SDK client used by all scripts in this folder.

Step 2: Configure the toolkit environment

The shared client loads credentials from a local .env.toolkit file so you can authenticate from the CLI.

# File: scripts/toolkit/.env.toolkit
PAYLOAD_URL=http://localhost:80/api
# OR set NEXT_PUBLIC_SERVER_URL and it will append /api
# NEXT_PUBLIC_SERVER_URL=http://localhost:80

PAYLOAD_EMAIL=you@example.com
PAYLOAD_PASSWORD=your-password

This file gives the SDK a base URL and login credentials. The client reads it automatically before it initializes, so every script can rely on the same configuration.

Step 3: Use the shared SDK client

All scripts should import the shared client from scripts/toolkit/client.ts. If you need authenticated access, call getAuthenticatedSdk() to get a JWT-backed SDK instance.

// File: scripts/toolkit/fix-titles.ts
import { getAuthenticatedSdk } from "./client";

async function run() {
  const sdk = await getAuthenticatedSdk();

  const pages = await sdk.find({ collection: "page", limit: 5 });
  if (pages.docs[0]) {
    await sdk.update({
      collection: "page",
      id: pages.docs[0].id,
      data: { title: "Updated from CLI" },
    });
  }
}

run().catch(console.error);

This example logs in, queries a few pages, and updates one document. The key idea is that authentication happens once in getAuthenticatedSdk(), and the returned SDK carries the JWT for all subsequent requests.

Example: find, update, create, delete

Use the SDK for quick CRUD tasks while staying within Payload permissions.

import { getAuthenticatedSdk } from "./client";

async function run() {
  const sdk = await getAuthenticatedSdk();

  // Find
  const pages = await sdk.find({ collection: "page", limit: 5 });
  console.log("Found:", pages.totalDocs);

  // Update
  if (pages.docs[0]) {
    await sdk.update({
      collection: "page",
      id: pages.docs[0].id,
      data: { title: "Updated from CLI" },
    });
  }

  // Create (remember tenant, see below)
  await sdk.create({
    collection: "page",
    data: {
      title: "CLI Page",
      // tenant: 1,
    },
  });

  // Delete
  // await sdk.delete({ collection: 'page', id: pages.docs[0].id })
}

run().catch(console.error);

Step 4: Run the script from the terminal

With the script in place, run it using tsx so TypeScript executes without a build step.

npx tsx scripts/toolkit/fix-titles.ts

At this point you’re running real SDK operations from the CLI, with the same permissions your user has in Payload—just without the Admin UI.

Step 5: Multi-tenant note for this project

This repo supports multiple tenants (adart, making-light). When you create documents in tenant-aware collections, include the tenant field in data. Updates by id are safe because IDs are unique. If you need a tenant-aware example, review scripts/toolkit/example-bulk-update.ts.

Tenant-aware create example

import { getAuthenticatedSdk } from "./client";

async function run() {
  const sdk = await getAuthenticatedSdk();

  await sdk.create({
    collection: "page",
    data: {
      title: "CLI Page (AdArt)",
      tenant: 1,
    },
  });
}

run().catch(console.error);

Conclusion

The problem was simple but painful: CLI scripts were blocked by Admin-only workflows. By wiring a shared Payload SDK client and a local .env.toolkit, you can run authenticated operations directly from the terminal. You now know how to configure the SDK, write a script, and execute it in this repo without the Admin UI. Let me know in the comments if you have questions, and subscribe for more practical development guides.

Thanks, Matija

0

Frequently Asked Questions

Comments

Leave a Comment

Your email will not be published

10-2000 characters

• Comments are automatically approved and will appear immediately

• Your name and email will be saved for future comments

• Be respectful and constructive in your feedback

• No spam, self-promotion, or off-topic content

Matija Žiberna
Matija Žiberna
Full-stack developer, co-founder

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.