payload
The Payload CMS for Evolve: a plugin that sets up your Payload config with the pages, media and content blocks of the shared content contract, and a module that serves that contract from the same database.
When to use
Use it when Payload is your CMS, typically self-hosted next to the commerce
engine. The admin app loads evolveCms() in its Payload config, and the
storefront's content subgraph runs PayloadModule from the very same config,
talking to the database through Payload's Local API. Every collection can be
scoped to a store, and catalog pickers can let editors choose products and
categories from the commerce engine. Use @evolve-framework/contentful or
@evolve-framework/storyblok instead for those CMSes.
Get started
Install the package with the peer dependencies it needs. It comes from the Evolve registry, which needs a one-time sign-in.
pnpm add @evolve-framework/payload @evolve-framework/core @payloadcms/richtext-lexical @payloadcms/ui fastify graphql graphql-yoga payload reactRequirements10
@evolve-framework/core>= 1.0.0 < 2.0.0@payloadcms/plugin-multi-tenant>= 3.87.0 < 4.0.0optional@payloadcms/richtext-lexical>= 3.87.0 < 4.0.0@payloadcms/ui^3@types/node>=22optionalfastify>= 5.12.1 < 6.0.0graphql>= 16.0.0 < 17.0.0graphql-yoga>= 5.21.3 < 6.0.0payload>= 3.87.0 < 4.0.0react^19
For example:
// payload.config.ts, loaded by the admin app and by the subgraphimport { buildConfig } from "payload";import { evolveCms } from "@evolve-framework/payload/config";import { db } from "./db.ts"; // your database adapter, e.g. postgresAdapter()
export default buildConfig({ db, secret: process.env.PAYLOAD_SECRET ?? "", // the editor and the rest of your Payload config plugins: [...evolveCms()],});Modules
PayloadModule
Module wiring the Payload resolvers and typedefs into a Domain Service.
Unlike the Contentful and Storyblok modules this one speaks no HTTP to the
CMS: it boots Payload's Local API in-process and reads straight from the
database the admin app writes to. There is no delivery client, no API key and
no page cache to invalidate -- and no webhook route, because the
content-modified events are published by an afterChange hook running in
the admin process.
Only one PayloadModule instance per process is supported -- the Payload instance and the mapper registry are held as module-scope singletons.
import { PayloadModule } from "@evolve-framework/payload";new PayloadModule(options: PayloadModuleOptions)| Option | Type | Description |
|---|---|---|
config | SanitizedConfig | Promise<SanitizedConfig> | The project's payload config -- the very same module the admin app loads.
Import it directly ( |
articlesPerProduct? | number | How many articles a product shows at most. Defaults to 4. |
blocks? | ContentBlockDef[] | The complete set of content blocks the module serves. Defaults to the
built-in MorePass the same array you passed to |
locales? | LocaleMap | Maps store-context locales ( |
homePath? | string | Path the site root is stored under. Frontends ask for the root with an empty path; this is what that resolves to. Defaults to "homepage". |
multiTenant? | boolean | Whether the config is tenant-scoped, i.e. whether reads must match the
request's |
tenants? | TenantMap | Maps store keys onto tenant slugs, for stores that share a tenant or whose tenant is named differently. A store missing from it reads the tenant whose slug is its store key. When set, catalog pages are kept in the mapped tenants only; when not, in every tenant. |
mappers? | ((registry: MapperRegistry) => void) | Optional callback invoked with the pre-populated MapperRegistry. Use it to add or override mappers for custom Payload blocks. |
ContentBlockDef8The fields of blocks.
| Field | Type | Description |
|---|---|---|
id | string | Payload block slug, e.g. "promo_list". Matches the incoming |
typename | string | Output GraphQL typename, e.g. "PromoList". |
typeDefs? | string | SDL fragment for the block's output type (+ its union membership). Omitted for blocks with no GraphQL surface of their own. |
payload? | Block | The Payload block config registered into MoreAny field whose name matches a declared slot and whose type is |
groups? | string[] | Groups this block is a member of (which slots it can fill). |
slots? | Record<string, Slot> | Slots this block exposes (what it can contain). |
mapper? | ContentBlockMapper | Maps a Payload block of this type onto the block's GraphQL type. Omitted
only for defs whose type is never mapped on its own -- pages (see
|
pageMapper? | PageMapper | Maps a document of this page type's collection onto its GraphQL page. Set only on page types, which the page resolvers dispatch to by id. |
Extends AbstractModule<PayloadLoaderContext>.
Commands
Installing this package adds these commands to the evolve CLI.
evolve eject: Eject built-in blocks, collections or fields into your project so you own them
Entry points
@evolve-framework/payload | PayloadModule, the built-in content blocks and defineContentBlock for your own, and catalog page sync.67 exports |
@evolve-framework/payload/config | evolveCms, the Payload plugin with the pages and media collections, multi-tenancy by store and the catalog pickers, plus the collections on their own.41 exports |
@evolve-framework/payload/graphql/context | The request context the module's resolvers expect.3 exports |
@evolve-framework/payload/graphql/types | The generated GraphQL types of the content contract.237 exports |
@evolve-framework/payload/testing | A test configuration, tenant seeding and a test executor.3 exports |
@evolve-framework/payload/admin | The admin UI components for picking products, variants and categories from the catalog.10 exports |
@evolve-framework/payload/migrations/drop-removed-schema | A predefined migration, for payload migrate:create --file, that drops the tables, enums and columns the config no longer declares.2 exports |

