vault backup: 2026-05-15 15:51:51
This commit is contained in:
parent
85ba36324d
commit
10be227149
4 changed files with 166 additions and 1 deletions
|
|
@ -35,7 +35,7 @@ This 3-hop pattern works for hundreds of articles without vector search.
|
|||
| [[wiki/reports/_index\|reports/]] | Weekly and monthly summaries — generate: `uv run python scripts/report-generator.py --weekly` | 1 |
|
||||
| [[wiki/infrastructure/_index\|infrastructure/]] | Server inventory: all 10 SSH hosts — optical, optical-dev, optical-prod, baic, librechat, modocmms, box-cli, aimpress, pve | 12 |
|
||||
| [[wiki/testing/_index\|testing/]] | Web app testing: functional, performance, security, UI types; TDD/BDD/Agile methodologies; Selenium/Cypress/Playwright/JMeter/OWASP ZAP tools | 1 |
|
||||
| [[wiki/payloadcms/_index\|payloadcms/]] | Full Payload CMS reference — getting started, config, database (Postgres/MongoDB/SQLite), all 22 field types, access control, hooks, authentication (cookies, JWT, API keys, custom strategies, token data), admin UI, custom components, Lexical rich text, live preview, versions/drafts, Local/REST/GraphQL APIs, queries, plugins, jobs queue, upload, ecommerce, production deploy, TypeScript, migration guides, i18n, localization, hierarchy | 93 |
|
||||
| [[wiki/payloadcms/_index\|payloadcms/]] | Full Payload CMS reference — getting started, config, database (Postgres/MongoDB/SQLite), all 22 field types, access control, hooks, authentication (cookies, JWT, API keys, custom strategies, token data), admin UI, custom components, Lexical rich text, live preview, versions/drafts, Local/REST/GraphQL APIs, queries, plugins, jobs queue, upload, ecommerce, production deploy, TypeScript, migration guides, i18n, localization, hierarchy | 94 |
|
||||
|
||||
| [[wiki/shared-patterns/_index\|shared-patterns/]] | Oliver Agency standard library patterns: httpx, structlog, pydantic-settings, alembic — reuse before writing from scratch | 4 |
|
||||
| [[wiki/mistakes/_index\|mistakes/]] | Anti-patterns extracted from sessions — per-stack running lists (fastapi, react, docker, postgres, general) — injected at session start | 5 |
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@
|
|||
| [[wiki/payloadcms/hooks-collections\|Collection Hooks]] | All 18 collection hook types (write/read/delete/auth lifecycle), `data` vs `originalDoc` gotcha, auth hooks, TypeScript generics | raw/hooks__collections.md | 2026-05-15 |
|
||||
| [[wiki/payloadcms/concepts-overview\|Core Concepts]] | Config, Collections, Globals, Fields, Hooks, Auth, Access Control, Admin Panel; Local/REST/GraphQL APIs; package structure | raw/getting-started__concepts.md | 2026-05-15 |
|
||||
| [[wiki/payloadcms/hooks-context\|Hooks — Context (req.context)]] | Share data across hooks without duplicate fetches; prevent infinite loop in afterChange with flag pattern; TypeScript module augmentation for RequestContext | raw/hooks__context.md | 2026-05-15 |
|
||||
| [[wiki/payloadcms/hooks-fields\|Field Hooks]] | Per-field lifecycle hooks: beforeValidate, beforeChange, afterChange, afterRead, beforeDuplicate — args reference, patterns, TypeScript generics | raw/hooks__fields.md | 2026-05-15 |
|
||||
| [[wiki/payloadcms/installation\|Installation]] | Requirements (Node 24+, Next.js 16.2.6+), create-payload-app quickstart, manual install steps, withPayload ESM config, tsconfig path alias | raw/getting-started__installation.md | 2026-05-15 |
|
||||
| [[wiki/payloadcms/graphql-extending\|GraphQL — Custom Queries and Mutations]] | Add custom GraphQL ops via `graphQL.queries`/`graphQL.mutations` in buildConfig; resolver signature, `depth: 0` gotcha, `buildPaginatedListType`, collection graphQL types | raw/graphql__extending.md | 2026-05-15 |
|
||||
| [[wiki/payloadcms/graphql-schema\|GraphQL — Schema Generation]] | `payload-graphql generate:schema` CLI, PAYLOAD_CONFIG_PATH for non-root configs, `interfaceName` for reusable top-level GraphQL types | raw/graphql__graphql-schema.md | 2026-05-15 |
|
||||
|
|
|
|||
164
wiki/payloadcms/hooks-fields.md
Normal file
164
wiki/payloadcms/hooks-fields.md
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
---
|
||||
title: "Field Hooks"
|
||||
aliases: [field-hooks, payload-field-hooks]
|
||||
tags: [payloadcms, hooks, fields, lifecycle]
|
||||
sources: [raw/hooks__fields.md]
|
||||
created: 2026-05-15
|
||||
updated: 2026-05-15
|
||||
---
|
||||
|
||||
Field Hooks run on individual fields during Document lifecycle events — allowing isolated, per-field logic separate from [[wiki/payloadcms/hooks-collections|Collection Hooks]].
|
||||
|
||||
## Config
|
||||
|
||||
Add to any field's `hooks` property:
|
||||
|
||||
```ts
|
||||
const field: Field = {
|
||||
name: 'username',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
beforeValidate: [(args) => { ... }],
|
||||
beforeChange: [(args) => { ... }],
|
||||
beforeDuplicate:[(args) => { ... }],
|
||||
afterChange: [(args) => { ... }],
|
||||
afterRead: [(args) => { ... }],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Each hook type accepts an **array** of sync or async functions. The return value replaces the field value for that operation.
|
||||
|
||||
> **GraphQL warning:** Changing the *type* of data returned from a field breaks GraphQL. Use [[wiki/payloadcms/hooks-collections|Collection Hooks]] for type changes.
|
||||
|
||||
## Hook Types
|
||||
|
||||
| Hook | Runs | Use for |
|
||||
|------|------|---------|
|
||||
| `beforeValidate` | create + update, after client validation | Normalise/format input before server validation |
|
||||
| `beforeChange` | create + update, before validation | Transform or gate data; data is unvalidated here |
|
||||
| `afterChange` | create + update, after save | Side effects, logging, downstream triggers |
|
||||
| `afterRead` | read operations | Format for output (dates, masks, derived values) |
|
||||
| `beforeDuplicate` | document duplication | Avoid unique constraint violations; runs before `beforeValidate` |
|
||||
|
||||
### Hook Execution Order (create/update)
|
||||
|
||||
1. `beforeValidate` (client → server)
|
||||
2. `validate` (server)
|
||||
3. `beforeChange`
|
||||
4. DB write
|
||||
5. `afterChange`
|
||||
|
||||
On read: `afterRead`.
|
||||
On duplicate: `beforeDuplicate` → `beforeValidate` → `beforeChange`.
|
||||
|
||||
## Arguments Reference
|
||||
|
||||
All field hooks receive the same args object:
|
||||
|
||||
| Arg | Type | Notes |
|
||||
|-----|------|-------|
|
||||
| `value` | any | Current field value |
|
||||
| `data` | Partial\<Doc\> | Incoming data (create/update) or full doc (afterRead) |
|
||||
| `originalDoc` | Doc | Doc before changes (update); resulting doc (afterChange) |
|
||||
| `previousDoc` | Doc | Doc before changes in `afterChange` |
|
||||
| `previousValue` | any | Previous field value — `beforeChange` and `afterChange` only |
|
||||
| `siblingData` | object | Adjacent field values |
|
||||
| `siblingFields` | Field[] | Adjacent field definitions |
|
||||
| `previousSiblingDoc` | object | Sibling data before changes — `beforeChange`/`afterChange` only |
|
||||
| `siblingDocWithLocales` | object | Sibling data with all locales |
|
||||
| `operation` | string | `'create'` \| `'update'` — useful for branching logic |
|
||||
| `field` | Field | Field definition |
|
||||
| `path` | string[] | Runtime path |
|
||||
| `schemaPath` | string[] | Schema-level path |
|
||||
| `collection` | Collection \| null | null if field belongs to a Global |
|
||||
| `global` | Global \| null | null if field belongs to a Collection |
|
||||
| `findMany` | boolean | `afterRead` only — differentiates find-one vs find-many |
|
||||
| `overrideAccess` | boolean | Whether access control is bypassed |
|
||||
| `context` | object | Shared context across hooks — see [[wiki/payloadcms/hooks-context|Hooks Context]] |
|
||||
| `req` | Request | Web request (mocked for Local API) |
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Normalise input (beforeValidate)
|
||||
```ts
|
||||
beforeValidate: [
|
||||
({ value }) => value?.trim().toLowerCase(),
|
||||
],
|
||||
```
|
||||
|
||||
### Branch on operation (beforeChange)
|
||||
```ts
|
||||
beforeChange: [
|
||||
({ value, operation }) => {
|
||||
if (operation === 'create') { /* extra logic */ }
|
||||
return value
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
### Log on change (afterChange)
|
||||
```ts
|
||||
afterChange: [
|
||||
({ value, previousValue, req }) => {
|
||||
if (value !== previousValue) {
|
||||
console.log(`Changed: ${previousValue} → ${value}`)
|
||||
}
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
### Format for display (afterRead)
|
||||
```ts
|
||||
afterRead: [
|
||||
({ value }) => new Date(value).toLocaleDateString(),
|
||||
],
|
||||
```
|
||||
|
||||
### Deduplicate on copy (beforeDuplicate)
|
||||
```ts
|
||||
beforeDuplicate: [
|
||||
({ value }) => (value ?? 0) + 1,
|
||||
],
|
||||
```
|
||||
Default behaviour for text fields: Payload appends `" - Copy"` unless you define your own `beforeDuplicate` hook.
|
||||
|
||||
## TypeScript
|
||||
|
||||
```ts
|
||||
import type { FieldHook } from 'payload'
|
||||
import type { Post } from '@/payload-types'
|
||||
|
||||
// Three generics: <DocumentType, ValueType, SiblingDataType>
|
||||
type PostTitleHook = FieldHook<Post, string, Post>
|
||||
|
||||
const slugifyTitle: PostTitleHook = ({ value, siblingData }) => {
|
||||
if (!siblingData.slug && value) {
|
||||
return value.toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
|
||||
}
|
||||
return value
|
||||
}
|
||||
```
|
||||
|
||||
Use all three generics for full autocomplete on `value`, `data`, `siblingData`, and `originalDoc`.
|
||||
|
||||
## Key Takeaways
|
||||
|
||||
- Field hooks are the preferred place to **isolate per-field logic** — keeps Collection hooks clean.
|
||||
- `operation` arg lets you branch `beforeChange`/`afterChange` between create and update.
|
||||
- `beforeChange` receives **unvalidated** data — validate manually if using for side effects.
|
||||
- Changing the return type breaks GraphQL — stick to same type or use Collection hooks.
|
||||
- `beforeDuplicate` runs **before** `beforeValidate`; default appends `" - Copy"` to unique text fields.
|
||||
- Use three-generic `FieldHook<Doc, Val, Sibling>` for full TypeScript safety.
|
||||
- `context` (from `req.context`) lets hooks share data without extra DB fetches — see [[wiki/payloadcms/hooks-context|Hooks Context]].
|
||||
|
||||
## Related
|
||||
|
||||
- [[wiki/payloadcms/hooks-collections|Collection Hooks]] — document-level lifecycle hooks
|
||||
- [[wiki/payloadcms/hooks-context|Hooks Context (req.context)]] — share state across hooks
|
||||
- [[wiki/payloadcms/hooks|Hooks Overview]] — hook categories and execution model
|
||||
- [[wiki/payloadcms/fields-overview|Fields Overview]] — field config, validation, custom components
|
||||
|
||||
## Sources
|
||||
|
||||
- `raw/hooks__fields.md` — https://payloadcms.com/docs/hooks/fields
|
||||
Loading…
Add table
Reference in a new issue