Roadmap Released

A self-hosted, headless TypeScript CMS



Maintain complete control over your Express app.



Take over the Admin panel with your own components.



Anything Payload does can be done through GraphQL.



For when GraphQL isn't your thing.



Easily localize on a field or document level.



Limitless auth & access control all built in.


File Upload

Manage locally stored file uploads.



HTTP-only cookies, max login attempts, and more.


Rich Text

Use and extend the built-in rich text editor.


Block Editor

Build dynamic layouts with Blocks.

Payload's significance lies in its simplicity. From auth to upload, Payload gives you what you need to build your apps and then stays out of your way.

Payload Headless CMS React and TypeScript Admin Dashboard
If you know JavaScript, you know Payload.

Part of why we built Payload is because we were tired of having to learn how to do things everybody else's special way.

We stick to familiar concepts and understandable abstractions, so you can practice TypeScript or JavaScript—not Payload.

Read the docs
  • Keep full control over your Express app.

    Nothing is imposed on the structure of your app. Just initialize Payload and pass it your Express app. Maintain your own functionality outside of Payload.

    Read more
    const payload = require('payload');
    const express = require('express');
    const app = express();
    // Do whatever you want with your app.
    // Just pass it to Payload and everything
    // will be scoped to Payload routers.
    mongoURL: 'mongodb://localhost/payload',
    express: app,
    app.listen(process.env.PORT, () => {
    console.log(`Application listening on ${3000}...`);
  • Extremely powerful function-based access control.

    Secure your data by writing access control functions based on either a document or field level. Build out your own RBAC or any access control pattern you need.

    Read more
    const Orders = {
    // ...
    access: {
    create: () => true, // Everyone can create
    read: ({ req: { user } }) => {
    if (user) {
    return { // Users can only read their own
    owner: { equals:, },
    return false; // Not logged in? Can't read any
    update: ({ req: { user } }) => {
    // Only Admins can update Orders
    if (user.roles.includes('admin')) return true;
    return false;
    delete: () => false, // No one can delete
  • Hooks for every action Payload provides.

    Both document and field-level hooks expose a ton of potential. Customize output, sanitize incoming data, or easily integrate with third-party platforms. The pattern is extremely powerful.

    Read more
    const Customers = {
    // ...
    hooks: {
    beforeChange: [
    // Before the Customer is created or updated,
    // sync it to Hubspot
    afterChange: [
    // Send the new Customer a welcome email
    // after it's successfully created
    afterRead: [
    // Dynamically append user's active subscriptions
    // straight from Stripe
  • Show and hide fields with conditional logic.

    Inspired by ACF, all admin fields can be toggled on or off based on a function that you define. Base your condition on what other fields are doing or whatever you want. It's just a function.

    Read more
    // ...
    fields: [
    name: 'enableGreeting',
    type: 'checkbox',
    name: 'greeting',
    type: 'text',
    admin: {
    condition: (data) => {
    if (data.enableGreeting) {
    // if `enableGreeting` is checked, show
    return true;
    } else {
    // Otherwise hide it
    return false;
  • Swap in your own components.

    Every high-level component in the Admin dashboard is swappable with your own React component. Customize existing views or field types—or even add your own routes—with an extremely intuitive API.

    Read more
    import { useFieldType } from 'payload/components/forms';
    // Customize anything within the Payload Admin panel
    // by providing your own React component(s)
    const CustomTextField = ({ path }) => {
    // All you need to handle is sending and
    // receiving the form's value
    const { value, setValue } = useFieldType({ path });
    return (
    onChange={(e) => setValue(}