Skip to content

Commit 89be107

Browse files
authored
chore(claude): further improve payload skill (#14705)
More improvements: - Adds type guards - Adds custom endpoints - Optimizes with [writing-skills](https://github.com/obra/superpowers/tree/main/skills/writing-skills) by obra - Adjust description
1 parent b9b11f0 commit 89be107

File tree

6 files changed

+1416
-4
lines changed

6 files changed

+1416
-4
lines changed

tools/claude-plugin/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Payload Skill for Claude Code
22

3-
Claude Code skill providing comprehensive guidance for Payload 3.x development with TypeScript patterns, field configurations, hooks, access control, and API examples.
3+
Claude Code skill providing comprehensive guidance for Payload development with TypeScript patterns, field configurations, hooks, access control, and API examples.
44

55
## Installation
66

tools/claude-plugin/skills/payload/SKILL.md

Lines changed: 182 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
---
22
name: payload
3-
description: Use when working with Payload CMS projects, payload.config.ts, collections, fields, hooks, access control, or Payload API. Provides TypeScript patterns and examples for Payload 3.x development.
3+
description: 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.
44
---
55

66
# Payload CMS Application Development
77

8-
Payload 3.x is a Next.js native CMS with TypeScript-first architecture, providing admin panel, database management, REST/GraphQL APIs, authentication, and file storage.
8+
Payload is a Next.js native CMS with TypeScript-first architecture, providing admin panel, database management, REST/GraphQL APIs, authentication, and file storage.
99

1010
## Quick Reference
1111

@@ -17,20 +17,27 @@ Payload 3.x is a Next.js native CMS with TypeScript-first architecture, providin
1717
| Draft/publish workflow | `versions: { drafts: true }` | [COLLECTIONS.md#versioning--drafts](reference/COLLECTIONS.md#versioning--drafts) |
1818
| Computed fields | `virtual: true` with afterRead | [FIELDS.md#virtual-fields](reference/FIELDS.md#virtual-fields) |
1919
| Conditional fields | `admin.condition` | [FIELDS.md#conditional-fields](reference/FIELDS.md#conditional-fields) |
20+
| Custom field validation | `validate` function | [FIELDS.md#validation](reference/FIELDS.md#validation) |
21+
| Filter relationship list | `filterOptions` on field | [FIELDS.md#relationship](reference/FIELDS.md#relationship) |
22+
| Select specific fields | `select` parameter | [QUERIES.md#field-selection](reference/QUERIES.md#field-selection) |
23+
| Auto-set author/dates | beforeChange hook | [HOOKS.md#collection-hooks](reference/HOOKS.md#collection-hooks) |
24+
| Prevent hook loops | `req.context` check | [HOOKS.md#context](reference/HOOKS.md#context) |
25+
| Cascading deletes | beforeDelete hook | [HOOKS.md#collection-hooks](reference/HOOKS.md#collection-hooks) |
2026
| Geospatial queries | `point` field with `near`/`within` | [FIELDS.md#point-geolocation](reference/FIELDS.md#point-geolocation) |
2127
| Reverse relationships | `join` field type | [FIELDS.md#join-fields](reference/FIELDS.md#join-fields) |
2228
| Next.js revalidation | Context control in afterChange | [HOOKS.md#nextjs-revalidation-with-context-control](reference/HOOKS.md#nextjs-revalidation-with-context-control) |
2329
| Query by relationship | Nested property syntax | [QUERIES.md#nested-properties](reference/QUERIES.md#nested-properties) |
2430
| Complex queries | AND/OR logic | [QUERIES.md#andor-logic](reference/QUERIES.md#andor-logic) |
2531
| Transactions | Pass `req` to operations | [ADAPTERS.md#threading-req-through-operations](reference/ADAPTERS.md#threading-req-through-operations) |
2632
| Background jobs | Jobs queue with tasks | [ADVANCED.md#jobs-queue](reference/ADVANCED.md#jobs-queue) |
27-
| Custom API routes | Collection/root endpoints | [ADVANCED.md#custom-endpoints](reference/ADVANCED.md#custom-endpoints) |
33+
| Custom API routes | Collection custom endpoints | [ADVANCED.md#custom-endpoints](reference/ADVANCED.md#custom-endpoints) |
2834
| Cloud storage | Storage adapter plugins | [ADAPTERS.md#storage-adapters](reference/ADAPTERS.md#storage-adapters) |
2935
| Multi-language | `localization` config + `localized: true` | [ADVANCED.md#localization](reference/ADVANCED.md#localization) |
3036
| Create plugin | `(options) => (config) => Config` | [PLUGIN-DEVELOPMENT.md#plugin-architecture](reference/PLUGIN-DEVELOPMENT.md#plugin-architecture) |
3137
| Plugin package setup | Package structure with SWC | [PLUGIN-DEVELOPMENT.md#plugin-package-structure](reference/PLUGIN-DEVELOPMENT.md#plugin-package-structure) |
3238
| Add fields to collection | Map collections, spread fields | [PLUGIN-DEVELOPMENT.md#adding-fields-to-collections](reference/PLUGIN-DEVELOPMENT.md#adding-fields-to-collections) |
3339
| Plugin hooks | Preserve existing hooks in array | [PLUGIN-DEVELOPMENT.md#adding-hooks](reference/PLUGIN-DEVELOPMENT.md#adding-hooks) |
40+
| Check field type | Type guard functions | [FIELD-TYPE-GUARDS.md](reference/FIELD-TYPE-GUARDS.md) |
3441

3542
## Quick Start
3643

@@ -138,6 +145,30 @@ export const Posts: CollectionConfig = {
138145

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

148+
### Access Control with Type Safety
149+
150+
```ts
151+
import type { Access } from 'payload'
152+
import type { User } from '@/payload-types'
153+
154+
// Type-safe access control
155+
export const adminOnly: Access = ({ req }) => {
156+
const user = req.user as User
157+
return user?.roles?.includes('admin') || false
158+
}
159+
160+
// Row-level access control
161+
export const ownPostsOnly: Access = ({ req }) => {
162+
const user = req.user as User
163+
if (!user) return false
164+
if (user.roles?.includes('admin')) return true
165+
166+
return {
167+
author: { equals: user.id },
168+
}
169+
}
170+
```
171+
141172
### Query Example
142173

143174
```ts
@@ -152,10 +183,156 @@ const posts = await payload.find({
152183
limit: 10,
153184
sort: '-createdAt',
154185
})
186+
187+
// Query with populated relationships
188+
const post = await payload.findByID({
189+
collection: 'posts',
190+
id: '123',
191+
depth: 2, // Populates relationships (default is 2)
192+
})
193+
// Returns: { author: { id: "user123", name: "John" } }
194+
195+
// Without depth, relationships return IDs only
196+
const post = await payload.findByID({
197+
collection: 'posts',
198+
id: '123',
199+
depth: 0,
200+
})
201+
// Returns: { author: "user123" }
155202
```
156203

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

206+
### Getting Payload Instance
207+
208+
```ts
209+
// In API routes (Next.js)
210+
import { getPayload } from 'payload'
211+
import config from '@payload-config'
212+
213+
export async function GET() {
214+
const payload = await getPayload({ config })
215+
216+
const posts = await payload.find({
217+
collection: 'posts',
218+
})
219+
220+
return Response.json(posts)
221+
}
222+
223+
// In Server Components
224+
import { getPayload } from 'payload'
225+
import config from '@payload-config'
226+
227+
export default async function Page() {
228+
const payload = await getPayload({ config })
229+
const { docs } = await payload.find({ collection: 'posts' })
230+
231+
return <div>{docs.map(post => <h1 key={post.id}>{post.title}</h1>)}</div>
232+
}
233+
```
234+
235+
## Security Pitfalls
236+
237+
### 1. Local API Access Control (CRITICAL)
238+
239+
**By default, Local API operations bypass ALL access control**, even when passing a user.
240+
241+
```ts
242+
// ❌ SECURITY BUG: Passes user but ignores their permissions
243+
await payload.find({
244+
collection: 'posts',
245+
user: someUser, // Access control is BYPASSED!
246+
})
247+
248+
// ✅ SECURE: Actually enforces the user's permissions
249+
await payload.find({
250+
collection: 'posts',
251+
user: someUser,
252+
overrideAccess: false, // REQUIRED for access control
253+
})
254+
```
255+
256+
**When to use each:**
257+
258+
- `overrideAccess: true` (default) - Server-side operations you trust (cron jobs, system tasks)
259+
- `overrideAccess: false` - When operating on behalf of a user (API routes, webhooks)
260+
261+
See [QUERIES.md#access-control-in-local-api](reference/QUERIES.md#access-control-in-local-api).
262+
263+
### 2. Transaction Failures in Hooks
264+
265+
**Nested operations in hooks without `req` break transaction atomicity.**
266+
267+
```ts
268+
// ❌ DATA CORRUPTION RISK: Separate transaction
269+
hooks: {
270+
afterChange: [
271+
async ({ doc, req }) => {
272+
await req.payload.create({
273+
collection: 'audit-log',
274+
data: { docId: doc.id },
275+
// Missing req - runs in separate transaction!
276+
})
277+
},
278+
]
279+
}
280+
281+
// ✅ ATOMIC: Same transaction
282+
hooks: {
283+
afterChange: [
284+
async ({ doc, req }) => {
285+
await req.payload.create({
286+
collection: 'audit-log',
287+
data: { docId: doc.id },
288+
req, // Maintains atomicity
289+
})
290+
},
291+
]
292+
}
293+
```
294+
295+
See [ADAPTERS.md#threading-req-through-operations](reference/ADAPTERS.md#threading-req-through-operations).
296+
297+
### 3. Infinite Hook Loops
298+
299+
**Hooks triggering operations that trigger the same hooks create infinite loops.**
300+
301+
```ts
302+
// ❌ INFINITE LOOP
303+
hooks: {
304+
afterChange: [
305+
async ({ doc, req }) => {
306+
await req.payload.update({
307+
collection: 'posts',
308+
id: doc.id,
309+
data: { views: doc.views + 1 },
310+
req,
311+
}) // Triggers afterChange again!
312+
},
313+
]
314+
}
315+
316+
// ✅ SAFE: Use context flag
317+
hooks: {
318+
afterChange: [
319+
async ({ doc, req, context }) => {
320+
if (context.skipHooks) return
321+
322+
await req.payload.update({
323+
collection: 'posts',
324+
id: doc.id,
325+
data: { views: doc.views + 1 },
326+
context: { skipHooks: true },
327+
req,
328+
})
329+
},
330+
]
331+
}
332+
```
333+
334+
See [HOOKS.md#context](reference/HOOKS.md#context).
335+
159336
## Project Structure
160337
161338
```txt
@@ -196,11 +373,13 @@ import type { Post, User } from '@/payload-types'
196373
## Reference Documentation
197374
198375
- **[FIELDS.md](reference/FIELDS.md)** - All field types, validation, admin options
376+
- **[FIELD-TYPE-GUARDS.md](reference/FIELD-TYPE-GUARDS.md)** - Type guards for runtime field type checking and narrowing
199377
- **[COLLECTIONS.md](reference/COLLECTIONS.md)** - Collection configs, auth, upload, drafts, live preview
200378
- **[HOOKS.md](reference/HOOKS.md)** - Collection hooks, field hooks, context patterns
201379
- **[ACCESS-CONTROL.md](reference/ACCESS-CONTROL.md)** - Collection, field, global access control, RBAC, multi-tenant
202380
- **[ACCESS-CONTROL-ADVANCED.md](reference/ACCESS-CONTROL-ADVANCED.md)** - Context-aware, time-based, subscription-based access, factory functions, templates
203381
- **[QUERIES.md](reference/QUERIES.md)** - Query operators, Local/REST/GraphQL APIs
382+
- **[ENDPOINTS.md](reference/ENDPOINTS.md)** - Custom API endpoints: authentication, helpers, request/response patterns
204383
- **[ADAPTERS.md](reference/ADAPTERS.md)** - Database, storage, email adapters, transactions
205384
- **[ADVANCED.md](reference/ADVANCED.md)** - Authentication, jobs, endpoints, components, plugins, localization
206385
- **[PLUGIN-DEVELOPMENT.md](reference/PLUGIN-DEVELOPMENT.md)** - Plugin architecture, monorepo structure, patterns, best practices

tools/claude-plugin/skills/payload/reference/ADVANCED.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ Multi-step jobs that run in sequence:
156156

157157
## Custom Endpoints
158158

159+
Add custom REST API routes to collections, globals, or root config. See [ENDPOINTS.md](ENDPOINTS.md) for detailed patterns, authentication, helpers, and real-world examples.
160+
159161
### Root Endpoints
160162

161163
```ts

0 commit comments

Comments
 (0)