Announcing Payload 2.0: Postgres, Live Preview, Lexical RTE, and More

Published On
Announcing Payload 2.0
Announcing Payload 2.0

Payload 2.0 is now available and is by far our biggest release to date. Let's talk about what's new.

First up, if you haven't heard of Payload before, I'm James and I'm gonna take you through what we're up to. Payload is half CMS, half app framework and we are doing things differently than all the other nameless, faceless content management systems out there.

With Payload, you actually have full control over your backend and you don't have to just "surrender to SaaS". You bring your own database, and when you have the ability to customize the backend, magical things can happen. Your CMS starts to feel more like an app framework akin to Laravel, and you can build incredible things insanely quickly. But now let's get to 2.0.

This release reflects a ton of hard work from my team and we could not be more excited to hear what you think. Our primary focus is on giving engineers the tools they need to build incredible products, and this update is an attempt to give you, the developer, what you need.

Adapter Patterns

Underpinning almost everything we've done in 2.0 is a new "adapter pattern" that we've built into the fabric of Payload core. We've modularized our codebase and built in abstractions so that we can not only keep up with the speed that the internet moves, but stay ahead of it.

We've built this adapter pattern into three places:

  1. All database interactions
  2. The bundler that's used to compile the admin panel for production
  3. The rich text editor used within the admin panel

This change reflects the most upvoted aspects of our public roadmap. You've asked for additional database support, a Webpack replacement, and a move to Lexical from Slate. This is our answer for how to get there. The adapter approach allows us to keep breaking changes to an absolute minimum for projects that were built on Payload 1.0, but also allows us to support additional tech into the future.

Postgres Support

If you've been following Payload for a while, you know that our most upvoted roadmap item by far is supporting additional databases.

I'm not going to lie, I think that Payload choosing MongoDB out of the gate was a very good move for us. We have made absolutely zero compromises to the features and functions that Payload offers.

Here's an example of a few "compromises" that other headless CMS have had to make:

  1. If you use an array field, it's just a JSON column. You can't do relationships within array fields and are limited to primitive field types within array fields
  2. You can't nest "blocks"
  3. You can't define an intrinsic "order" to one-to-many relationships

In MongoDB, all of those things were trivial—and even better, we deliver all of this power significantly faster than other headless CMS. MongoDB gave us the flexibility that made this happen. Our API is clean and reflects the exact shape that you define your schema in, and you don't hit weird limits when you use certain field types.

But now that we've established our API, we've replicated everything that MongoDB can do—but in Postgres which is now available in Payload 2.0 as beta.

Everything is done in the exact way that you'd expect coming from a relational DB world. Arrays automatically create new tables (not JSON columns), and you can still do relationships within arrays. Blocks can be nested and relate to other blocks. Relationships feature ordering out-of-the-box. It's all seamless and done just simply by installing the @payloadcms/db-postgres package.

Drizzle ORM

The team and I have done a deep dive into the state of relational DB ORMs and after a lot of research, we landed on Drizzle. I'm pumped about it and I think it turned out to be an incredible choice.

Here are a few reasons we chose Drizzle to power our Postgres adapter:

  1. It's got extremely minimal overhead.
  2. It's got first-class TypeScript support, which is obviously important to Payload.
  3. It's fast.
  4. You can make a single query to the DB and auto-join all the tables you need. Huge.
  5. You can define schemas dynamically, which means we don't have to maintain a separate ORM-specific schema file and can continue to leverage the Payload config as the schema source-of-truth.

There are lots of other reasons but building on Drizzle was quite smooth. And now that we have the hard parts done, we can easily add support for MySQL and SQLite in the near future. DynamoDB, too, when Drizzle supports it.

Which database should I use?

Honestly I still think that MongoDB is the best choice for most Payload projects, because there is less technical complexity involved and fewer tables (collections) to deal with.

For example, if your configs are highly nested, and have lots of blocks, array fields, groups, localized content, etc - your schema will be simpler with MongoDB than it will be with Postgres.

But if your project calls for a schema that is well-known, flat, and might benefit from the constraints of the relational world, then by all means, go for Postgres. You should choose the database that is most appropriate for your schema, and that may even differ from project to project. Think about it in terms of choosing the best tool for the job. One is not innately better than the other.

The only thing that we do not officially support in Postgres yet is the Point field but other than that, everything we have in MongoDB can be accomplished within Postgres. Even querying on JSON / Rich Text fields.

Migrations

Payload now ships with full, first-class migration support and they are written in TypeScript. It's a beautiful thing. We now expose a new suite of bin commands:

  1. payload migrate
  2. payload migrate:status
  3. payload migrate:create
  4. payload migrate:down
  5. payload migrate:refresh
  6. payload migrate:reset
  7. payload migrate:fresh

Here's an example of what a migration will look like with Payload:

1
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/db-postgres'
2
import { sql } from 'drizzle-orm'
3
4
export async function up({ payload }: MigrateUpArgs): Promise<void> {
5
// The SQL below is automatically generated by Drizzle,
6
// and you don't have to do anything.
7
// When you're done making changes to your config, just run `payload migrate:create`
8
// and Drizzle does the hard parts.
9
10
await payload.db.drizzle.execute(sql`
11
CREATE TABLE IF NOT EXISTS "pages" (
12
"id" serial PRIMARY KEY NOT NULL,
13
"title" varchar NOT NULL,
14
"slug" varchar NOT NULL,
15
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
16
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
17
);
18
`);
19
20
// Do whatever you want here, it's TypeScript!
21
};
22
23
export async function down({ payload }: MigrateDownArgs): Promise<void> {
24
await payload.db.drizzle.execute(sql`
25
DROP TABLE "pages";
26
`);
27
};
28

When's the last time you saw a CMS with full migration support? It's great.

But in dev mode, you can choose to use push, which makes it so your local dev database stays in sync with the changes you make to your config automatically. That means that you don't have to do anything while you're working locally, and everything just works.

But when it's time to go to production, you can run your migration scripts accordingly, however you want.

Transactions

Another feature we've added is full database transaction support. Transactions in databases are great if you need to perform lots of updates in one fell swoop, but have the entire thing fail if one of the operations fails. It's a safety layer that you can opt into if you want. Payload now uses it seamlessly under the hood. This is important, because if you have a complex schema, one update operation might actually insert rows into two or more related tables. And if one fails, you don't want to have straggler rows left over. Transactions allow us to make sure this all works very gracefully.

In addition to Payload leveraging transactions under the hood, you can opt-in easily within your own hooks if you'd like simply by passing req.transactionID through to your local API operations.

Vite Support

Another highly upvoted item on our roadmap is to get rid of Webpack and allow for more modern bundlers (Vite, in specific). And we've delivered through our adapter-based approach. You can now choose between Webpack or Vite, and either one will work with Payload.

I'm not the biggest fan of Vite, I will not lie. I think it does a lot of gymnastics, and in most cases, your browser is doing a LOT of heavy lifting in dev mode. I'm a big believer in keeping things simple wherever possible. But it does indeed give your development workflow a speed boost, especially after you've done the "cold start". From there, it's fast, and I'll give it that. But I don't think this is the end-all-be-all.

We evaluated a few other options as we were looking at Vite. Specifically, we looked at rspack which claims to be a drop-in Webpack replacement built on Rust, but we ran into a few discrepancies with how rspack handles things vs. Webpack. So we paused work there. Maybe we will pick it back up at some point, but that point is not today.

I'm personally excited for Turbopack but right now it only works in NextJS. We're keeping an eye out. We'll have more to talk about on this subject soon.

Admin UI Updates

If you've been around for a while, you'll know that we've done a deep dive on how to evolve our admin UI to accommodate some new shiny features. Well, we did it. The admin UI now features a lot of new updates which significantly improve its usability for both non-technical and technical users alike. We've maximized the amount of available horizontal real estate by allowing the sidebar to collapse, and we've reorganized elements in the Edit views to allow for more customization and flexibility while you create custom views within Payload's admin UI.

Live Preview

This one blows my brain. @jacobsfletch is a maniac and pulled this off beautifully. Now you can easily add live preview to any collection or global so your editors can preview what they're doing directly inside Payload. I also have to give MASSIVE props to Dennis, Daniel, and Jan at pemedia for their work here, as they're the ones that really pushed us to ship this feature. They built a beautiful live preview plugin and were able to accomplish all of this strictly by extending Payload through a plugin, and we were able to pull their work directly into Payload core. Open source at its finest.

Upload cropping & focal point selection

We didn't even think we could get this stuff into 2.0 but @JessMarieChow nailed it. You can now crop and set focal points for image uploads seamlessly right within Payload. This is super handy because you might have auto-generated image sizes (like a hero banner or similar) where you need to set the focal point on someone's face so that their head doesn't get cropped off.

It's enabled by default for all Payload projects running on 2.0.

Completely new rich text editor

If you're in our Discord, you've probably seen Alessio in there being a maniac for the past year and a half or so. He built a Lexical editor plugin for Payload that got a lot of traction, and for good reason.

Our rich text editor has been built on Slate since we launched, and while Slate is great, it can be difficult to build custom elements for. We like that it stores JSON instead of HTML (yuck) and that has allowed us to do powerful things like dynamically reference relationships directly within the rich text JSON, but the docs are quite hard to understand and you really have to do some work to get the Slate "mental model" down. We're all about DX though. And we want to make sure that you can build whatever you want on top of Payload. Slate was proving to have a difficult dev experience and we knew there would be a lot of work in front of us to accomplish our goals with Slate.

Speaking of our goals, our rich text editor is central to the admin editor experience. As an editor, you'll be spending most of your time in the rich text field, and it's crucial that we nail it.

So we hired Alessio from our community and he's taken his Lexical editor up a notch. It's now officially supported and will be where we focus our rich text efforts from here on out.

Thanks to our new rich text editor adapter pattern, you can still use Slate, but we're going to focus new efforts on the Lexical editor.

In Lexical, custom elements are more powerful and much easier to build. It also comes with a completely new design, including a "/" menu, a popup toolbar, drag and drop, and lots more.

But by far, the fanciest thing about it in my opinion is that you can now re-use your existing Payload blocks directly within your rich text editor. It's insane. Like, truly insane.

Monorepo

In order to keep up with the new adapter patterns I've discussed above, and not end up with one fafillion separate repos, we've moved Payload over to PNPM and it's now a monorepo. This is going to dramatically improve the visibility we have into our other packages—including their issues, discussions, and more. Over time we are going to consolidate our first-party plugins, boilerplates, and everything else directly into the Payload monorepo which is going to pay dividends across the entire ecosystem.

It's also helped us ensure test coverage with things like database adapters. Our entire integration test suite runs on MongoDB and on Postgres. It's beautiful.

Performance boosts

In 2.0, Payload is now faster as well, and in some places—significantly faster. Querying drafts used to become a bit slower if you had tens of thousands of documents, but we've completely refactored how the draft querying works and it's now lightning fast no matter how many docs you have.

There are lots of other changes that have taken place under the hood. Too many to name here. But over the next few weeks, we'll be covering this stuff in further detail into the future.

What's next?

If you've made it this far, good work. We're pumped over here and we're just getting started. Here's what we're going to do next:

  1. Improve the way we "split" server-side code and admin code from the Payload config.
  2. Evaluate a move to ESM.
  3. Add multiplayer editing.
  4. Add true visual editing directly from the frontend of your website.
  5. Add more database support, like SQLite and MySQL.
  6. Add controls to manage relationships on a fine-grained level.
  7. Add AI translations.
  8. Add AI writing assistants.
  9. Add AI image generation, directly within Payload.
  10. Add a realtime data API.
  11. ??? There's a big announcement in the works...but we're not ready to talk about it yet.

Lots more little stuff is on the way but we've got our work cut out for us.