From caea0bf3782a61516e255de1202187d447be05e1 Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Fri, 15 May 2026 15:32:01 +0100 Subject: [PATCH] vault backup: 2026-05-15 15:32:01 --- raw/{ => _processed}/fields__overview.md | 0 wiki/_master-index.md | 2 +- wiki/payloadcms/_index.md | 1 + wiki/payloadcms/fields-overview.md | 353 +++++++++++++++++++++++ 4 files changed, 355 insertions(+), 1 deletion(-) rename raw/{ => _processed}/fields__overview.md (100%) create mode 100644 wiki/payloadcms/fields-overview.md diff --git a/raw/fields__overview.md b/raw/_processed/fields__overview.md similarity index 100% rename from raw/fields__overview.md rename to raw/_processed/fields__overview.md diff --git a/wiki/_master-index.md b/wiki/_master-index.md index f6935e4..c57f06a 100644 --- a/wiki/_master-index.md +++ b/wiki/_master-index.md @@ -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 | 73 | +| [[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 | 74 | | [[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 | diff --git a/wiki/payloadcms/_index.md b/wiki/payloadcms/_index.md index 0ed73e0..8fd7b45 100644 --- a/wiki/payloadcms/_index.md +++ b/wiki/payloadcms/_index.md @@ -74,3 +74,4 @@ | [[wiki/payloadcms/fields-join\|Join Field]] | Virtual bi-directional relationship field — zero-overhead reverse lookup, custom junction tables, polymorphic, query options across all APIs | raw/fields__join.md | 2026-05-15 | | [[wiki/payloadcms/fields-json\|JSON Field]] | Stores native JSON in DB (not string) — jsonSchema validation + Monaco typeahead, local/remote schemas, vs Code Field | raw/fields__json.md | 2026-05-15 | | [[wiki/payloadcms/fields-number\|Number Field]] | Numeric field — min/max validation, hasMany array mode, step admin control, unique/index, virtual, custom components | raw/fields__number.md | 2026-05-15 | +| [[wiki/payloadcms/fields-overview\|Fields Overview]] | Cross-cutting field config: virtual fields (boolean + path), validation (context, perf, reuse), defaultValue, admin options (condition, disabled, components), custom components (Field/Cell/Filter/Label/Error/Diff), reserved names | raw/fields__overview.md | 2026-05-15 | diff --git a/wiki/payloadcms/fields-overview.md b/wiki/payloadcms/fields-overview.md new file mode 100644 index 0000000..09eb5d2 --- /dev/null +++ b/wiki/payloadcms/fields-overview.md @@ -0,0 +1,353 @@ +--- +title: "Fields Overview — Config, Validation, Virtual, Admin Options" +aliases: [payload-fields-overview, payload-field-config] +tags: [payloadcms, fields, validation, virtual-fields, admin-panel] +sources: [raw/fields__overview.md] +created: 2026-05-15 +updated: 2026-05-15 +--- + +# Fields Overview — Config, Validation, Virtual, Admin Options + +Fields are the building blocks of Payload. They define the DB schema **and** auto-generate Admin Panel UI. This article covers shared config capabilities that apply across all field types: virtual fields, validation, default values, admin options, and custom components. + +See [[wiki/payloadcms/fields-basic|Fields: Basic]] for scalar field types and [[wiki/payloadcms/fields-complex|Fields: Complex]] for structural/relational types. + +--- + +## Field Categories + +| Category | Purpose | `name` required? | +|----------|---------|-----------------| +| **Data** | Stored in DB | Yes | +| **Presentational** | Layout only (Row, Collapsible, unnamed Tabs/Group, UI) | No | +| **Virtual** | Computed/derived, not stored | Yes | + +### Data Fields (full list) + +Array, Blocks, Checkbox, Code, Date, Email, Group (named), JSON, Number, Point, Radio, Relationship, Rich Text, Select, Tabs (named), Text, Textarea, Upload + +### Presentational Fields + +Collapsible, Row, Tabs (unnamed), Group (unnamed), UI + +### Virtual Fields + +- **Join** — purpose-built virtual type for bi-directional relationships +- **Any field** with `virtual: true` or `virtual: 'path.to.value'` + +--- + +## Virtual Field Configuration + +Any data field type can be made virtual by adding the `virtual` property. + +### Boolean virtual — computed via hook + +```ts +{ + name: 'fullName', + type: 'text', + virtual: true, + hooks: { + afterRead: [({ siblingData }) => + `${siblingData.firstName} ${siblingData.lastName}` + ] + } +} +``` + +### String path virtual — resolves relationship data + +```ts +{ + name: 'authorName', + type: 'text', + virtual: 'author.name' // author relationship must exist on the same collection +} +``` + +**Path syntax rules:** +- Dot notation traverses relationships: `author.profile.bio` +- `hasMany` relationships return arrays: `categories.title` → `['Tech', 'News']` +- Source relationship field **must** exist in the same schema +- Resolved at query time, not stored + +**Use cases:** display relationship names without ID, computed word counts, formatted summaries. + +```json +// API response includes virtual fields alongside real ones +{ + "id": "123", + "title": "My Post", + "author": "64f123...", + "authorName": "John Doe", + "categoryTitles": ["Tech", "News"], + "wordCount": 450 +} +``` + +--- + +## Field Names + +- Must be **unique** among siblings +- Follow JavaScript identifier conventions: start with letter or `_`, only letters/numbers/`_` +- **Avoid** hyphens (breaks GraphQL enum generation) and leading digits +- **Reserved names** (cannot be used): `__v`, `salt`, `hash`, `file`, `status` (with Postgres + drafts) + +--- + +## Default Values + +```ts +// Static +{ name: 'status', type: 'text', defaultValue: 'draft' } + +// Dynamic function — runs on create/update +{ + name: 'attribution', + type: 'text', + defaultValue: ({ user, locale, req }) => + `${translation[locale]} ${user.name}` +} +``` + +Dynamic `defaultValue` receives: `user`, `locale`, `req` (use `req.payload` for Local API calls). Can be `async`. + +--- + +## Validation + +Fields auto-validate based on type and options (`required`, `min`, `max`). Override with `validate`: + +```ts +{ + name: 'myField', + type: 'text', + validate: (value, { user, data, siblingData, operation, id, req, event }) => + Boolean(value) || 'This field is required' +} +``` + +Returns `true` (valid) or an error string. + +### Validation Context (`ctx`) + +| Property | Description | +|----------|-------------| +| `data` | Full document being edited | +| `siblingData` | Fields in same parent | +| `operation` | `'create'` or `'update'` | +| `path` | Array path: `['group', 'myArray', '1', 'field']` | +| `id` | Document ID (undefined during create) | +| `req` | HTTP request with `payload`, `user`, etc. | +| `event` | `'onChange'` or `'submit'` | + +### Localized error messages + +```ts +validate: (value, { req: { t } }) => + Boolean(value) || t('validation:required') +``` + +### Reuse built-in validators + +```ts +import { text } from 'payload/shared' + +validate: (val, args) => { + if (val === 'bad') return 'Cannot be "bad"' + return text(val, args) // delegate to built-in +} +``` + +Available: `array, blocks, checkbox, code, date, email, json, number, point, radio, relationship, richText, select, tabs, text, textarea, upload` + +### Validation performance + +Admin Panel validates on **every change**. For expensive ops (DB queries), gate on `event`: + +```ts +validate: async (val, { event }) => { + if (event === 'onChange') return true + const response = await fetch(`/api/check?val=${val}`) + return response.ok || 'Invalid value' +} +``` + +--- + +## Field-level Hooks + +```ts +{ + name: 'myField', + type: 'text', + hooks: { + beforeValidate: [...], + beforeChange: [...], + afterChange: [...], + afterRead: [...] + } +} +``` + +See [[wiki/payloadcms/hooks|Hooks]] for full details. + +--- + +## Field-level Access Control + +```ts +{ + name: 'sensitiveData', + type: 'text', + access: { + read: ({ req: { user } }) => Boolean(user?.isAdmin), + create: () => false, + update: ({ req: { user } }) => Boolean(user?.isAdmin), + } +} +``` + +See [[wiki/payloadcms/access-control|Access Control]] for full details. + +--- + +## Custom ID Fields + +Override auto-generated ID: + +```ts +fields: [ + { + name: 'id', + type: 'number', // or 'text' + required: true, + } +] +``` + +- Only `number` or `text` types allowed +- Text IDs must not contain `/` or `.` characters + +--- + +## Admin Options + +Configured via `admin: {}` on any field: + +| Option | Description | +|--------|-------------| +| `condition` | `(data, siblingData, ctx) => boolean` — show/hide field | +| `components` | Swap individual UI parts (see Custom Components below) | +| `description` | Help text (string, function, or React component) | +| `position` | `'sidebar'` or `'main'` (default) | +| `width` | CSS width — useful in `row` fields | +| `style` | Inline CSS on root element | +| `className` | CSS class on root element | +| `readOnly` | UI-only; no effect on API | +| `disabled` | Hide from all admin surfaces (`true`) or specific ones (object) | +| `hidden` | Converts to `` — still submits | + +### `admin.disabled` granular control + +```ts +admin: { disabled: { column: true, filter: true } } +// Keys: field, column, filter, groupBy, bulkEdit +``` + +UI fields default to `disabled: { bulkEdit: true }`. + +### Conditional Logic + +```ts +{ + name: 'greeting', + type: 'text', + admin: { + condition: (data, siblingData, { blockData, path, user }) => { + return Boolean(data.enableGreeting) + } + } +} +``` + +--- + +## Custom Components + +Swap any part of a field's UI via `admin.components`: + +| Component | Renders in | Notes | +|-----------|-----------|-------| +| `Field` | Edit View — the input | Use `useField()` hook to manage value | +| `Cell` | List View — table cell | Use `DefaultCellComponentProps` / `DefaultServerCellComponentProps` | +| `Filter` | List View — filter dropdown | Receives `disabled, onChange, operator, value` | +| `Label` | Anywhere labels appear | | +| `Error` | Below input on validation fail | | +| `Description` | Below label | Use component for dynamic/reactive help text | +| `Diff` | Version Diff View | Only visible when versioning is enabled | +| `beforeInput` | Before `` element | Array of components | +| `afterInput` | After `` element | Array of components | + +### Field component — managing value + +```tsx +'use client' +import { useField } from '@payloadcms/ui' + +export const CustomTextField = () => { + const { value, setValue } = useField() + return setValue(e.target.value)} value={value} /> +} +``` + +### TypeScript for custom components + +```ts +import type { + TextFieldClientComponent, + TextFieldServerComponent, + TextFieldLabelClientComponent, + TextFieldDescriptionClientComponent, + TextFieldErrorClientComponent, + TextFieldDiffClientComponent, +} from 'payload' +``` + +Convention: `{FieldType}{ComponentRole}{ServerOrClient}Component` (e.g. `TextFieldLabelClientComponent`) + +### Cell component example + +```tsx +'use client' +import type { DefaultCellComponentProps } from 'payload' + +export const PriceCellComponent: React.FC = ({ cellData, rowData }) => { + const currency = rowData.currency || 'USD' + return {new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(cellData)} +} +``` + +--- + +## Key Takeaways + +- **Three categories**: Data (stored), Presentational (layout), Virtual (computed/derived) +- **Any field can be virtual** with `virtual: true` (hook-computed) or `virtual: 'path.string'` (relationship path) +- **Virtual path fields** auto-resolve relationship data without a DB column; `hasMany` → returns arrays +- **Reserved field names** (`__v`, `salt`, `hash`, `file`, `status` with Postgres+drafts) cause silent config sanitization +- **Validation** runs on every keystroke in Admin — use `event === 'onChange'` guard for expensive async checks +- **`validate` returns** `true` (valid) or an error message string +- **`defaultValue`** can be async; receives `{ user, locale, req }` — use `req.payload` for Local API +- **`admin.disabled`** accepts boolean or object with keys `{ field, column, filter, groupBy, bulkEdit }` +- **Custom components** registered via path strings in `admin.components`; use `useField()` to wire value +- **TypeScript types** follow `{FieldType}{Role}{Env}Component` naming convention + +--- + +## Sources + +- `raw/fields__overview.md` — https://payloadcms.com/docs/fields/overview