noklip-io/agent-skills

payload

Use when working with Payload CMS projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.

First seen Jan 21, 2026

Installation

$ npx skills add noklip-io/agent-skills --skill payload

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from noklip-io/agent-skills.

npx skills add noklip-io/agent-skills

Browse all from noklip-io/agent-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Declared
Codex Not declared
GitHub Copilot Declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Declared

Repository health

Stars 1
License LICENSE
Default branch main
Open issues 0
Status Active

Skill metadata

Parsed from SKILL.md frontmatter.

Declared agents claude-code cursor github-copilot opencode

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 15,894 B
  • docs README.md 1,911 B
  • docs SUMMARY.md 244 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 45 installs

SKILL.md

Payload CMS 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.

Installation Note

This is the standalone version of the Payload skill for AI coding agents.

For Claude Code users:

  • Prefer installing the official Payload Marketplace plugin which includes this skill along with MCP tools and additional features
  • Only install this standalone skill if you prefer not to use the full plugin

For other AI agents (Cursor, OpenCode, GitHub Copilot, etc.):

  • Install this skill directly:

``bash npx skills add noklip-io/agent-skills --skill payload ``

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#validation](reference/FIELDS.md#validation)
Filter relationship list filterOptions on field [FIELDS.md#relationship](reference/FIELDS.md#relationship)
Select specific fields select parameter [QUERIES.md#field-selection](reference/QUERIES.md#field-selection)
Auto-set author/dates beforeChange hook [HOOKS.md#collection-hooks](reference/HOOKS.md#collection-hooks)
Prevent hook loops req.context check [HOOKS.md#context](reference/HOOKS.md#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

npx create-payload-app@latest my-app
cd my-app
pnpm dev

Minimal Config

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

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

// 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

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

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

// 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

// 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 <div>{docs.map(post => <h1 key={post.id}>{post.title}</h1>)}</div>
}

Security Pitfalls

1. Local API Access Control (CRITICAL)

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

// ❌ 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.

// ❌ 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.

// ❌ 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

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

// 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