🎯

payload

🎯Skill

from samunderwood/agent-skills

VibeIndex|
What it does

Develops and debugs Payload CMS projects by managing collections, fields, hooks, access control, and API interactions with TypeScript-first approach.

πŸ“¦

Part of

samunderwood/agent-skills(17 items)

payload

Installation

pnpmRun with pnpm
pnpm dev
πŸ“– Extracted from docs: samunderwood/agent-skills
1Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

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.

Overview

# 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 | slugField() | [FIELDS.md#slug-field-helper](reference/FIELDS.md#slug-field-helper) |

| Restrict content by user | Access control with query | [ACCESS-CONTROL.md#row-level-security-with-complex-queries](reference/ACCESS-CONTROL.md#row-level-security-with-complex-queries) |

| Local API user ops | user + overrideAccess: false | [QUERIES.md#access-control-in-local-api](reference/QUERIES.md#access-control-in-local-api) |

| Draft/publish workflow | versions: { drafts: true } | [COLLECTIONS.md#versioning--drafts](reference/COLLECTIONS.md#versioning--drafts) |

| Computed fields | virtual: true with afterRead | [FIELDS.md#virtual-fields](reference/FIELDS.md#virtual-fields) |

| Conditional fields | admin.condition | [FIELDS.md#conditional-fields](reference/FIELDS.md#conditional-fields) |

| Custom field validation | validate function | [FIELDS.md#text-field](reference/FIELDS.md#text-field) |

| Filter relationship list | filterOptions on field | [FIELDS.md#relationship](reference/FIELDS.md#relationship) |

| Select specific fields | select parameter | [QUERIES.md#local-api](reference/QUERIES.md#local-api) |

| Auto-set author/dates | beforeChange hook | [HOOKS.md#collection-hooks](reference/HOOKS.md#collection-hooks) |

| Prevent hook loops | req.context check | [HOOKS.md#hook-context](reference/HOOKS.md#hook-context) |

| Cascading deletes | beforeDelete hook | [HOOKS.md#collection-hooks](reference/HOOKS.md#collection-hooks) |

| Geospatial queries | point field with near/within | [FIELDS.md#point-geolocation](reference/FIELDS.md#point-geolocation) |

| Reverse relationships | join field type | [FIELDS.md#join-fields](reference/FIELDS.md#join-fields) |

| Next.js revalidation | Context control in afterChange | [HOOKS.md#nextjs-revalidation-with-context-control](reference/HOOKS.md#nextjs-revalidation-with-context-control) |

| Query by relationship | Nested property syntax | [QUERIES.md#nested-properties](reference/QUERIES.md#nested-properties) |

| Complex queries | AND/OR logic | [QUERIES.md#andor-logic](reference/QUERIES.md#andor-logic) |

| Transactions | Pass req to operations | [ADAPTERS.md#threading-req-through-operations](reference/ADAPTERS.md#threading-req-through-operations) |

| Background jobs | Jobs queue with tasks | [ADVANCED.md#jobs-queue](reference/ADVANCED.md#jobs-queue) |

| Custom API routes | Collection custom endpoints | [ADVANCED.md#custom-endpoints](reference/ADVANCED.md#custom-endpoints) |

| Cloud storage | Storage adapter plugins | [ADAPTERS.md#storage-adapters](reference/ADAPTERS.md#storage-adapters) |

| Multi-language | localization config + localized: true | [ADVANCED.md#localization](reference/ADVANCED.md#localization) |

| Create plugin | (options) => (config) => Config | [PLUGIN-DEVELOPMENT.md#plugin-architecture](reference/PLUGIN-DEVELOPMENT.md#plugin-architecture) |

| Plugin package setup | Package structure with SWC | [PLUGIN-DEVELOPMENT.md#plugin-package-structure](reference/PLUGIN-DEVELOPMENT.md#plugin-package-structure) |

| Add fields to collection | Map collections, spread fields | [PLUGIN-DEVELOPMENT.md#adding-fields-to-collections](reference/PLUGIN-DEVELOPMENT.md#adding-fields-to-collections) |

| Plugin hooks | Preserve existing hooks in array | [PLUGIN-DEVELOPMENT.md#adding-hooks](reference/PLUGIN-DEVELOPMENT.md#adding-hooks) |

| Check field type | Type guard functions | [FIELD-TYPE-GUARDS.md](reference/FIELD-TYPE-GUARDS.md) |

Quick Start

```bash

npx create-payload-app@latest my-app

cd my-app

pnpm dev

```

Minimal Config

```ts

import { buildConfig } from "payload";

import { mongooseAdapter } from "@payloadcms/db-mongodb";

import { lexicalEditor } from "@payloadcms/richtext-lexical";

import path from "path";

import { fileURLToPath } from "url";

const filename = fileURLToPath(import.meta.url);

const dirname = path.dirname(filename);

export default buildConfig({

admin: {

user: "users",

importMap: {

baseDir: path.resolve(dirname),

},

},

collections: [Users, Media],

editor: lexicalEditor(),

secret: process.env.PAYLOAD_SECRET,

typescript: {

outputFile: path.resolve(dirname, "payload-types.ts"),

},

db: mongooseAdapter({

url: process.env.DATABASE_URL,

}),

});

```

Essential Patterns

Basic Collection

```ts

import type { CollectionConfig } from "payload";

export const Posts: CollectionConfig = {

slug: "posts",

admin: {

useAsTitle: "title",

defaultColumns: ["title", "author", "status", "createdAt"],

},

fields: [

{ name: "title", type: "text", required: true },

{ name: "slug", type: "text", unique: true, index: true },

{ name: "content", type: "richText" },

{ name: "author", type: "relationship", relationTo: "users" },

],

timestamps: true,

};

```

For more collection patterns (auth, upload, drafts, live preview), see [COLLECTIONS.md](reference/COLLECTIONS.md).

Common Fields

```ts

// Text field

{ name: 'title', type: 'text', required: true }

// Relationship

{ name: 'author', type: 'relationship', relationTo: 'users', required: true }

// Rich text

{ name: 'content', type: 'richText', required: true }

// Select

{ name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' }

// Upload

{ name: 'image', type: 'upload', relationTo: 'media' }

```

For all field types (array, blocks, point, join, virtual, conditional, etc.), see [FIELDS.md](reference/FIELDS.md).

Hook Example

```ts

export const Posts: CollectionConfig = {

slug: "posts",

hooks: {

beforeChange: [

async ({ data, operation }) => {

if (operation === "create") {

data.slug = slugify(data.title);

}

return data;

},

],

},

fields: [{ name: "title", type: "text" }],

};

```

For all hook patterns, see [HOOKS.md](reference/HOOKS.md). For access control, see [ACCESS-CONTROL.md](reference/ACCESS-CONTROL.md).

Access Control with Type Safety

```ts

import type { Access } from "payload";

import type { User } from "@/payload-types";

// Type-safe access control

export const adminOnly: Access = ({ req }) => {

const user = req.user as User;

return user?.roles?.includes("admin") || false;

};

// Row-level access control

export const ownPostsOnly: Access = ({ req }) => {

const user = req.user as User;

if (!user) return false;

if (user.roles?.includes("admin")) return true;

return {

author: { equals: user.id },

};

};

```

Query Example

```ts

// Local API

const posts = await payload.find({

collection: "posts",

where: {

status: { equals: "published" },

"author.name": { contains: "john" },

},

depth: 2,

limit: 10,

sort: "-createdAt",

});

// Query with populated relationships

const post = await payload.findByID({

collection: "posts",

id: "123",

depth: 2, // Populates relationships (default is 2)

});

// Returns: { author: { id: "user123", name: "John" } }

// Without depth, relationships return IDs only

const post = await payload.findByID({

collection: "posts",

id: "123",

depth: 0,

});

// Returns: { author: "user123" }

```

For all query operators and REST/GraphQL examples, see [QUERIES.md](reference/QUERIES.md).

Getting Payload Instance

```ts

// In API routes (Next.js)

import { getPayload } from 'payload'

import config from '@payload-config'

export async function GET() {

const payload = await getPayload({ config })

const posts = await payload.find({

collection: 'posts',

})

return Response.json(posts)

}

// In Server Components

import { getPayload } from 'payload'

import config from '@payload-config'

export default async function Page() {

const payload = await getPayload({ config })

const { docs } = await payload.find({ collection: 'posts' })

return

{docs.map(post =>

{post.title}

)}

}

```

Logger Usage

```ts

// βœ… Valid: single string

payload.logger.error("Something went wrong");

// βœ… Valid: object with msg and err

payload.logger.error({ msg: "Failed to process", err: error });

// ❌ Invalid: don't pass error as second argument

payload.logger.error("Failed to process", error);

// ❌ Invalid: use err not error, use msg not message

payload.logger.error({ message: "Failed", error: error });

```

Security Pitfalls

1. Local API Access Control (CRITICAL)

By default, Local API operations bypass ALL access control, even when passing a user.

```ts

// ❌ SECURITY BUG: Passes user but ignores their permissions

await payload.find({

collection: "posts",

user: someUser, // Access control is BYPASSED!

});

// βœ… SECURE: Actually enforces the user's permissions

await payload.find({

collection: "posts",

user: someUser,

overrideAccess: false, // REQUIRED for access control

});

```

When to use each:

  • overrideAccess: true (default) - Server-side operations you trust (cron jobs, system tasks)
  • overrideAccess: false - When operating on behalf of a user (API routes, webhooks)

See [QUERIES.md#access-control-in-local-api](reference/QUERIES.md#access-control-in-local-api).

2. Transaction Failures in Hooks

Nested operations in hooks without req break transaction atomicity.

```ts

// ❌ DATA CORRUPTION RISK: Separate transaction

hooks: {

afterChange: [

async ({ doc, req }) => {

await req.payload.create({

collection: "audit-log",

data: { docId: doc.id },

// Missing req - runs in separate transaction!

});

},

];

}

// βœ… ATOMIC: Same transaction

hooks: {

afterChange: [

async ({ doc, req }) => {

await req.payload.create({

collection: "audit-log",

data: { docId: doc.id },

req, // Maintains atomicity

});

},

];

}

```

See [ADAPTERS.md#threading-req-through-operations](reference/ADAPTERS.md#threading-req-through-operations).

3. Infinite Hook Loops

Hooks triggering operations that trigger the same hooks create infinite loops.

```ts

// ❌ INFINITE LOOP

hooks: {

afterChange: [

async ({ doc, req }) => {

await req.payload.update({

collection: "posts",

id: doc.id,

data: { views: doc.views + 1 },

req,

}); // Triggers afterChange again!

},

];

}

// βœ… SAFE: Use context flag

hooks: {

afterChange: [

async ({ doc, req, context }) => {

if (context.skipHooks) return;

await req.payload.update({

collection: "posts",

id: doc.id,

data: { views: doc.views + 1 },

context: { skipHooks: true },

req,

});

},

];

}

```

See [HOOKS.md#context](reference/HOOKS.md#context).

Project Structure

```txt

src/

β”œβ”€β”€ app/

β”‚ β”œβ”€β”€ (frontend)/

β”‚ β”‚ └── page.tsx

β”‚ └── (payload)/

β”‚ └── admin/[[...segments]]/page.tsx

β”œβ”€β”€ collections/

β”‚ β”œβ”€β”€ Posts.ts

β”‚ β”œβ”€β”€ Media.ts

β”‚ └── Users.ts

β”œβ”€β”€ globals/

β”‚ └── Header.ts

β”œβ”€β”€ components/

β”‚ └── CustomField.tsx

β”œβ”€β”€ hooks/

β”‚ └── slugify.ts

└── payload.config.ts

```

Type Generation

```ts

// payload.config.ts

export default buildConfig({

typescript: {

outputFile: path.resolve(dirname, "payload-types.ts"),

},

// ...

});

// Usage

import type { Post, User } from "@/payload-types";

```

Reference Documentation

  • [FIELDS.md](reference/FIELDS.md) - All field types, validation, admin options
  • [FIELD-TYPE-GUARDS.md](reference/FIELD-TYPE-GUARDS.md) - Type guards for runtime field type checking and narrowing
  • [COLLECTIONS.md](reference/COLLECTIONS.md) - Collection configs, auth, upload, drafts, live preview
  • [HOOKS.md](reference/HOOKS.md) - Collection hooks, field hooks, context patterns
  • [ACCESS-CONTROL.md](reference/ACCESS-CONTROL.md) - Collection, field, global access control, RBAC, multi-tenant
  • [ACCESS-CONTROL-ADVANCED.md](reference/ACCESS-CONTROL-ADVANCED.md) - Context-aware, time-based, subscription-based access, factory functions, templates
  • [QUERIES.md](reference/QUERIES.md) - Query operators, Local/REST/GraphQL APIs
  • [ENDPOINTS.md](reference/ENDPOINTS.md) - Custom API endpoints: authentication, helpers, request/response patterns
  • [ADAPTERS.md](reference/ADAPTERS.md) - Database, storage, email adapters, transactions
  • [ADVANCED.md](reference/ADVANCED.md) - Authentication, jobs, endpoints, components, plugins, localization
  • [PLUGIN-DEVELOPMENT.md](reference/PLUGIN-DEVELOPMENT.md) - Plugin architecture, monorepo structure, patterns, best practices

Resources

  • llms-full.txt:
  • Docs:
  • GitHub:
  • Examples:
  • Templates:

More from this repository10

🎯
copywriting🎯Skill

Crafts persuasive marketing copy that converts by transforming product features into compelling customer-focused narratives across web pages.

🎯
frontend-design🎯Skill

Generates distinctive, production-grade frontend interfaces with creative design, transforming web components and applications into visually striking, memorable experiences.

🎯
email-best-practices🎯Skill

Guides developers in building compliant, deliverable emails with best practices for validation, authentication, consent, and sending reliability.

🎯
next-upgrade🎯Skill

I apologize, but I cannot generate a description without seeing the actual code or having more context about the "next-upgrade" skill from the repository. Could you provide more details about what ...

🎯
send-email🎯Skill

Sends transactional and bulk emails via Resend API, supporting single and batch email approaches with robust error handling.

🎯
next-best-practices🎯Skill

Streamlines and recommends best practices for Next.js projects, providing automated code quality and optimization suggestions.

🎯
find-skills🎯Skill

Discovers and recommends installable agent skills by searching the skills ecosystem when users need help with specific tasks or capabilities.

🎯
react-email🎯Skill

Generates React-based email templates with cross-client compatibility, enabling developers to create consistent, reusable transactional and marketing emails.

🎯
better-auth-best-practices🎯Skill

Securely manages authentication with TypeScript-first framework, supporting email/password, OAuth, magic links, and passkeys via flexible plugins.

🎯
resend🎯Skill

Enables seamless email operations using Resend platform, routing to specialized sub-skills for sending, receiving, and managing email workflows.