v3.83.0 (2026-04-15)
🚀 Features
- expand plugin API (#16247) (54189e1)
- add profiling utilities for performance analysis (#16198) (9391c20)
- add internal plugin priority and slug api for cross-plugin discovery (#16244) (5f5694f)
- hide slug field buttons when field is read-only (#14824) (67c2c47)
- cpa: add --agent flag for coding agent skill installation (#16278) (9f9f343)
- drizzle: add uuidv7 support (#16113) (ac01e82)
- email-resend: add Custom headers for the Resend adapter (#15645) (a7dd17c)
- next: add support for custom collection views (#16243) (835a0ad)
- plugin-form-builder: change checkbox label from 'Default Value' to 'Checked by default' (#15229) (b3d2054)
- plugin-mcp: allow external plugins to extend mcp plugin (#16245) (ac4fc31)
- richtext-lexical: add view override system for custom node rendering (#14244) (1ef43eb)
- storage-*: add useCompositePrefixes option and fix client upload prefix handling (#16230) (74aa825)
Expanded Plugin API — New definePlugin helper introduces opt-in execution ordering, cross-plugin discovery via a slug-keyed plugins map, and module augmentation for type-safe plugin options. The existing (config) => config contract remains unchanged. #16247
import { definePlugin } from 'payload'
export const seoPlugin = definePlugin<SEOPluginOptions>({
slug: 'plugin-seo',
order: 10,
plugin: ({ config, plugins, collections, generateTitle }) => ({
...config,
// collections and generateTitle come from SEOPluginOptions
}),
})Profiling Utilities — Lightweight timeSync and timeAsync wrappers for measuring function execution time during development. Wrap any function to capture its duration, then call printProfileResults for a formatted timing table. Not intended for production use. #16198
Internal Plugin Priority & Slug API — Plugins can now attach priority, slug, and options properties for execution ordering and cross-plugin discovery. Lower priority runs first; other plugins can find each other by slug via config.plugins without imports. Marked @internal for now. #16244
Hidden Slug Field Buttons on Read-Only — The Generate and Lock/Unlock buttons on slug fields are now automatically hidden when the field is read-only, removing controls that serve no purpose in that state. #14824
Agent Flag for CPA (cpa) — create-payload-app now supports a --agent / -a flag (claude, codex, cursor) that downloads the Payload coding skill from GitHub and installs it in the correct directory for your agent. A root-level CLAUDE.md or AGENTS.md is written for discoverability. Use --no-agent to skip. #16278
UUIDv7 Support (drizzle) — New idType: 'uuidv7' option for Postgres and SQLite adapters generates time-ordered UUIDs that are friendlier for B-tree indexes than random v4 UUIDs, while using the same storage column type. IDs are generated in application code so older Postgres versions are supported. #16113
Custom Email Headers (email-resend) — The Resend adapter now passes custom headers from sendEmail options to the Resend API, enabling features like List-Unsubscribe headers that were previously silently dropped. #15645
await payload.sendEmail({
from: "Test <test@domain.com>",
to: "jimmybillbob@example.com",
subject: "Email with custom headers",
html: html,
headers: {
"List-Unsubscribe": "<https://domain.com/unsubscribe>",
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
},
});Custom Collection Views (next) — Register custom views at the collection level via admin.components.views[key] with a Component and path. Folders take routing precedence over custom views on upload collections. #16243
{
slug: 'products',
admin: {
components: {
views: {
grid: {
Component: '/components/GridView',
path: '/grid',
exact: true,
},
},
},
},
}Checkbox Label Clarity (plugin-form-builder) — The form builder checkbox field label was changed from "Default Value" to "Checked by default" to eliminate confusion about whether the checkbox toggles a default value or sets the initial checked state. #15229
Extensible MCP Plugin (plugin-mcp) — External plugins can now extend plugin-mcp by finding it via slug in config.plugins and injecting custom MCP tools into its options. Also exports the MCPPluginConfig type for type-safe tool injection. #16245
View Override System for Custom Node Rendering (richtext-lexical) —
export const myViews: LexicalEditorViewMap = {
default: {
heading: {
createDOM() {
const h2 = document.createElement('h2')
h2.textContent = 'Custom Heading'
return h2
},
},
horizontalRule: {
Component: () => <div className="custom-hr">---</div>,
},
link: {
html: '<a href="#">Custom Link</a>',
},
},
}{
fields: [
{
name: 'content',
type: 'richText',
editor: lexicalEditor({
views: '/path/to/views.tsx#myViews',
}),
},
]
}Composite Prefixes for Storage Adapters (storage-*) — New useCompositePrefixes option combines collection and document prefixes instead of one overriding the other. Also fixes a bug where client uploads ignored document prefix entirely. Applies to S3, Azure, GCS, R2, and Vercel Blob. #16230
| Mode | Collection Prefix | Doc Prefix | Result |
|---|---|---|---|
false (default) |
media-folder |
user-123 |
user-123/file.jpg |
true |
media-folder |
user-123 |
media-folder/user-123/file.jpg |
🐛 Bug Fixes
- restore falling back to current document values for
undefinedfields (#16272) (4b4d61c) - enforce mimeTypes restriction when useTempFiles is enabled (#16255) (bb749a5)
- prevent cron from permanently dying after handler errors (#16219) (c5e0e02)
- use safe property assignment in deepMergeSimple (#16271) (2b23010)
- handle concurrent autosave write conflicts gracefully (#16216) (e8502fa)
- use instanceof instead of constructor name in formatErrors (#16089) (216d162)
- parse JSON string where query param (#15745) (2a26b5a)
- expose type for field's
position(#14390) (d9b3c07) - default virtual fields to readOnly in admin UI (#16016) (72396f6)
- localized array,group,blocks fields duplicate with empty values (#15849) (067e14f)
- fixes importMap type in monorepo usage (#15203) (79f4cc7)
- db-postgres: bump
drizzle-ormto0.45.2to resolve an SQL injection vulnerability andpgto8.20.0(#16168) (af1a932) - drizzle: surface connection errors in beginTransaction instead of hanging forever (#16220) (1e1c591)
- drizzle: ensure
getPrimaryDbis used for all write operations (#16240) (aa44649) - email-resend: support path and preserve base64 content in mapAttachments (#16094) (57e71f5)
- next: persist language cookie across browser sessions (#15081) (0acff80)
- plugin-ecommerce: price not showing as 0 when explicitly set (#16289) (62aa42b)
- plugin-ecommerce: priceInput does not respect required field status (#15783) (ed1c874)
- plugin-import-export: hide invalid sortBy options (#11676) (9d6bf0b)
- plugin-search: serialize reindexing to prevent locale data race conditions (#15917) (79d1b6b)
- plugin-seo,richtext-lexical: add missing translations (#11859) (da27afc)
- plugin-stripe: supports webhooks within serverless environments (#10890) (4179bf3)
- translations: 'noResults' translation expected a singular label, rewrite sentence sine a plural label is injected. (#13360) (cb0ce1c)
- translations: modified Spanish translations to be gender neutral (#15796) (a00267d)
- translations: fall back to base language tag for Accept-Language matching (#15076) (cf95cad)
- ui: usePreventLeave default message (#15042) (8de32a2)
- ui: use i18n for live preview "Open in new window" tooltip (#16038) (173b453)
- ui: preselect folder when bulk uploading from inside a folder (#16030) (71a6c60)
- ui: prevent NaN aspect ratio in createThumbnail on svg files (#10488) (b332bff)
- ui: thread cache tag to list view thumbnails (#11741) (5afcef5)
⚡ Performance
🛠 Refactors
- simplify storage adapter handler pattern (#16231) (6690769)
- clean up dead code and minor bugs in job queue system (#16234) (e49c224)
📚 Documentation
- documents upload node markdown placeholder (#16135) (a9b0ca7)
- change import to use type import for GlobalConfig (#11578) (289e2b6)
🧪 Tests
- remove debug console.log left in SlugField overrides fields test suite (#16279) (22e2466)
- plugin-multi-tenant: verify that tenant selector respects access control (#14916) (0516803)
📝 Templates
📓 Examples
⚙️ CI
- consolidate docker service startup into start-services action (#16277) (4912005)
- make build cache restore resilient to eviction (#16275) (d1837ad)
- update actions to resolve Node.js 20 deprecation warnings (#16267) (04163ea)
- run e2e tests using turbopack (#16237) (3a7c9f3)
🏡 Chores
- allow e2e port to be defined by env vars (#16285) (cb97614)
- mark the plugin API as experimental (#16276) (56325ef)
- increase test summary script suite timeout to 120 seconds (#16264) (59f78f0)
- update deprecated VS code rules (#16185) (359dc94)
- templates: fix postgres volume (#15157) (b44135e)
- templates: wrong links to Draft Preview Example (#13053) (a2dee84)
🤝 Contributors
- Elliot DeNolf (@denolfe)
- Paul (@paulpopus)
- Philipp Schneider (@philipp-tailor)
- Riley Langbein (@rilrom)
- Patrik (@PatrikKozak)
- Ran (@eddieran)
- Alessio Gravili (@AlessioGr)
- Jens Becker (@jhb-dev)
- Ruben Hutter (@ruben-hutter)
- German Jablonski (@GermanJablo)
- Sean Zubrickas (@zubricks)
- Muhammad Aqib (@aqib-io)
- Jarrod Flesch (@JarrodMFlesch)
- Bob Bass (@robertjbass)
- Said Akhrarov (@akhrarovsaid)
- Mahmoud Hamdy (@mahmoodhamdi)
- fdurackbrooks (@fdurackbrooks)
- Jake Fletcher (@jacobsfletch)
- David Murdoch (@dsm23)
- Ossaid (@ossaidqadri)
- Adrian Maj (@AdrianMaj)
- Thijs Marinussen (@ThijsAtFreave)
- roboin (@Robot-Inventor)
- Jeremías Benitez (@jerebenitez)
- Sebastian Blank (@blankse)
- Sasha (@r1tsuu)
- Yaniv (@y4aniv)
- Marwin Hormiz (@marwinhormiz)
- Jeffery To (@jefferyto)
- Roman Ryzhikov (@yagee)
- Hugo Duarte (@hugomeduarte)
- Boyan Bratvanov (@bratvanov)
- jakobpevec (@jakobpevec)
- Siim Sams (@siimsams)
- W1ckh3ad (@W1ckh3ad)
- Shane Friedman (@smoores-dev)
- Marius (@MariusMatu)
- guoyangzhen (@guoyangzhen)