Why we love Payload CMS (and what we compared it against)
We rebuilt scorchsoft.com on Payload CMS after seriously considering WordPress and MODX. An honest look at what unopinionated schema-as-code buys you, where the alternatives win, and why a content model that lives in the repository changes how you work with AI.

In summary
We rebuilt scorchsoft.com on Payload CMS after seriously considering WordPress and MODX. This is the honest account: what unopinionated schema-as-code buys you, where the alternatives genuinely win, the page and block conventions we settled on, and why a content model that lives in the repository changed how we work with AI agents.
Key takeaways
- Payload gives you a config-as-code schema and generates the admin UI, the database schema and the API from it — it has no opinion about your front end.
- WordPress, MODX, Strapi, Sanity, Contentful, Directus and Ghost each beat Payload on something. Payload wins when the content model is genuinely yours and the build is part website, part application.
- A page is a document with a layout array of blocks; each block is a co-located config.ts schema plus a Component.tsx server component.
- Shared blocks live in a Block Library page and are pulled in by reference, so one edit updates every page that uses them.
- Because the schema lives in the repository, an AI agent can read the real content model rather than a prose description of it.
- AI can only ever create or edit drafts. Publishing is a human action, enforced in four independent places.
We rebuilt scorchsoft.com on Payload CMS. This article is the honest version of why: what "unopinionated" actually buys you, what we compared Payload against before committing, the conventions we settled on, and the part that decided it for us — the schema lives in the repository as code, so an AI agent can read the whole content model and still never publish a word without a human.
What "unopinionated" actually buys you
Most content management systems arrive with an opinion about your front end. WordPress has a theme layer. MODX has templates and chunks. Ghost has a blog. You can fight any of them, and people do, but you are pushing against the grain.
Payload has no opinion. You describe your content in TypeScript, and Payload generates a database schema, a REST and GraphQL API, and an admin panel to match. What you render is entirely your problem, which sounds like a burden until you realise it is the whole point. Our front end is Next.js 15 with the App Router and Tailwind. Payload does not know that, and does not care.
Three things follow in practice.
The admin panel is derived from the schema, not maintained alongside it. When we add a field to a block, the editing interface for that field exists the moment the code does. Nobody builds a form.
The content model can match the business, not the CMS. Our site has 15 collections, and only a few of them are what a CMS vendor would call content. Assessments and assessment submissions are business objects. Webinar sessions are scheduled events. The migration log is an operational record of which legacy page has been brought across. None of that needed a plugin.
The front end can be fast because we own it. Every page is a server component by default. Client components are the exception, they carry a .client.tsx suffix so you can see them in a directory listing, and the rule we hold ourselves to is that they stay small and leaf-level. An FAQ section is a server component; only the accordion that expands is a client island.
What we compared it against
We did not arrive at Payload by elimination. We seriously considered two options, and looked hard at several more. Being one-sided about this would be useless to you, so here is the fair version.

- WordPress. The biggest plugin library in existence, editors who already know it, and a hosting market that makes it nearly free to run. If your site is mostly articles and marketing pages, and you want to hand it to a non-technical team tomorrow, WordPress is a completely defensible answer — it is why we still offer WordPress web development. What it is not is a typed application back end. Structured content means Advanced Custom Fields, and the schema then lives in a database or an export file rather than in your repository. We wanted the content model under version control.
- MODX. The legacy scorchsoft.com ran on MODX for years, and it was a reasonable choice at the time: flexible templating, no imposed content types, and a small, sane core. We still maintain MODX sites and still like it, which is why MODX app development is on our technologies list. But the community is small, the front end is coupled to the templating layer, and there is no path to a React front end that is not a rewrite. Rebuilding on MODX would have bought us another decade of the same constraints.
- Strapi. The closest comparison, and a good product. Self-hosted, Node, plugin-friendly, with a large community. The difference that mattered to us is where the content model lives: Strapi's content-type builder writes schema files, but the admin-first workflow encourages people to click types into existence, and the generated code is a serialisation of the UI rather than the source of truth. Payload's config is the source, and TypeScript types are generated from it.
- Sanity. Excellent editing experience, real-time collaboration, and a genuinely good structured-content model with schemas defined in code. If hosted editing and multi-user live collaboration are top of your list, Sanity beats Payload on both. The trade is that your content sits in Sanity's hosted datastore, and querying it is GROQ rather than SQL. We wanted our own Postgres.
- Contentful. Enterprise operations done properly: uptime, roles, audit, localisation at scale, and a commercial relationship to lean on. If you are a large organisation with a procurement process, that is worth real money. For a 250-page site where we are the engineering team, we would have been paying for governance we already have and giving up the ability to run a migration ourselves.
- Directus. A database-first approach — point it at an existing SQL schema and it gives you an admin panel over it. If you already have the database and want a back office on top, that is a better fit than Payload. We were starting from nothing, so the schema-as-code direction suited us better.
- Ghost. The best pure publishing experience on this list. If the job is a blog or a newsletter, use Ghost and stop reading. It is not trying to be an application back end and does not pretend to be.
Payload's advantage is narrow and specific: it is the one on this list where the entire content model, the admin behaviour and the front end all live in one typed repository, and where the same product is plausible as an application back end. That is exactly the shape of work we do, so it won. On a different brief, several of the above would beat it.
How we model a page
A page in our system is a document with a layout field, and layout is an array of blocks. That is close to the whole idea. The page's URL is not stored by hand — it is a path computed from the parent hierarchy and the slug, so moving a page under a new parent re-computes the children too.

Around that sit the fields that make a page behave: draft and published versions, SEO metadata, listing and navigation fields for how it appears in cards and menus, and a pageType.
pageType is worth dwelling on, because it is where a lot of teams accidentally build a template system. Ours has six values — standard, solution, case study, listing, landing and block library — and every one of them changes behaviour, not layout. A landing page is kept out of the AI-readable llms.txt file and out of directory listings while staying publicly routable and listed in both sitemaps. A block library page can be referenced by other pages. None of them select a different renderer. The rule we enforce in code review is that a page type may never appear as a positive branch in a render path: the moment you write "if this is a landing page, render X", you have grown a template system and lost the composability that made blocks worth having.
How we build a block
Every block is a folder with two files that sit next to each other.

config.ts is the Payload schema — fields only, no React. Component.tsx is the server component that renders it, and its props are typed from the generated payload-types.ts. If the block needs interactivity, a third file appears with a .client.tsx suffix and contains only the interactive part. A single RenderBlocks.tsx maps each blockType string to its component.
The co-location is not a filing preference. It means a block's schema and its rendering cannot drift apart in review, because they are in the same diff. Add a field to config.ts and the component beside it either uses the field or visibly does not. Change the component's props and the schema is right there to change with it. We have more than 50 block types, and this is the only reason that number is manageable rather than frightening.
It is also why the block catalogue can be generated rather than written. Every block carries a metadata entry, and an admin-only Component Catalogue page renders the whole set at request time. A new block appears in the catalogue as soon as its metadata exists — there is no inventory document to keep in step, which means there is no inventory document to be wrong.
Shared blocks: edit once, change everywhere
Some sections genuinely are the same everywhere. The closing call-to-action banner, the client logo strip, a "what we do" chip grid.

Copying those onto 200 pages is how a site starts disagreeing with itself. So we made a Block Library page — a page whose pageType is blockLibrary, living at /_blocks — that holds the canonical instances. Any other page can drop in a sharedBlockRef block that points at one of them. The section renders from the library, and editing the library changes every page that references it. Our shared closing call-to-action is referenced by over 200 page instances; changing its wording is one edit.
Two rules keep it honest. Only the library page can be referenced, so nobody can accidentally make the home page a dependency of 40 others. And a reference renders the published library, so a draft change to a shared block does not leak onto live pages before a human approves it.
The rest of the conventions
The parts that are less visible but do most of the work:
- Collections and globals. Collections are the repeating things — pages, posts, media, users, guides, glossary terms, assessments. Globals are the one-of-a-kind things — the header, the footer, site settings, integration settings. A global gets an editing screen without needing to be a collection with exactly one row in it.
- Access control as functions. Access rules are plain functions returning a boolean or a query constraint. We run three roles: admin, editor, and a dedicated AI role. The AI role can create and edit drafts, and only through the MCP endpoint; on the ordinary REST and admin API it cannot write at all, which is what makes a leaked key inert.
- Hooks for the things schema cannot express. Computing a page's path from its parents. Minting a 301 redirect when a migrated page's URL differs from its legacy one. Validating every internal link before a page is allowed to publish. These are collection hooks, in the repo, testable.
- Migrations, never auto-push. Payload can push schema changes straight into the database in development. We turned that off everywhere. Every schema change is a migration file, committed, applied in order, and applied automatically at container boot in production. We are 47 migrations in. It is slower to work with and it is the reason no deploy has ever been a surprise.
- Caching with tags. Every page is dynamic, so the data layer is the only cache. List queries in blocks go through a cached finder tagged by collection, and the collection's revalidate hook fires that tag on write. Because cached documents are populated, a change to an embedded record — a media item, a category — has to bust the caches that embed it, so the dependency map is written down in one file rather than rediscovered each time.
Payload is an application back end, not just a CMS
This is the part people underestimate. A collection is not a "content type" in any limiting sense — it is a typed business object with a database table, an API, an admin screen, access rules and hooks. Once you have those four things, quite a lot of software is just collections.

Our assessments are the clearest example. An assessment is a scored questionnaire: its questions, weightings and score bands are a collection. A visitor's answers land in a separate submissions collection. The runner that serves it at /assessments/<slug>/take is our own React, but everything behind it — the definition, the storage, the access rules, the admin screens the team uses to read results — came free with the content model.
The same is true of the form builder and the lead pipeline. Forms are documents. Submissions are documents. The notification email, the CRM handoff and the analytics event hang off hooks on the submission. Nothing about that is a "website" feature.
If you are choosing a stack for something that is part marketing site and part application — a portal with a public front, a product with a content marketing layer, a tool that needs an admin back office — that overlap is the argument. You do not have to run a CMS and a separate back end and keep them agreeing with each other. This is why we now offer Payload CMS development as a service line rather than treating it as an internal preference.
The real reason we chose it: the schema is in the repository
Here is the thing that decided it.
When your content model lives in a hosted dashboard, an AI coding agent cannot see it. It can be told about it, in prose, in a prompt, at which point the description and the reality start drifting the moment somebody adds a field. Everything the agent then produces is a guess dressed as a fact.
When the content model is TypeScript in a git repository, the agent reads the actual thing. It can open every block's config.ts and know exactly which fields exist, which are required, and what the enum values are. It can read the generated types. It can read RenderBlocks.tsx and know which block types actually render. It can read the conventions in our own documentation, in the same checkout, at the same commit.
That is the difference between an AI that writes plausible content and an AI that writes content that validates. We keep the house conventions as committed skills in the repo — how to compose a page, the canonical call-to-action destinations, the voice and tone rules, the blocks that suit a given brief. An agent working on this site reads those the way a new developer would read the onboarding docs, except it does it every time.
The MCP connector
Reading the repository gets you a model that understands the site. Changing the site needs a way in.

So we built a Model Context Protocol server into the application itself. It exposes a controlled set of collections — pages, posts, podcasts, guides, glossary terms and a handful more — plus custom tools that do the jobs a generic API cannot: convert markdown to the editor's format, run a deterministic on-page SEO audit against a draft, screenshot a page, upload an image, look up real keyword data, validate a page plan before anything is written.
The effect is that an agent can do a whole unit of work end to end. Plan the page from the block catalogue, write the copy against the voice rules, generate or find the imagery, land it, audit it, fix what the audit flagged, and hand back a URL. It is the same pattern we build for clients through MCP integration and development — the interesting part is not the protocol, it is having a system worth connecting it to.
AI writes drafts. Humans publish.
We were only willing to do any of this because of the boundary, so it is worth being precise about it rather than waving at "human in the loop".

Every AI write to this site lands as a draft. Publishing is a human action in the admin panel. That is not a policy in a document, it is enforced in four independent places:
- The role. The AI has its own user role. It can create and update, never delete, never touch users or settings — and only through the MCP endpoint. Off that endpoint the same credentials cannot write at all.
- The tool layer. The MCP tools are patched so that a client-supplied publish status is stripped before it reaches Payload, and every create and update is forced into draft mode. An agent cannot ask to publish, because the request never carries the field.
- The hook. Independently of the patch, a
beforeChangehook on pages, posts and podcasts hard-overrides the status to draft on any request that came in over MCP. Belt and braces, deliberately: either mechanism alone would do it, and neither is trusted to be the only one. - Provenance. The same hook stamps every AI revision with who wrote it, which environment it landed in, and whether it was generated fresh or migrated from the legacy site. Because that stamp is on the revision, the admin can offer a "restore last human revision" button that finds the most recent version not written by an AI and rolls back to it.
On top of those sits the publish gate — a validation hook that runs only when a human presses publish. It walks every internal link on the page and blocks the publish if one is broken: a blank URL, a path that matches no page or redirect, an anchor no block defines. Links whose target merely does not exist yet are warnings rather than blocks, because a cross-linked set of drafts would otherwise be unpublishable in any order. The warnings are summarised onto a badge next to the publish button, so the person clicking it can see what they are accepting.
And the admin dashboard opens on a panel listing AI drafts awaiting review, with a count of what the AI wrote this week. The review queue is the first thing you see.
The summary is simple. AI makes authoring faster. A person is still accountable for everything that goes live, and can see exactly what changed and undo it in one click.
Should you build on Payload?
Not always.
If the site is a blog, use Ghost. If a non-technical team needs to own it with no developer in the loop, WordPress is kinder. If you need hosted, real-time, multi-editor collaboration today, Sanity is ahead. If you have an enterprise procurement process and a localisation programme, Contentful earns its price.
Choose Payload when the content model is genuinely yours — when the thing you are building is part website and part application, when you want the schema, the rendering and the deployment under one set of tests, and when you want your own database. And choose it if you intend to work with AI agents seriously, because a content model an agent can read is worth more than any amount of prompt engineering about one it cannot.
That last point is the one we did not fully anticipate when we started, and it is now the first thing we say about it.
If you are weighing this up for a real project, our Payload CMS development page sets out how we scope and build on it, and the wider technologies we work with shows what it usually sits alongside. If you already know roughly what you want built, ask us for a free quote and we will come back with a number and a plan.
Key topics covered
- What "unopinionated" means in practice
- Payload vs WordPress, MODX, Strapi, Sanity, Contentful, Directus and Ghost
- Modelling a page as a stack of blocks
- Block anatomy: schema and component, co-located
- Shared blocks by reference
- Collections, access control, hooks and migrations
- Payload as an application back end
- Schema-as-code and AI-assisted authoring
- The MCP connector
- Draft-only AI writes and the publish gate
Sources referenced
Thinking about building on Payload?
We scope, build and run Payload CMS projects — from content-led sites to portals and internal tools that need a real back end.
Share
Want to talk about your project?
Tell us what you’re trying to achieve and we’ll map the fastest credible path.
