Payload's plugin system is built around a simple contract: a plugin is a function that receives a config and returns a modified config. That simplicity is intentional and permanent — the basics will never change.
This page covers the advanced plugin API that makes plugins more powerful: execution ordering via order, typed cross-plugin communication via RegisteredPlugins, and the definePlugin helper that ties it all together.
The plain function form is unchanged and will always be supported:
Everything below builds on top of this — none of it is required for simple plugins.
definePlugin — recommended for published pluginsdefinePlugin replaces the boilerplate of manually attaching slug, order, and options to the function after the fact. Your plugin function receives a single object containing config, a plugins map, and any user-provided options spread directly in:
Import it from payload:
The result of definePlugin is a factory function — call it with your options to get a Plugin:
orderBy default, plugins execute in the order they appear in the plugins array. Setting order lets you declare execution order explicitly, regardless of array position.
Lower order values run first. The default is 0.
Settle on a convention so the ecosystem converges:
Range | Use case |
|---|---|
Negative | Must run before everything — config normalization, polyfills |
| Default — no dependencies on other plugins |
| Depends on collections or fields added by other plugins |
| Must run last — audit, introspection, or final-config plugins |
Plugins often need to be aware of each other. The pattern for this is:
slug exposes its options object — the same object passed at call timeplugins map and mutates those options before the first plugin runsSince options are resolved before any plugin runs, this works cleanly without re-execution.
plugins mapEvery plugin created with definePlugin receives a plugins map — a slug-keyed object of all plugins in the config. No imports needed:
For registered slugs (see below), the plugins map entries are automatically typed — no cast needed.
RegisteredPlugins — module augmentation for type safetyPlugin packages can register their slug and options type by augmenting the RegisteredPlugins interface. This ships with the package and is activated automatically when the plugin is imported — no code generation required.
Once a plugin package augments RegisteredPlugins, any project that imports it gets typed access via the plugins map:
Here is a complete example of two decoupled plugins that communicate via the plugins map. The writer plugin (order 1) runs first and injects an item into the reader plugin's options. The reader plugin (order 10) runs second and sees the injected item.
Use cross-plugin mutation (plugins map + options mutation) when:
Use direct options when:
You can check whether a plugin is present without importing it:
Or via the plugins map inside a definePlugin function: