Contents

Upgrading Payload

payload and every @payloadcms/* package are pinned to the same exact version in package.json. They must always move together: a mix of versions fails in ways that are hard to read.

The weekly upgrade #

Every Monday, .github/workflows/payload-upgrade.yml checks npm for a newer Payload in the current major. When there is one, it:

  1. pins every Payload package to it and installs;
  2. regenerates the types and the import map;
  3. writes a PostgreSQL migration if the new version changes the tables;
  4. opens a pull request (fix(deps): upgrade Payload to …) with a link to the release notes, and starts CI on it.

CI then runs everything on both databases. Read the release notes and the table below, and merge when it is green. Nothing merges by itself.

  • A new major (Payload 4, say) is not attempted: the workflow opens an issue instead, because a major needs its migration guide.
  • When the upgrade can't be prepared, usually because a patch in patches/ no longer applies, it opens an issue with the install log.
  • Run it now from the Actions tab ("Payload upgrade", "Run workflow").
  • It needs "Allow GitHub Actions to create and approve pull requests" in the repository's Actions settings, the same setting the release workflow needs.

New versions of the kit itself arrive the same way, as a weekly pull request of their own: see Updating Tolk Starter.

Dependabot leaves Payload to this workflow and keeps the other dependencies current; Next.js and React move together in a group of their own.

To upgrade by hand instead, follow the steps below.

Steps #

  1. Update every Payload package to the same version. In package.json, set the new version for payload and for every @payloadcms/* dependency, exactly (no ^), then run pnpm install.

  2. Regenerate the generated files and commit what changes:

    pnpm generate:types
    pnpm generate:importmap
  3. Run everything CI runs: pnpm lint, pnpm typecheck, pnpm test:unit, pnpm test:int, pnpm build, then the end-to-end tests against the build (CI=true pnpm test:e2e, see Testing).

  4. On PostgreSQL, if the new version changes the schema, create a migration (pnpm payload migrate:create) and commit it with the upgrade. If pnpm install stops at a patch in patches/, see Fixes to dependencies.

  5. Read the release notes for breaking changes, especially to plugins the kit uses: form builder, nested docs, redirects and SEO.

Where the kit leans on Payload #

Most of the kit uses Payload's public API, which the tests cover. A few places go further; check them by hand when an upgrade touches them:

WhereWhat it relies on
src/app/(payload)/layout.tsxMarked as generated, but edited on purpose: it registers the schedule-publish server function (src/access/schedulePublish.ts), so only people who may publish can schedule. If an upgrade regenerates the file, put the edit back.
src/access/schedulePublish.tsImports Payload's own schedule handler from @payloadcms/ui/utilities/schedulePublishHandler.
src/jobs/start.tsStarts the jobs timer with getPayload({ config, cron: true }), reads payload.crons and calls payload._initializeCrons() after a failed start.
src/jobs/index.tsjobs.autoRun, jobs.shouldAutoRun and the payload-jobs-stats global.
src/endpoints/translate/mcp/keys.tsThe name Payload gives the API-key login strategy (<users slug>-api-key) and its header format.
src/access/roles.tsPayload adding its own collections (preferences, locks, migrations, jobs) after the plugins run.
scripts/seed-demo.ts and the other payload run scriptsHow payload run passes arguments to a script (hence the -- in the seed:demo script).

Behaviours the code works around are explained in CLAUDE.md and in comments where they are used: a Local API call with a hook's req and a locale switches req.locale for the rest of the save; sibling fields validate in parallel with beforeChange hooks; the edit view throws on a block type the config lacks; claiming a job is not atomic. The tests cover each of them, so a change in behaviour shows up as a failing test.

These guides ship with Tolk Starter 0.6.0: buyers get the same pages in their repository, next to the code they describe.