Payload Application Development Payload is a Next.js native CMS with TypeScript-first architecture, providing admin panel, database management, REST/GraphQL APIs, authentication, and file storage. Quick Reference | Task | Solution | Details | | ------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Auto-generate slugs | | FIELDS.md#slug-field-helper | | Restrict content by user | Access control with query | ACCESS-CONTROL.md#row-level-security-wi…

, '')),\n },\n },\n}\n```\n\n### Advanced Hooks\n\n#### Global Error Hooks\n\nAdd global error handling:\n\n```ts\nreturn {\n ...config,\n hooks: {\n afterError: [\n ...(config.hooks?.afterError ?? []),\n async (args) => {\n const { error } = args\n const status = (error as APIError).status ?? 500\n\n if (status >= 500 || captureErrors.includes(status)) {\n captureException(error, {\n tags: {\n collection: args.collection?.slug,\n operation: args.operation,\n },\n user: args.req?.user ? { id: args.req.user.id } : undefined,\n })\n }\n },\n ],\n },\n}\n```\n\n#### Multiple Hook Types on Same Collection\n\nCoordinate multiple lifecycle hooks together for complex workflows (e.g., validation → sync → cache → cleanup):\n\n```ts\ncollection.hooks = {\n ...collection.hooks,\n\n beforeValidate: [\n ...(collection.hooks?.beforeValidate || []),\n async ({ data }) => {\n // Normalize before validation\n return data\n },\n ],\n\n beforeChange: [\n ...(collection.hooks?.beforeChange || []),\n async ({ data, operation }) => {\n // Sync to external service\n if (operation === 'create') {\n data.externalId = await externalService.create(data)\n }\n return data\n },\n ],\n\n afterChange: [\n ...(collection.hooks?.afterChange || []),\n async ({ doc }) => {\n // Invalidate cache\n await cache.invalidate(`doc:${doc.id}`)\n },\n ],\n\n afterDelete: [\n ...(collection.hooks?.afterDelete || []),\n async ({ doc }) => {\n // Cleanup external resources\n await externalService.delete(doc.externalId)\n },\n ],\n}\n```\n\n### Access Control & Filtering\n\n#### Access Control Wrapper Pattern\n\nWrap existing access control with plugin-specific logic:\n\n```ts\n// From plugin-multi-tenant\nexport const multiTenantPlugin =\n (pluginOptions: PluginOptions) =>\n (config: Config): Config => ({\n ...config,\n collections: (config.collections || []).map((collection) => {\n if (!pluginOptions.collections.includes(collection.slug)) {\n return collection\n }\n\n return {\n ...collection,\n access: {\n ...collection.access,\n read: ({ req }) => {\n // Inject tenant filter\n return {\n and: [\n collection.access?.read ? collection.access.read({ req }) : {},\n { tenant: { equals: req.user?.tenant } },\n ],\n }\n },\n },\n }\n }),\n })\n```\n\n#### BaseFilter Composition\n\nCombine plugin filters with existing baseListFilter:\n\n```ts\n// From plugin-multi-tenant\nconst existingBaseFilter = collection.admin?.baseListFilter\nconst tenantFilter = { tenant: { equals: req.user?.tenant } }\n\ncollection.admin = {\n ...collection.admin,\n baseListFilter: existingBaseFilter ? { and: [existingBaseFilter, tenantFilter] } : tenantFilter,\n}\n```\n\n#### Relationship FilterOptions Modification\n\nAdd filters to relationship field options:\n\n```ts\n// From plugin-multi-tenant\ncollection.fields = collection.fields.map((field) => {\n if (field.type === 'relationship') {\n return {\n ...field,\n filterOptions: ({ relationTo }) => {\n return {\n and: [field.filterOptions?.(relationTo) || {}, { tenant: { equals: req.user?.tenant } }],\n }\n },\n }\n }\n return field\n})\n```\n\n### Admin UI Customization\n\n#### Metadata Storage Pattern\n\nUse admin.meta for plugin-specific UI state without database fields:\n\n```ts\n// From plugin-nested-docs\nexport const nestedDocsPlugin =\n (pluginOptions: PluginOptions) =>\n (config: Config): Config => ({\n ...config,\n collections: config.collections?.map((collection) => ({\n ...collection,\n admin: {\n ...collection.admin,\n meta: {\n ...collection.admin?.meta,\n nestedDocs: {\n breadcrumbsFieldSlug: pluginOptions.breadcrumbsFieldSlug || 'breadcrumbs',\n parentFieldSlug: pluginOptions.parentFieldSlug || 'parent',\n },\n },\n },\n })),\n })\n```\n\n#### Conditional Component Rendering\n\nAdd components based on plugin configuration:\n\n```ts\n// From plugin-seo\nconst beforeFields = collection.admin?.components?.beforeFields || []\n\nif (pluginOptions.uploadsCollection === collection.slug) {\n beforeFields.push('/path/to/ImagePreview#ImagePreview')\n}\n\ncollection.admin = {\n ...collection.admin,\n components: {\n ...collection.admin?.components,\n beforeFields,\n },\n}\n```\n\n#### Custom Provider Pattern\n\nInject context providers for shared state:\n\n```ts\n// From plugin-nested-docs\ncollection.admin = {\n ...collection.admin,\n components: {\n ...collection.admin?.components,\n providers: [\n ...(collection.admin?.components?.providers || []),\n '/components/NestedDocsProvider#NestedDocsProvider',\n ],\n },\n}\n```\n\n#### Custom Actions\n\nAdd collection-level action buttons:\n\n```ts\n// From plugin-import-export\ncollection.admin = {\n ...collection.admin,\n components: {\n ...collection.admin?.components,\n actions: [\n ...(collection.admin?.components?.actions || []),\n '/components/ImportButton#ImportButton',\n '/components/ExportButton#ExportButton',\n ],\n },\n}\n```\n\n#### Custom List Item Views\n\nModify how items appear in collection lists:\n\n```ts\n// From plugin-ecommerce\ncollection.admin = {\n ...collection.admin,\n components: {\n ...collection.admin?.components,\n views: {\n ...collection.admin?.components?.views,\n list: {\n ...collection.admin?.components?.views?.list,\n Component: '/views/ProductList#ProductList',\n },\n },\n },\n}\n```\n\n#### Custom Collection Endpoints\n\nAdd collection-scoped endpoints (accessible at `/api/\u003ccollection-slug>/\u003cpath>`):\n\n```ts\n// From plugin-import-export\ncollection.endpoints = [\n ...(collection.endpoints || []),\n {\n path: '/import',\n method: 'post',\n handler: async (req) => {\n // Import logic accessible at /api/posts/import\n return Response.json({ success: true })\n },\n },\n {\n path: '/export',\n method: 'get',\n handler: async (req) => {\n // Export logic accessible at /api/posts/export\n return Response.json({ data: exportedData })\n },\n },\n]\n```\n\n### Field & Collection Modifications\n\n#### Admin Folders Override\n\nControl admin UI organization:\n\n```ts\n// From plugin-redirects\ncollection.admin = {\n ...collection.admin,\n group: pluginOptions.group || 'Settings',\n hidden: pluginOptions.hidden,\n defaultColumns: pluginOptions.defaultColumns || ['from', 'to', 'updatedAt'],\n}\n```\n\n### Background Jobs & Async Operations\n\n#### Jobs Registration\n\nRegister plugin background tasks:\n\n```ts\n// From plugin-stripe\nexport const stripePlugin =\n (pluginOptions: PluginOptions) =>\n (config: Config): Config => ({\n ...config,\n jobs: {\n ...config.jobs,\n tasks: [\n ...(config.jobs?.tasks || []),\n {\n slug: 'syncStripeProducts',\n handler: async ({ req }) => {\n const products = await stripe.products.list()\n // Sync to Payload\n return { output: { synced: products.data.length } }\n },\n },\n ],\n },\n })\n```\n\n## Testing Plugins\n\n### Local Development with dev/ Directory (optional)\n\nInclude a `dev/` directory with a complete Payload project for local development:\n\n1. Create `dev/.env` from `.env.example`:\n\n```bash\nDATABASE_URL=mongodb://127.0.0.1/plugin-dev\nPAYLOAD_SECRET=your-secret-here\n```\n\n2. Configure `dev/payload.config.ts`:\n\n```ts\nimport { buildConfig } from 'payload'\nimport { mongooseAdapter } from '@payloadcms/db-mongodb'\nimport { myPlugin } from '../src/index.js'\n\nexport default buildConfig({\n secret: process.env.PAYLOAD_SECRET!,\n db: mongooseAdapter({ url: process.env.DATABASE_URL! }),\n plugins: [\n myPlugin({\n collections: ['posts'],\n }),\n ],\n collections: [\n {\n slug: 'posts',\n fields: [{ name: 'title', type: 'text' }],\n },\n ],\n})\n```\n\n3. Run development server:\n\n```bash\nnpm run dev # Starts Next.js on http://localhost:3000\n```\n\n### Integration Tests (Vitest) (optional)\n\nCreate `dev/int.spec.ts`:\n\n```ts\nimport type { Payload } from 'payload'\nimport config from '@payload-config'\nimport { createPayloadRequest, getPayload } from 'payload'\nimport { afterAll, beforeAll, describe, expect, test } from 'vitest'\nimport { customEndpointHandler } from '../src/endpoints/handler.js'\n\nlet payload: Payload\n\nbeforeAll(async () => {\n payload = await getPayload({ config })\n})\n\nafterAll(async () => {\n await payload.destroy()\n})\n\ndescribe('Plugin integration tests', () => {\n test('should add field to collection', async () => {\n const post = await payload.create({\n collection: 'posts',\n data: {\n title: 'Test',\n addedByPlugin: 'plugin value',\n },\n })\n expect(post.addedByPlugin).toBe('plugin value')\n })\n\n test('should create plugin collection', async () => {\n expect(payload.collections['plugin-collection']).toBeDefined()\n const { docs } = await payload.find({ collection: 'plugin-collection' })\n expect(docs.length).toBeGreaterThan(0)\n })\n\n test('should query custom endpoint', async () => {\n const request = new Request('http://localhost:3000/api/my-endpoint')\n const payloadRequest = await createPayloadRequest({ config, request })\n const response = await customEndpointHandler(payloadRequest)\n const data = await response.json()\n expect(data).toMatchObject({ message: 'Hello' })\n })\n})\n```\n\nRun: `npm run test:int`\n\n### End-to-End Tests (Playwright)\n\nCreate `dev/e2e.spec.ts`:\n\n```ts\nimport { test, expect } from '@playwright/test'\n\ntest.describe('Plugin e2e tests', () => {\n test('should render custom admin component', async ({ page }) => {\n await page.goto('http://localhost:3000/admin')\n await expect(page.getByText('Added by the plugin')).toBeVisible()\n })\n})\n```\n\nRun: `npm run test:e2e`\n\n## Common Plugin Types\n\n### Field Enhancer\n\nAdds fields to existing collections (SEO, timestamps, audit logs)\n\n### Collection Provider\n\nAdds new collections (redirects, forms, logs)\n\n### Hook Injector\n\nAdds hooks to collections (nested docs, cache invalidation)\n\n### UI Enhancer\n\nAdds custom components (dashboards, field types)\n\n### Integration\n\nConnects external services (Stripe, Sentry, storage adapters)\n\n### Adapter\n\nProvides infrastructure (database, storage, email)\n\n## Resources\n\n- [Plugin Examples](https://github.com/payloadcms/payload/tree/main/packages/) - Official plugins source code, payload-\\* prefix\n- [Plugin Template](https://github.com/payloadcms/payload/tree/main/templates/plugin) - Starter template for new plugins\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":35279,"content_sha256":"5d2d0545754c9d5845b95cfc5d81bd59ad2e5b90aaee5b22d0c1dd506ccfa3ed"},{"filename":"reference/QUERIES.md","content":"# Payload Querying Reference\n\nComplete reference for querying data across Local API, REST, and GraphQL.\n\n## Query Operators\n\n```ts\nimport type { Where } from 'payload'\n\n// Equals\nconst equalsQuery: Where = { color: { equals: 'blue' } }\n\n// Not equals\nconst notEqualsQuery: Where = { status: { not_equals: 'draft' } }\n\n// Greater/less than\nconst greaterThanQuery: Where = { price: { greater_than: 100 } }\nconst lessThanEqualQuery: Where = { age: { less_than_equal: 65 } }\n\n// Contains (case-insensitive)\nconst containsQuery: Where = { title: { contains: 'payload' } }\n\n// Like (all words present)\nconst likeQuery: Where = { description: { like: 'cms headless' } }\n\n// In/not in\nconst inQuery: Where = { category: { in: ['tech', 'news'] } }\n\n// Exists\nconst existsQuery: Where = { image: { exists: true } }\n\n// Near (point fields)\nconst nearQuery: Where = { location: { near: '-122.4194,37.7749,10000' } }\n```\n\n## AND/OR Logic\n\n```ts\nimport type { Where } from 'payload'\n\nconst complexQuery: Where = {\n or: [\n { color: { equals: 'mint' } },\n {\n and: [{ color: { equals: 'white' } }, { featured: { equals: false } }],\n },\n ],\n}\n```\n\n## Nested Properties\n\n```ts\nimport type { Where } from 'payload'\n\nconst nestedQuery: Where = {\n 'author.role': { equals: 'editor' },\n 'meta.featured': { exists: true },\n}\n```\n\n## Local API\n\n```ts\n// Find documents\nconst posts = await payload.find({\n collection: 'posts',\n where: {\n status: { equals: 'published' },\n 'author.name': { contains: 'john' },\n },\n depth: 2,\n limit: 10,\n page: 1,\n sort: '-createdAt',\n locale: 'en',\n select: {\n title: true,\n author: true,\n },\n})\n\n// Find by ID\nconst post = await payload.findByID({\n collection: 'posts',\n id: '123',\n depth: 2,\n})\n\n// Create\nconst post = await payload.create({\n collection: 'posts',\n data: {\n title: 'New Post',\n status: 'draft',\n },\n})\n\n// Update\nawait payload.update({\n collection: 'posts',\n id: '123',\n data: {\n status: 'published',\n },\n})\n\n// Delete\nawait payload.delete({\n collection: 'posts',\n id: '123',\n})\n\n// Count\nconst count = await payload.count({\n collection: 'posts',\n where: {\n status: { equals: 'published' },\n },\n})\n```\n\n### Threading req Parameter\n\nWhen performing operations in hooks or nested operations, pass the `req` parameter to maintain transaction context:\n\n```ts\n// ✅ CORRECT: Pass req for transaction safety\nconst afterChange: CollectionAfterChangeHook = async ({ doc, req }) => {\n await req.payload.create({\n collection: 'audit-log',\n data: { action: 'created', docId: doc.id },\n req, // Maintains transaction atomicity\n })\n}\n\n// ❌ WRONG: Missing req breaks transaction\nconst afterChange: CollectionAfterChangeHook = async ({ doc, req }) => {\n await req.payload.create({\n collection: 'audit-log',\n data: { action: 'created', docId: doc.id },\n // Missing req - runs in separate transaction\n })\n}\n```\n\nThis is critical for MongoDB replica sets and Postgres. See [ADAPTERS.md#threading-req-through-operations](ADAPTERS.md#threading-req-through-operations) for details.\n\n### Access Control in Local API\n\n**Important**: Local API bypasses access control by default (`overrideAccess: true`). When passing a `user` parameter, you must explicitly set `overrideAccess: false` to respect that user's permissions.\n\n```ts\n// ❌ WRONG: User is passed but access control is bypassed\nconst posts = await payload.find({\n collection: 'posts',\n user: currentUser,\n // Missing: overrideAccess: false\n // Result: Operation runs with ADMIN privileges, ignoring user's permissions\n})\n\n// ✅ CORRECT: Respects user's access control permissions\nconst posts = await payload.find({\n collection: 'posts',\n user: currentUser,\n overrideAccess: false, // Required to enforce access control\n // Result: User only sees posts they have permission to read\n})\n\n// Administrative operation (intentionally bypass access control)\nconst allPosts = await payload.find({\n collection: 'posts',\n // No user parameter\n // overrideAccess defaults to true\n // Result: Returns all posts regardless of access control\n})\n```\n\n**When to use `overrideAccess: false`:**\n\n- Performing operations on behalf of a user\n- Testing access control logic\n- API routes that should respect user permissions\n- Any operation where `user` parameter is provided\n\n**When `overrideAccess: true` is appropriate:**\n\n- Administrative operations (migrations, seeds, cron jobs)\n- Internal system operations\n- Operations explicitly intended to bypass access control\n\nSee [ACCESS-CONTROL.md#important-notes](ACCESS-CONTROL.md#important-notes) for more details.\n\n## REST API\n\n```ts\nimport { stringify } from 'qs-esm'\n\nconst query = {\n status: { equals: 'published' },\n}\n\nconst queryString = stringify(\n {\n where: query,\n depth: 2,\n limit: 10,\n },\n { addQueryPrefix: true },\n)\n\nconst response = await fetch(`https://api.example.com/api/posts${queryString}`)\nconst data = await response.json()\n```\n\n### REST Endpoints\n\n```txt\nGET /api/{collection} - Find documents\nGET /api/{collection}/{id} - Find by ID\nPOST /api/{collection} - Create\nPATCH /api/{collection}/{id} - Update\nDELETE /api/{collection}/{id} - Delete\nGET /api/{collection}/count - Count documents\n\nGET /api/globals/{slug} - Get global\nPOST /api/globals/{slug} - Update global\n```\n\n## GraphQL\n\n```graphql\nquery {\n Posts(where: { status: { equals: published } }, limit: 10, sort: \"-createdAt\") {\n docs {\n id\n title\n author {\n name\n }\n }\n totalDocs\n hasNextPage\n }\n}\n\nmutation {\n createPost(data: { title: \"New Post\", status: draft }) {\n id\n title\n }\n}\n\nmutation {\n updatePost(id: \"123\", data: { status: published }) {\n id\n status\n }\n}\n\nmutation {\n deletePost(id: \"123\") {\n id\n }\n}\n```\n\n## Performance Best Practices\n\n- Set `maxDepth` on relationships to prevent over-fetching\n- Use `select` to limit returned fields\n- Index frequently queried fields\n- Use `virtual` fields for computed data\n- Cache expensive operations in hook `context`\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6093,"content_sha256":"468ca0bb663f3a818ad50072906feafb6b4ee665b4565d93494a89bfe0906ed0"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Payload Application Development","type":"text"}]},{"type":"paragraph","content":[{"text":"Payload is a Next.js native CMS with TypeScript-first architecture, providing admin panel, database management, REST/GraphQL APIs, authentication, and file storage.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Reference","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Task","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Solution","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Details","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Auto-generate slugs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"slugField()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"FIELDS.md#slug-field-helper","type":"text","marks":[{"type":"link","attrs":{"href":"reference/FIELDS.md#slug-field-helper","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Restrict content by user","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Access control with query","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ACCESS-CONTROL.md#row-level-security-with-complex-queries","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ACCESS-CONTROL.md#row-level-security-with-complex-queries","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Local API user ops","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"user","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"overrideAccess: false","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"QUERIES.md#access-control-in-local-api","type":"text","marks":[{"type":"link","attrs":{"href":"reference/QUERIES.md#access-control-in-local-api","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Draft/publish workflow","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"versions: { drafts: true }","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"COLLECTIONS.md#versioning--drafts","type":"text","marks":[{"type":"link","attrs":{"href":"reference/COLLECTIONS.md#versioning--drafts","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Computed fields","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"virtual: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" with afterRead","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"FIELDS.md#virtual-fields","type":"text","marks":[{"type":"link","attrs":{"href":"reference/FIELDS.md#virtual-fields","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Conditional fields","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"admin.condition","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"FIELDS.md#conditional-fields","type":"text","marks":[{"type":"link","attrs":{"href":"reference/FIELDS.md#conditional-fields","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Custom field validation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"validate","type":"text","marks":[{"type":"code_inline"}]},{"text":" function","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"FIELDS.md#text-field","type":"text","marks":[{"type":"link","attrs":{"href":"reference/FIELDS.md#text-field","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Filter relationship list","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"filterOptions","type":"text","marks":[{"type":"code_inline"}]},{"text":" on field","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"FIELDS.md#relationship","type":"text","marks":[{"type":"link","attrs":{"href":"reference/FIELDS.md#relationship","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Select specific fields","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"select","type":"text","marks":[{"type":"code_inline"}]},{"text":" parameter","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"QUERIES.md#local-api","type":"text","marks":[{"type":"link","attrs":{"href":"reference/QUERIES.md#local-api","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Auto-set author/dates","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"beforeChange hook","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HOOKS.md#collection-hooks","type":"text","marks":[{"type":"link","attrs":{"href":"reference/HOOKS.md#collection-hooks","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prevent hook loops","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"req.context","type":"text","marks":[{"type":"code_inline"}]},{"text":" check","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HOOKS.md#hook-context","type":"text","marks":[{"type":"link","attrs":{"href":"reference/HOOKS.md#hook-context","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cascading deletes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"beforeDelete hook","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HOOKS.md#collection-hooks","type":"text","marks":[{"type":"link","attrs":{"href":"reference/HOOKS.md#collection-hooks","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Geospatial queries","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"point","type":"text","marks":[{"type":"code_inline"}]},{"text":" field with ","type":"text"},{"text":"near","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"within","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"FIELDS.md#point-geolocation","type":"text","marks":[{"type":"link","attrs":{"href":"reference/FIELDS.md#point-geolocation","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Reverse relationships","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"join","type":"text","marks":[{"type":"code_inline"}]},{"text":" field type","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"FIELDS.md#join-fields","type":"text","marks":[{"type":"link","attrs":{"href":"reference/FIELDS.md#join-fields","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Next.js revalidation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Context control in afterChange","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HOOKS.md#nextjs-revalidation-with-context-control","type":"text","marks":[{"type":"link","attrs":{"href":"reference/HOOKS.md#nextjs-revalidation-with-context-control","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Query by relationship","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Nested property syntax","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"QUERIES.md#nested-properties","type":"text","marks":[{"type":"link","attrs":{"href":"reference/QUERIES.md#nested-properties","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Complex queries","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"AND/OR logic","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"QUERIES.md#andor-logic","type":"text","marks":[{"type":"link","attrs":{"href":"reference/QUERIES.md#andor-logic","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Transactions","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pass ","type":"text"},{"text":"req","type":"text","marks":[{"type":"code_inline"}]},{"text":" to operations","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ADAPTERS.md#threading-req-through-operations","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ADAPTERS.md#threading-req-through-operations","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Background jobs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Jobs queue with tasks","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ADVANCED.md#jobs-queue","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ADVANCED.md#jobs-queue","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Custom API routes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Collection custom endpoints","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ADVANCED.md#custom-endpoints","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ADVANCED.md#custom-endpoints","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cloud storage","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Storage adapter plugins","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ADAPTERS.md#storage-adapters","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ADAPTERS.md#storage-adapters","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Multi-language","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"localization","type":"text","marks":[{"type":"code_inline"}]},{"text":" config + ","type":"text"},{"text":"localized: true","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ADVANCED.md#localization","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ADVANCED.md#localization","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Create plugin","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"(options) => (config) => Config","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PLUGIN-DEVELOPMENT.md#plugin-architecture","type":"text","marks":[{"type":"link","attrs":{"href":"reference/PLUGIN-DEVELOPMENT.md#plugin-architecture","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Plugin package setup","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Package structure with SWC","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PLUGIN-DEVELOPMENT.md#plugin-package-structure","type":"text","marks":[{"type":"link","attrs":{"href":"reference/PLUGIN-DEVELOPMENT.md#plugin-package-structure","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add fields to collection","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Map collections, spread fields","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PLUGIN-DEVELOPMENT.md#adding-fields-to-collections","type":"text","marks":[{"type":"link","attrs":{"href":"reference/PLUGIN-DEVELOPMENT.md#adding-fields-to-collections","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Plugin hooks","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Preserve existing hooks in array","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PLUGIN-DEVELOPMENT.md#adding-hooks","type":"text","marks":[{"type":"link","attrs":{"href":"reference/PLUGIN-DEVELOPMENT.md#adding-hooks","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check field type","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Type guard functions","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"FIELD-TYPE-GUARDS.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/FIELD-TYPE-GUARDS.md","title":null}}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Start","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"npx create-payload-app@latest my-app\ncd my-app\npnpm dev","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Minimal Config","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"import { buildConfig } from 'payload'\nimport { mongooseAdapter } from '@payloadcms/db-mongodb'\nimport { lexicalEditor } from '@payloadcms/richtext-lexical'\nimport path from 'path'\nimport { fileURLToPath } from 'url'\n\nconst filename = fileURLToPath(import.meta.url)\nconst dirname = path.dirname(filename)\n\nexport default buildConfig({\n admin: {\n user: 'users',\n importMap: {\n baseDir: path.resolve(dirname),\n },\n },\n collections: [Users, Media],\n editor: lexicalEditor(),\n secret: process.env.PAYLOAD_SECRET,\n typescript: {\n outputFile: path.resolve(dirname, 'payload-types.ts'),\n },\n db: mongooseAdapter({\n url: process.env.DATABASE_URL,\n }),\n})","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Essential Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Basic Collection","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"import type { CollectionConfig } from 'payload'\n\nexport const Posts: CollectionConfig = {\n slug: 'posts',\n admin: {\n useAsTitle: 'title',\n defaultColumns: ['title', 'author', 'status', 'createdAt'],\n },\n fields: [\n { name: 'title', type: 'text', required: true },\n { name: 'slug', type: 'text', unique: true, index: true },\n { name: 'content', type: 'richText' },\n { name: 'author', type: 'relationship', relationTo: 'users' },\n ],\n timestamps: true,\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"For more collection patterns (auth, upload, drafts, live preview), see ","type":"text"},{"text":"COLLECTIONS.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/COLLECTIONS.md","title":null}}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Common Fields","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"// Text field\n{ name: 'title', type: 'text', required: true }\n\n// Relationship\n{ name: 'author', type: 'relationship', relationTo: 'users', required: true }\n\n// Rich text\n{ name: 'content', type: 'richText', required: true }\n\n// Select\n{ name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' }\n\n// Upload\n{ name: 'image', type: 'upload', relationTo: 'media' }","type":"text"}]},{"type":"paragraph","content":[{"text":"For all field types (array, blocks, point, join, virtual, conditional, etc.), see ","type":"text"},{"text":"FIELDS.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/FIELDS.md","title":null}}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Hook Example","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"export const Posts: CollectionConfig = {\n slug: 'posts',\n hooks: {\n beforeChange: [\n async ({ data, operation }) => {\n if (operation === 'create') {\n data.slug = slugify(data.title)\n }\n return data\n },\n ],\n },\n fields: [{ name: 'title', type: 'text' }],\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"For all hook patterns, see ","type":"text"},{"text":"HOOKS.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/HOOKS.md","title":null}}]},{"text":". For access control, see ","type":"text"},{"text":"ACCESS-CONTROL.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ACCESS-CONTROL.md","title":null}}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Access Control with Type Safety","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"import type { Access } from 'payload'\nimport type { User } from '@/payload-types'\n\n// Type-safe access control\nexport const adminOnly: Access = ({ req }) => {\n const user = req.user as User\n return user?.roles?.includes('admin') || false\n}\n\n// Row-level access control\nexport const ownPostsOnly: Access = ({ req }) => {\n const user = req.user as User\n if (!user) return false\n if (user.roles?.includes('admin')) return true\n\n return {\n author: { equals: user.id },\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Query Example","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"// Local API\nconst posts = await payload.find({\n collection: 'posts',\n where: {\n status: { equals: 'published' },\n 'author.name': { contains: 'john' },\n },\n depth: 2,\n limit: 10,\n sort: '-createdAt',\n})\n\n// Query with populated relationships\nconst post = await payload.findByID({\n collection: 'posts',\n id: '123',\n depth: 2, // Populates relationships (default is 2)\n})\n// Returns: { author: { id: \"user123\", name: \"John\" } }\n\n// Without depth, relationships return IDs only\nconst post = await payload.findByID({\n collection: 'posts',\n id: '123',\n depth: 0,\n})\n// Returns: { author: \"user123\" }","type":"text"}]},{"type":"paragraph","content":[{"text":"For all query operators and REST/GraphQL examples, see ","type":"text"},{"text":"QUERIES.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/QUERIES.md","title":null}}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Getting Payload Instance","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"// In API routes (Next.js)\nimport { getPayload } from 'payload'\nimport config from '@payload-config'\n\nexport async function GET() {\n const payload = await getPayload({ config })\n\n const posts = await payload.find({\n collection: 'posts',\n })\n\n return Response.json(posts)\n}\n\n// In Server Components\nimport { getPayload } from 'payload'\nimport config from '@payload-config'\n\nexport default async function Page() {\n const payload = await getPayload({ config })\n const { docs } = await payload.find({ collection: 'posts' })\n\n return \u003cdiv>{docs.map(post => \u003ch1 key={post.id}>{post.title}\u003c/h1>)}\u003c/div>\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Logger Usage","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"// ✅ Valid: single string\npayload.logger.error('Something went wrong')\n\n// ✅ Valid: object with msg and err\npayload.logger.error({ msg: 'Failed to process', err: error })\n\n// ❌ Invalid: don't pass error as second argument\npayload.logger.error('Failed to process', error)\n\n// ❌ Invalid: use `err` not `error`, use `msg` not `message`\npayload.logger.error({ message: 'Failed', error: error })","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Security Pitfalls","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Local API Access Control (CRITICAL)","type":"text"}]},{"type":"paragraph","content":[{"text":"By default, Local API operations bypass ALL access control","type":"text","marks":[{"type":"strong"}]},{"text":", even when passing a user.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"// ❌ SECURITY BUG: Passes user but ignores their permissions\nawait payload.find({\n collection: 'posts',\n user: someUser, // Access control is BYPASSED!\n})\n\n// ✅ SECURE: Actually enforces the user's permissions\nawait payload.find({\n collection: 'posts',\n user: someUser,\n overrideAccess: false, // REQUIRED for access control\n})","type":"text"}]},{"type":"paragraph","content":[{"text":"When to use each:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"overrideAccess: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" (default) - Server-side operations you trust (cron jobs, system tasks)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"overrideAccess: false","type":"text","marks":[{"type":"code_inline"}]},{"text":" - When operating on behalf of a user (API routes, webhooks)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"QUERIES.md#access-control-in-local-api","type":"text","marks":[{"type":"link","attrs":{"href":"reference/QUERIES.md#access-control-in-local-api","title":null}}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Transaction Failures in Hooks","type":"text"}]},{"type":"paragraph","content":[{"text":"Nested operations in hooks without ","type":"text","marks":[{"type":"strong"}]},{"text":"req","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" break transaction atomicity.","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"// ❌ DATA CORRUPTION RISK: Separate transaction\nhooks: {\n afterChange: [\n async ({ doc, req }) => {\n await req.payload.create({\n collection: 'audit-log',\n data: { docId: doc.id },\n // Missing req - runs in separate transaction!\n })\n },\n ]\n}\n\n// ✅ ATOMIC: Same transaction\nhooks: {\n afterChange: [\n async ({ doc, req }) => {\n await req.payload.create({\n collection: 'audit-log',\n data: { docId: doc.id },\n req, // Maintains atomicity\n })\n },\n ]\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"ADAPTERS.md#threading-req-through-operations","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ADAPTERS.md#threading-req-through-operations","title":null}}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Infinite Hook Loops","type":"text"}]},{"type":"paragraph","content":[{"text":"Hooks triggering operations that trigger the same hooks create infinite loops.","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"// ❌ INFINITE LOOP\nhooks: {\n afterChange: [\n async ({ doc, req }) => {\n await req.payload.update({\n collection: 'posts',\n id: doc.id,\n data: { views: doc.views + 1 },\n req,\n }) // Triggers afterChange again!\n },\n ]\n}\n\n// ✅ SAFE: Use context flag\nhooks: {\n afterChange: [\n async ({ doc, req, context }) => {\n if (context.skipHooks) return\n\n await req.payload.update({\n collection: 'posts',\n id: doc.id,\n data: { views: doc.views + 1 },\n context: { skipHooks: true },\n req,\n })\n },\n ]\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"HOOKS.md#context","type":"text","marks":[{"type":"link","attrs":{"href":"reference/HOOKS.md#context","title":null}}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Project Structure","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"txt"},"content":[{"text":"src/\n├── app/\n│ ├── (frontend)/\n│ │ └── page.tsx\n│ └── (payload)/\n│ └── admin/[[...segments]]/page.tsx\n├── collections/\n│ ├── Posts.ts\n│ ├── Media.ts\n│ └── Users.ts\n├── globals/\n│ └── Header.ts\n├── components/\n│ └── CustomField.tsx\n├── hooks/\n│ └── slugify.ts\n└── payload.config.ts","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Type Generation","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"// payload.config.ts\nexport default buildConfig({\n typescript: {\n outputFile: path.resolve(dirname, 'payload-types.ts'),\n },\n // ...\n})\n\n// Usage\nimport type { Post, User } from '@/payload-types'","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Reference Documentation","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"FIELDS.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/FIELDS.md","title":null}},{"type":"strong"}]},{"text":" - All field types, validation, admin options","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"FIELD-TYPE-GUARDS.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/FIELD-TYPE-GUARDS.md","title":null}},{"type":"strong"}]},{"text":" - Type guards for runtime field type checking and narrowing","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"COLLECTIONS.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/COLLECTIONS.md","title":null}},{"type":"strong"}]},{"text":" - Collection configs, auth, upload, drafts, live preview","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"HOOKS.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/HOOKS.md","title":null}},{"type":"strong"}]},{"text":" - Collection hooks, field hooks, context patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ACCESS-CONTROL.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ACCESS-CONTROL.md","title":null}},{"type":"strong"}]},{"text":" - Collection, field, global access control, RBAC, multi-tenant","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ACCESS-CONTROL-ADVANCED.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ACCESS-CONTROL-ADVANCED.md","title":null}},{"type":"strong"}]},{"text":" - Context-aware, time-based, subscription-based access, factory functions, templates","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"QUERIES.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/QUERIES.md","title":null}},{"type":"strong"}]},{"text":" - Query operators, Local/REST/GraphQL APIs","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ENDPOINTS.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ENDPOINTS.md","title":null}},{"type":"strong"}]},{"text":" - Custom API endpoints: authentication, helpers, request/response patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ADAPTERS.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ADAPTERS.md","title":null}},{"type":"strong"}]},{"text":" - Database, storage, email adapters, transactions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ADVANCED.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/ADVANCED.md","title":null}},{"type":"strong"}]},{"text":" - Authentication, jobs, endpoints, components, plugins, localization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PLUGIN-DEVELOPMENT.md","type":"text","marks":[{"type":"link","attrs":{"href":"reference/PLUGIN-DEVELOPMENT.md","title":null}},{"type":"strong"}]},{"text":" - Plugin architecture, monorepo structure, patterns, best practices","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resources","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"llms-full.txt: ","type":"text"},{"text":"https://payloadcms.com/llms-full.txt","type":"text","marks":[{"type":"link","attrs":{"href":"https://payloadcms.com/llms-full.txt","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Docs: ","type":"text"},{"text":"https://payloadcms.com/docs","type":"text","marks":[{"type":"link","attrs":{"href":"https://payloadcms.com/docs","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"GitHub: ","type":"text"},{"text":"https://github.com/payloadcms/payload","type":"text","marks":[{"type":"link","attrs":{"href":"https://github.com/payloadcms/payload","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Examples: ","type":"text"},{"text":"https://github.com/payloadcms/payload/tree/main/examples","type":"text","marks":[{"type":"link","attrs":{"href":"https://github.com/payloadcms/payload/tree/main/examples","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Templates: ","type":"text"},{"text":"https://github.com/payloadcms/payload/tree/main/templates","type":"text","marks":[{"type":"link","attrs":{"href":"https://github.com/payloadcms/payload/tree/main/templates","title":null}}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"payload","author":"@skillopedia","source":{"stars":114,"repo_name":"skills","origin_url":"https://github.com/payloadcms/skills/blob/HEAD/skills/payload/SKILL.md","repo_owner":"payloadcms","body_sha256":"d04e7f555d96138402d4cccde85a862ee904d535a003397614a85b0be849c15b","cluster_key":"26c24b225deaa141661c36fdb142e42490beddf59cb4351a982f958d40b4741c","clean_bundle":{"format":"clean-skill-bundle-v1","source":"payloadcms/skills/skills/payload/SKILL.md","attachments":[{"id":"3a6622b5-946f-5bf2-9c20-ec1c7d558e89","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3a6622b5-946f-5bf2-9c20-ec1c7d558e89/attachment.md","path":"README.md","size":2309,"sha256":"d54f0ac159c50c10f895c5331a31b9e794f3fa66a3324aee1c446a6bf0e0cd05","contentType":"text/markdown; charset=utf-8"},{"id":"360857d9-4a95-5671-8584-affc2d681b6e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/360857d9-4a95-5671-8584-affc2d681b6e/attachment.md","path":"reference/ACCESS-CONTROL-ADVANCED.md","size":17989,"sha256":"d9f8d8dea33afe17cfb060ee2b61bb28917ad366fad0184b8c22013fb37d6609","contentType":"text/markdown; charset=utf-8"},{"id":"7707638e-5d6a-5d81-aa07-145bf0a8faa2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7707638e-5d6a-5d81-aa07-145bf0a8faa2/attachment.md","path":"reference/ACCESS-CONTROL.md","size":18339,"sha256":"5d3a832e2d5cd410b8360bdba9bf36b4a96ae734da144b27b42678eca7049b03","contentType":"text/markdown; charset=utf-8"},{"id":"0819615d-be47-53a6-b674-6ae50cd1e49f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0819615d-be47-53a6-b674-6ae50cd1e49f/attachment.md","path":"reference/ADAPTERS.md","size":7433,"sha256":"b3114cd31914dbc3a79928da5625c39f49a2010428766c6d1eb971f9e809e5eb","contentType":"text/markdown; charset=utf-8"},{"id":"358c6b0b-373c-54b6-9fc9-dfe2ca02ea43","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/358c6b0b-373c-54b6-9fc9-dfe2ca02ea43/attachment.md","path":"reference/ADVANCED.md","size":9005,"sha256":"70c458931ff016d07eacc866546883a34401989995c14734e44ef0512a7701a6","contentType":"text/markdown; charset=utf-8"},{"id":"d387e548-94eb-50ee-b740-78d57b0723e5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d387e548-94eb-50ee-b740-78d57b0723e5/attachment.md","path":"reference/COLLECTIONS.md","size":5996,"sha256":"4927007c1a85b332d8040fba1e6e1715e18a3d8b3c9dde8088514e15d9c1479d","contentType":"text/markdown; charset=utf-8"},{"id":"ff65f86a-cff0-58a8-9806-bbf3bdcf1600","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ff65f86a-cff0-58a8-9806-bbf3bdcf1600/attachment.md","path":"reference/ENDPOINTS.md","size":15435,"sha256":"126c5bc1d99e794720c21b38b748b85d118a9b6c13fbf7913df7f90ae310f874","contentType":"text/markdown; charset=utf-8"},{"id":"3435eb5a-0c99-5d3a-b336-a7e014cab386","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3435eb5a-0c99-5d3a-b336-a7e014cab386/attachment.md","path":"reference/FIELD-TYPE-GUARDS.md","size":10900,"sha256":"cb8a4eecb78a0cb5eb151345ff8079f33b05a5bb9ce4f182c598416e3bab3951","contentType":"text/markdown; charset=utf-8"},{"id":"5c4336fc-8b32-57b9-b578-6f2bda8709e1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5c4336fc-8b32-57b9-b578-6f2bda8709e1/attachment.md","path":"reference/FIELDS.md","size":17540,"sha256":"990172ff51d8958c9711a23e0857029ef2c92dfeb75112e304aa96af1704825d","contentType":"text/markdown; charset=utf-8"},{"id":"6a42965b-bc20-5548-abfd-992202bf9e60","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6a42965b-bc20-5548-abfd-992202bf9e60/attachment.md","path":"reference/HOOKS.md","size":4341,"sha256":"c77dddf075e571818b87e1bd4056c634aae4fbf19bec36624dd11afd91aca6ef","contentType":"text/markdown; charset=utf-8"},{"id":"7ad2c9ba-39f6-5387-8844-6f365d8e6c36","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7ad2c9ba-39f6-5387-8844-6f365d8e6c36/attachment.md","path":"reference/PLUGIN-DEVELOPMENT.md","size":35279,"sha256":"5d2d0545754c9d5845b95cfc5d81bd59ad2e5b90aaee5b22d0c1dd506ccfa3ed","contentType":"text/markdown; charset=utf-8"},{"id":"b3d38536-2043-51c1-9c4a-37e58132914e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b3d38536-2043-51c1-9c4a-37e58132914e/attachment.md","path":"reference/QUERIES.md","size":6093,"sha256":"468ca0bb663f3a818ad50072906feafb6b4ee665b4565d93494a89bfe0906ed0","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"8dd9c5a5d91868a244b9efd9d2f55afba4aeb1ec9e8aacc29a35f189d583a7ba","attachment_count":12,"text_attachments":12,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/payload/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"security","category_label":"Security"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"security","import_tag":"clean-skills-v1","description":"Use when working with Payload projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior."}},"renderedAt":1782986850726}

Payload Application Development Payload is a Next.js native CMS with TypeScript-first architecture, providing admin panel, database management, REST/GraphQL APIs, authentication, and file storage. Quick Reference | Task | Solution | Details | | ------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Auto-generate slugs | | FIELDS.md#slug-field-helper | | Restrict content by user | Access control with query | ACCESS-CONTROL.md#row-level-security-wi…