Skip to content
Launch
Esc
↑↓navigate↵open⌘Jpreview
On this page

Payload

Payload as one vinext app on Workers, with D1 as its database by default and Postgres through Hyperdrive kept as an option.

The Payload reference is a single app: the Payload admin, plus front-end pages that read content through Payload’s Local API in the same Worker. There is no separate API call between the front-end and the CMS. It runs Payload 4 (beta) under vinext on Cloudflare Workers, with media in R2.

Database: D1 by default

Decision (2026-10-29): the Payload app uses Cloudflare D1. Postgres through Hyperdrive stays available behind PAYLOAD_DB=postgres, and the two still need comparing on real Workers. See ADR 0007.

D1 Postgres through Hyperdrive
Status Default Option, PAYLOAD_DB=postgres
Admin, uploads, drafts, REST and GraphQL Pass Pass
Migrations on boot and the Payload CLI Pass Pass
Extra work needed None A request-scoped connection pool (below)
Measured on real Workers Not yet Not yet

Why D1 for now: both databases passed the same end-to-end browser test in local workerd, and D1 needs nothing extra. Postgres needs a connection pool that opens a connection per request, because workerd ties a socket to the request that opened it. Hyperdrive does the real pooling.

No performance difference has been measured yet. Local runs say nothing about network latency, which is where the two will differ, so the comparison waits for a deploy.

PAYLOAD_DB is replaced at build time, so the unused adapter is left out of the bundle and cloudflare.config.ts declares only the matching binding.

The demo

pnpm seed builds the same invented site as the WordPress seed, so the two references can be compared page by page.

  • Pages hold a layout of blocks, one per section in the content model, each with a Section options tab (skin, width, padding, anchor, hidden). Field names match the WordPress page builder, so the Payload adapter only maps rich text, links and media.
  • Heroes come in five variants, one of them free-form rich text.
  • Rich text is Lexical, with blocks for buttons, images, pullquotes and video or X embeds.
  • News: posts with authors and categories, listed at /news.
  • Forms from Payload’s form-builder plugin. A form section posts to the app, which keeps only the form’s own fields, checks the required ones and saves the submission with the visitor’s access. Email delivery is not wired up yet.
  • Preview: pages and posts autosave drafts, and editors see them through the preview button and Live Preview, which re-renders on each autosave.

On localhost only, the login page offers a one-click sign-in as the first user. It is hidden through a tunnel and on a deployed Worker.

Deployed

The app runs as the Worker launch-payload, with its D1 database and R2 bucket. Branch Previews are off by default, because they would share production’s bindings. The app’s README covers the deploy, secrets and Workers Builds setup.

What else the research found

  • vite dev does not work with Payload yet. Develop with vite build and vite preview until it does.
  • Every Local API call made for a visitor passes the visitor’s user and overrideAccess: false explicitly. Only trusted server code, such as seeding and migrations, overrides access.
  • There is no schema push on Workers. Every environment uses migrations, and the Worker applies pending ones on its first request.
  • Image cropping and focal points are off, because there is no sharp on Workers.

The full write-up, with every check and workaround, is Payload as one app under vinext on Workers.

Was this page helpful?