1253 lines
30 KiB
Markdown
1253 lines
30 KiB
Markdown
|
|
# Payload CMS Field Reference for AI-Assisted Migration
|
||
|
|
|
||
|
|
This document helps AI assistants analyze source CMS data and generate appropriate Payload collection configurations. When given sample data from a source CMS, use this reference to determine the correct Payload field types.
|
||
|
|
|
||
|
|
## How to Use This Document
|
||
|
|
|
||
|
|
1. Analyze the source data structure (JSON, API response, or database schema)
|
||
|
|
2. For each field, determine the data type and pattern
|
||
|
|
3. Match to the appropriate Payload field type below
|
||
|
|
4. Generate a Payload collection config
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Field Type Schemas
|
||
|
|
|
||
|
|
Every field shares these **base properties**:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
type BaseField = {
|
||
|
|
name: string // Required. Field identifier (camelCase)
|
||
|
|
label?: string // Admin UI label. Defaults to name
|
||
|
|
required?: boolean // Validation. Default: false
|
||
|
|
unique?: boolean // Database unique constraint
|
||
|
|
index?: boolean // Database index for faster queries
|
||
|
|
localized?: boolean // Enable per-locale values
|
||
|
|
hidden?: boolean // Hide from admin UI
|
||
|
|
saveToJWT?: boolean // Include in auth JWT
|
||
|
|
defaultValue?: unknown // Default when creating new docs
|
||
|
|
validate?: Function // Custom validation function
|
||
|
|
access?: { // Field-level access control
|
||
|
|
create?: Function
|
||
|
|
read?: Function
|
||
|
|
update?: Function
|
||
|
|
}
|
||
|
|
hooks?: { // Field lifecycle hooks
|
||
|
|
beforeValidate?: Function[]
|
||
|
|
beforeChange?: Function[]
|
||
|
|
afterChange?: Function[]
|
||
|
|
afterRead?: Function[]
|
||
|
|
}
|
||
|
|
admin?: {
|
||
|
|
condition?: Function // Conditionally show/hide field
|
||
|
|
description?: string // Help text below field
|
||
|
|
position?: 'sidebar' // Move to sidebar in admin
|
||
|
|
width?: string // CSS width (e.g., '50%')
|
||
|
|
style?: CSSProperties // Inline styles
|
||
|
|
className?: string // CSS class
|
||
|
|
readOnly?: boolean // Disable editing
|
||
|
|
disabled?: boolean // Disable field entirely
|
||
|
|
hidden?: boolean // Hide in admin
|
||
|
|
components?: { // Custom React components
|
||
|
|
Field?: Component
|
||
|
|
Cell?: Component
|
||
|
|
Filter?: Component
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Field Types
|
||
|
|
|
||
|
|
### text
|
||
|
|
|
||
|
|
Single-line text input.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type TextField = BaseField & {
|
||
|
|
type: 'text'
|
||
|
|
minLength?: number // Minimum character count
|
||
|
|
maxLength?: number // Maximum character count
|
||
|
|
hasMany?: boolean // Allow multiple values (array of strings)
|
||
|
|
minRows?: number // Min items when hasMany: true
|
||
|
|
maxRows?: number // Max items when hasMany: true
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
placeholder?: string // Placeholder text
|
||
|
|
autoComplete?: string // HTML autocomplete attribute
|
||
|
|
rtl?: boolean // Right-to-left text
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Short strings (titles, names, slugs, URLs)
|
||
|
|
- Data is typically < 200 characters
|
||
|
|
- No line breaks expected
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{ "title": "Hello World" }
|
||
|
|
{ "slug": "hello-world" }
|
||
|
|
{ "url": "https://example.com" }
|
||
|
|
{ "sku": "PROD-12345" }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{ name: 'title', type: 'text', required: true }
|
||
|
|
{ name: 'slug', type: 'text', unique: true, index: true }
|
||
|
|
{ name: 'tags', type: 'text', hasMany: true, maxRows: 10 }
|
||
|
|
{ name: 'sku', type: 'text', minLength: 5, maxLength: 20 }
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### textarea
|
||
|
|
|
||
|
|
Multi-line text without formatting.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type TextareaField = BaseField & {
|
||
|
|
type: 'textarea'
|
||
|
|
minLength?: number // Minimum character count
|
||
|
|
maxLength?: number // Maximum character count
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
placeholder?: string // Placeholder text
|
||
|
|
rows?: number // Visible rows (height)
|
||
|
|
rtl?: boolean // Right-to-left text
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Longer text content without HTML/rich formatting
|
||
|
|
- Descriptions, excerpts, plain summaries
|
||
|
|
- Data contains line breaks but no markup
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{ "description": "A longer description\nthat spans multiple lines" }
|
||
|
|
{ "excerpt": "Brief summary of the content..." }
|
||
|
|
{ "bio": "Author biography text here" }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{ name: 'description', type: 'textarea' }
|
||
|
|
{ name: 'excerpt', type: 'textarea', maxLength: 500 }
|
||
|
|
{ name: 'bio', type: 'textarea', admin: { rows: 6 } }
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### richText
|
||
|
|
|
||
|
|
Rich text editor (Lexical by default, or Slate).
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type RichTextField = BaseField & {
|
||
|
|
type: 'richText'
|
||
|
|
editor?: LexicalEditorConfig // Lexical editor configuration
|
||
|
|
// Lexical-specific options (via editor config):
|
||
|
|
// - features: Enable/disable toolbar features
|
||
|
|
// - lexical: Raw Lexical configuration
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
hideGutter?: boolean // Hide left gutter
|
||
|
|
elements?: string[] // Deprecated (Slate). Use editor.features
|
||
|
|
leaves?: string[] // Deprecated (Slate). Use editor.features
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- HTML content from WYSIWYG editors
|
||
|
|
- Markdown content (will need conversion)
|
||
|
|
- Content with formatting (bold, italic, links, headings)
|
||
|
|
- Content blocks from Contentful, Sanity, etc.
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{ "content": "<p>Hello <strong>world</strong></p>" }
|
||
|
|
{ "body": "# Heading\n\nParagraph with **bold**" }
|
||
|
|
{ "content": { "nodeType": "document", "content": [...] } }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{ name: 'content', type: 'richText' }
|
||
|
|
{ name: 'body', type: 'richText', required: true }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Migration notes:**
|
||
|
|
- WordPress `content.rendered` can be imported as HTML
|
||
|
|
- Contentful Rich Text requires conversion to Lexical format
|
||
|
|
- Markdown should be converted to HTML first, or use Lexical markdown plugin
|
||
|
|
- Data stored as Lexical JSON, not HTML
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### number
|
||
|
|
|
||
|
|
Numeric values (integers or decimals).
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type NumberField = BaseField & {
|
||
|
|
type: 'number'
|
||
|
|
min?: number // Minimum value
|
||
|
|
max?: number // Maximum value
|
||
|
|
hasMany?: boolean // Allow multiple values (array of numbers)
|
||
|
|
minRows?: number // Min items when hasMany: true
|
||
|
|
maxRows?: number // Max items when hasMany: true
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
placeholder?: string // Placeholder text
|
||
|
|
autoComplete?: string // HTML autocomplete attribute
|
||
|
|
step?: number // Increment step (e.g., 0.01 for currency)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Prices, quantities, counts
|
||
|
|
- Ratings, scores
|
||
|
|
- Any numeric data
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{ "price": 29.99 }
|
||
|
|
{ "quantity": 5 }
|
||
|
|
{ "rating": 4.5 }
|
||
|
|
{ "views": 1000 }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{ name: 'price', type: 'number', min: 0, admin: { step: 0.01 } }
|
||
|
|
{ name: 'quantity', type: 'number', min: 0, max: 1000 }
|
||
|
|
{ name: 'rating', type: 'number', min: 0, max: 5 }
|
||
|
|
{ name: 'scores', type: 'number', hasMany: true }
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### email
|
||
|
|
|
||
|
|
Email address field with built-in validation.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type EmailField = BaseField & {
|
||
|
|
type: 'email'
|
||
|
|
minLength?: number // Minimum character count
|
||
|
|
maxLength?: number // Maximum character count
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
placeholder?: string // Placeholder text
|
||
|
|
autoComplete?: string // HTML autocomplete attribute
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Field contains email addresses
|
||
|
|
- Field name suggests email (email, contactEmail, etc.)
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{ "email": "user@example.com" }
|
||
|
|
{ "contactEmail": "support@company.com" }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{ name: 'email', type: 'email', required: true }
|
||
|
|
{ name: 'contactEmail', type: 'email', admin: { placeholder: 'you@example.com' } }
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### date
|
||
|
|
|
||
|
|
Date/datetime picker.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type DateField = BaseField & {
|
||
|
|
type: 'date'
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
placeholder?: string // Placeholder text
|
||
|
|
date?: {
|
||
|
|
displayFormat?: string // Display format (e.g., 'MMM d, yyyy')
|
||
|
|
pickerAppearance?: 'dayOnly' | 'dayAndTime' | 'monthOnly' | 'timeOnly'
|
||
|
|
minDate?: Date // Earliest selectable date
|
||
|
|
maxDate?: Date // Latest selectable date
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- ISO date strings
|
||
|
|
- Timestamps
|
||
|
|
- Any date/time values
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{ "publishedAt": "2024-01-15T10:30:00Z" }
|
||
|
|
{ "createdAt": "2024-01-15" }
|
||
|
|
{ "eventDate": 1705312200000 }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{ name: 'publishedAt', type: 'date' }
|
||
|
|
{ name: 'eventDate', type: 'date', admin: { date: { pickerAppearance: 'dayAndTime' } } }
|
||
|
|
{ name: 'birthDate', type: 'date', admin: { date: { pickerAppearance: 'dayOnly' } } }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Migration notes:**
|
||
|
|
- Payload stores dates as ISO strings
|
||
|
|
- Unix timestamps should be converted: `new Date(timestamp).toISOString()`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### checkbox
|
||
|
|
|
||
|
|
Boolean true/false toggle.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type CheckboxField = BaseField & {
|
||
|
|
type: 'checkbox'
|
||
|
|
defaultValue?: boolean // Default checked state
|
||
|
|
admin?: BaseField['admin'] // No additional checkbox-specific admin options
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Boolean values
|
||
|
|
- Yes/no flags
|
||
|
|
- Feature toggles
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{ "featured": true }
|
||
|
|
{ "isPublished": false }
|
||
|
|
{ "allowComments": true }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{ name: 'featured', type: 'checkbox', defaultValue: false }
|
||
|
|
{ name: 'isPublished', type: 'checkbox' }
|
||
|
|
{ name: 'allowComments', type: 'checkbox', defaultValue: true }
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### select
|
||
|
|
|
||
|
|
Dropdown with predefined options.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type SelectField = BaseField & {
|
||
|
|
type: 'select'
|
||
|
|
options: Array< // Required. List of options
|
||
|
|
| string // Simple: just the value (label = value)
|
||
|
|
| { label: string; value: string } // Full: separate label and value
|
||
|
|
>
|
||
|
|
hasMany?: boolean // Allow multiple selections
|
||
|
|
defaultValue?: string | string[] // Default selected value(s)
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
isClearable?: boolean // Allow clearing selection
|
||
|
|
isSortable?: boolean // Allow drag-to-reorder when hasMany
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Enum values
|
||
|
|
- Status fields
|
||
|
|
- Category/type with fixed options
|
||
|
|
- Field has limited set of valid values
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{ "status": "published" }
|
||
|
|
{ "priority": "high" }
|
||
|
|
{ "type": "article" }
|
||
|
|
{ "tags": ["featured", "trending"] }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
// Simple options (value = label)
|
||
|
|
{ name: 'priority', type: 'select', options: ['low', 'medium', 'high'] }
|
||
|
|
|
||
|
|
// Full options
|
||
|
|
{
|
||
|
|
name: 'status',
|
||
|
|
type: 'select',
|
||
|
|
options: [
|
||
|
|
{ label: 'Draft', value: 'draft' },
|
||
|
|
{ label: 'Published', value: 'published' },
|
||
|
|
{ label: 'Archived', value: 'archived' },
|
||
|
|
],
|
||
|
|
defaultValue: 'draft',
|
||
|
|
}
|
||
|
|
|
||
|
|
// Multiple selection
|
||
|
|
{
|
||
|
|
name: 'tags',
|
||
|
|
type: 'select',
|
||
|
|
hasMany: true,
|
||
|
|
options: [
|
||
|
|
{ label: 'Featured', value: 'featured' },
|
||
|
|
{ label: 'Trending', value: 'trending' },
|
||
|
|
{ label: 'New', value: 'new' },
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Detecting options from data:**
|
||
|
|
If you see the same field with different values across records, collect unique values to build options:
|
||
|
|
```json
|
||
|
|
// Record 1: { "status": "draft" }
|
||
|
|
// Record 2: { "status": "published" }
|
||
|
|
// Record 3: { "status": "published" }
|
||
|
|
// options: draft, published
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### radio
|
||
|
|
|
||
|
|
Radio button group (single selection, always visible).
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type RadioField = BaseField & {
|
||
|
|
type: 'radio'
|
||
|
|
options: Array< // Required. List of options
|
||
|
|
| string // Simple: just the value
|
||
|
|
| { label: string; value: string } // Full: separate label and value
|
||
|
|
>
|
||
|
|
defaultValue?: string // Default selected value
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
layout?: 'horizontal' | 'vertical' // Button arrangement
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Same as select, but fewer options (2-4)
|
||
|
|
- User should see all options at once
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
name: 'size',
|
||
|
|
type: 'radio',
|
||
|
|
options: [
|
||
|
|
{ label: 'Small', value: 'sm' },
|
||
|
|
{ label: 'Medium', value: 'md' },
|
||
|
|
{ label: 'Large', value: 'lg' },
|
||
|
|
],
|
||
|
|
defaultValue: 'md',
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
name: 'alignment',
|
||
|
|
type: 'radio',
|
||
|
|
options: ['left', 'center', 'right'],
|
||
|
|
admin: { layout: 'horizontal' },
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### relationship
|
||
|
|
|
||
|
|
Reference to another document.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type RelationshipField = BaseField & {
|
||
|
|
type: 'relationship'
|
||
|
|
relationTo: string | string[] // Required. Target collection slug(s)
|
||
|
|
hasMany?: boolean // Allow multiple selections
|
||
|
|
minRows?: number // Min items when hasMany: true
|
||
|
|
maxRows?: number // Max items when hasMany: true
|
||
|
|
filterOptions?: // Limit selectable documents
|
||
|
|
| Where // Static where query
|
||
|
|
| ((args: FilterOptionsProps) => Where | boolean) // Dynamic filter
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
isSortable?: boolean // Allow drag-to-reorder when hasMany
|
||
|
|
allowCreate?: boolean // Allow creating new docs from field (default: true)
|
||
|
|
allowEdit?: boolean // Allow editing related doc inline
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// When relationTo is an array (polymorphic), stored value shape is:
|
||
|
|
// { relationTo: 'collectionSlug', value: 'documentId' }
|
||
|
|
|
||
|
|
// When relationTo is a string, stored value is just the ID:
|
||
|
|
// 'documentId'
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Foreign key / ID reference to another collection
|
||
|
|
- Nested object that should be a separate document
|
||
|
|
- Author, category, tag references
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
// ID reference
|
||
|
|
{ "author": 123 }
|
||
|
|
{ "authorId": "user_abc123" }
|
||
|
|
|
||
|
|
// Object with ID
|
||
|
|
{ "author": { "id": 123, "name": "John" } }
|
||
|
|
|
||
|
|
// Contentful link
|
||
|
|
{ "author": { "sys": { "id": "abc123", "linkType": "Entry" } } }
|
||
|
|
|
||
|
|
// Array of references
|
||
|
|
{ "categories": [1, 2, 3] }
|
||
|
|
{ "tags": [{ "id": 1 }, { "id": 2 }] }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
// Single relationship
|
||
|
|
{ name: 'author', type: 'relationship', relationTo: 'users' }
|
||
|
|
|
||
|
|
// Multiple relationships (hasMany)
|
||
|
|
{ name: 'categories', type: 'relationship', relationTo: 'categories', hasMany: true }
|
||
|
|
|
||
|
|
// Polymorphic (multiple collection types)
|
||
|
|
{
|
||
|
|
name: 'relatedContent',
|
||
|
|
type: 'relationship',
|
||
|
|
relationTo: ['posts', 'pages', 'products'],
|
||
|
|
hasMany: true,
|
||
|
|
}
|
||
|
|
|
||
|
|
// With filter (only show published posts)
|
||
|
|
{
|
||
|
|
name: 'featuredPost',
|
||
|
|
type: 'relationship',
|
||
|
|
relationTo: 'posts',
|
||
|
|
filterOptions: {
|
||
|
|
status: { equals: 'published' },
|
||
|
|
},
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### upload
|
||
|
|
|
||
|
|
File/media upload field. References a document in an upload-enabled collection.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type UploadField = BaseField & {
|
||
|
|
type: 'upload'
|
||
|
|
relationTo: string // Required. Upload collection slug (e.g., 'media')
|
||
|
|
hasMany?: boolean // Allow multiple files
|
||
|
|
minRows?: number // Min items when hasMany: true
|
||
|
|
maxRows?: number // Max items when hasMany: true
|
||
|
|
filterOptions?: // Limit selectable files
|
||
|
|
| Where
|
||
|
|
| ((args: FilterOptionsProps) => Where | boolean)
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
isSortable?: boolean // Allow drag-to-reorder when hasMany
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Stored value is the upload document ID (or array of IDs when hasMany)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Image URLs or references
|
||
|
|
- File attachments
|
||
|
|
- Media library references
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
// URL reference
|
||
|
|
{ "featuredImage": "https://example.com/image.jpg" }
|
||
|
|
|
||
|
|
// WordPress media ID
|
||
|
|
{ "featured_media": 456 }
|
||
|
|
|
||
|
|
// Object with URL
|
||
|
|
{ "image": { "url": "https://...", "alt": "Description" } }
|
||
|
|
|
||
|
|
// Contentful asset
|
||
|
|
{ "image": { "sys": { "linkType": "Asset" }, "fields": { "file": { "url": "//images.ctfassets.net/..." } } } }
|
||
|
|
|
||
|
|
// Multiple images
|
||
|
|
{ "gallery": ["https://...", "https://..."] }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{ name: 'featuredImage', type: 'upload', relationTo: 'media' }
|
||
|
|
{ name: 'gallery', type: 'upload', relationTo: 'media', hasMany: true, maxRows: 10 }
|
||
|
|
{ name: 'document', type: 'upload', relationTo: 'documents' }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Migration notes:**
|
||
|
|
- Download remote images and upload to Payload
|
||
|
|
- Store the new Payload media ID in the field
|
||
|
|
- Preserve alt text as a separate field on the media collection or via a group
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### array
|
||
|
|
|
||
|
|
Repeatable group of fields.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type ArrayField = BaseField & {
|
||
|
|
type: 'array'
|
||
|
|
fields: Field[] // Required. Sub-fields for each row
|
||
|
|
minRows?: number // Minimum number of rows
|
||
|
|
maxRows?: number // Maximum number of rows
|
||
|
|
labels?: { // Custom row labels
|
||
|
|
singular?: string
|
||
|
|
plural?: string
|
||
|
|
}
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
initCollapsed?: boolean // Start rows collapsed
|
||
|
|
isSortable?: boolean // Allow drag-to-reorder (default: true)
|
||
|
|
components?: {
|
||
|
|
RowLabel?: Component // Custom row label component
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Each row automatically gets an 'id' field
|
||
|
|
}
|
||
|
|
|
||
|
|
// Stored as array of objects:
|
||
|
|
// [{ id: 'abc', field1: 'value', field2: 'value' }, ...]
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Array of objects with consistent structure
|
||
|
|
- Repeater fields (ACF, etc.)
|
||
|
|
- List of items with multiple properties each
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"socialLinks": [
|
||
|
|
{ "platform": "twitter", "url": "https://twitter.com/..." },
|
||
|
|
{ "platform": "github", "url": "https://github.com/..." }
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
"features": [
|
||
|
|
{ "title": "Feature 1", "description": "..." },
|
||
|
|
{ "title": "Feature 2", "description": "..." }
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
name: 'socialLinks',
|
||
|
|
type: 'array',
|
||
|
|
labels: { singular: 'Link', plural: 'Links' },
|
||
|
|
minRows: 1,
|
||
|
|
maxRows: 5,
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: 'platform',
|
||
|
|
type: 'select',
|
||
|
|
options: ['twitter', 'github', 'linkedin'],
|
||
|
|
required: true,
|
||
|
|
},
|
||
|
|
{ name: 'url', type: 'text', required: true },
|
||
|
|
],
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
name: 'features',
|
||
|
|
type: 'array',
|
||
|
|
fields: [
|
||
|
|
{ name: 'title', type: 'text', required: true },
|
||
|
|
{ name: 'description', type: 'textarea' },
|
||
|
|
{ name: 'icon', type: 'upload', relationTo: 'media' },
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### group
|
||
|
|
|
||
|
|
Nested object (non-repeating).
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type GroupField = BaseField & {
|
||
|
|
type: 'group'
|
||
|
|
fields: Field[] // Required. Sub-fields
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
hideGutter?: boolean // Remove left border/gutter
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Stored as nested object:
|
||
|
|
// { field1: 'value', field2: 'value' }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Nested object that's always singular
|
||
|
|
- Organizational grouping of related fields
|
||
|
|
- SEO metadata, address blocks, etc.
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"seo": {
|
||
|
|
"title": "Page Title",
|
||
|
|
"description": "Meta description",
|
||
|
|
"keywords": ["a", "b"]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
"address": {
|
||
|
|
"street": "123 Main St",
|
||
|
|
"city": "Springfield",
|
||
|
|
"zip": "12345"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
name: 'seo',
|
||
|
|
type: 'group',
|
||
|
|
label: 'SEO Settings',
|
||
|
|
fields: [
|
||
|
|
{ name: 'title', type: 'text', maxLength: 60 },
|
||
|
|
{ name: 'description', type: 'textarea', maxLength: 160 },
|
||
|
|
{ name: 'keywords', type: 'text', hasMany: true },
|
||
|
|
],
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
name: 'address',
|
||
|
|
type: 'group',
|
||
|
|
fields: [
|
||
|
|
{ name: 'street', type: 'text' },
|
||
|
|
{ name: 'city', type: 'text' },
|
||
|
|
{ name: 'state', type: 'text' },
|
||
|
|
{ name: 'zip', type: 'text' },
|
||
|
|
{ name: 'country', type: 'select', options: ['US', 'CA', 'UK'] },
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### blocks
|
||
|
|
|
||
|
|
Flexible content / page builder blocks.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type BlocksField = BaseField & {
|
||
|
|
type: 'blocks'
|
||
|
|
blocks: Block[] // Required. Available block types
|
||
|
|
minRows?: number // Minimum number of blocks
|
||
|
|
maxRows?: number // Maximum number of blocks
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
initCollapsed?: boolean // Start blocks collapsed
|
||
|
|
isSortable?: boolean // Allow drag-to-reorder (default: true)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
type Block = {
|
||
|
|
slug: string // Required. Unique block identifier
|
||
|
|
labels?: { // Custom labels
|
||
|
|
singular?: string
|
||
|
|
plural?: string
|
||
|
|
}
|
||
|
|
fields: Field[] // Required. Fields in this block
|
||
|
|
imageURL?: string // Preview image URL
|
||
|
|
imageAltText?: string // Alt text for preview
|
||
|
|
admin?: {
|
||
|
|
components?: {
|
||
|
|
Label?: Component // Custom block label
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Stored as array with blockType identifier:
|
||
|
|
// [
|
||
|
|
// { id: 'abc', blockType: 'hero', title: 'Welcome' },
|
||
|
|
// { id: 'def', blockType: 'textBlock', content: {...} }
|
||
|
|
// ]
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Dynamic content zones
|
||
|
|
- Page builder layouts
|
||
|
|
- ACF Flexible Content
|
||
|
|
- Contentful/Sanity block content
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"layout": [
|
||
|
|
{ "type": "hero", "title": "Welcome", "image": "..." },
|
||
|
|
{ "type": "textBlock", "content": "<p>...</p>" },
|
||
|
|
{ "type": "gallery", "images": [...] }
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
name: 'layout',
|
||
|
|
type: 'blocks',
|
||
|
|
minRows: 1,
|
||
|
|
blocks: [
|
||
|
|
{
|
||
|
|
slug: 'hero',
|
||
|
|
labels: { singular: 'Hero Section', plural: 'Hero Sections' },
|
||
|
|
fields: [
|
||
|
|
{ name: 'title', type: 'text', required: true },
|
||
|
|
{ name: 'subtitle', type: 'text' },
|
||
|
|
{ name: 'image', type: 'upload', relationTo: 'media' },
|
||
|
|
{
|
||
|
|
name: 'cta',
|
||
|
|
type: 'group',
|
||
|
|
fields: [
|
||
|
|
{ name: 'label', type: 'text' },
|
||
|
|
{ name: 'url', type: 'text' },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
slug: 'textBlock',
|
||
|
|
labels: { singular: 'Text Block', plural: 'Text Blocks' },
|
||
|
|
fields: [
|
||
|
|
{ name: 'content', type: 'richText', required: true },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
slug: 'gallery',
|
||
|
|
fields: [
|
||
|
|
{ name: 'images', type: 'upload', relationTo: 'media', hasMany: true },
|
||
|
|
{ name: 'columns', type: 'select', options: ['2', '3', '4'] },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Migration notes:**
|
||
|
|
- Map source block `type` field to Payload `blockType`
|
||
|
|
- Each block type needs its own field definitions
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### json
|
||
|
|
|
||
|
|
Arbitrary JSON data.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type JSONField = BaseField & {
|
||
|
|
type: 'json'
|
||
|
|
jsonSchema?: JSONSchema // Optional JSON Schema for validation
|
||
|
|
admin?: BaseField['admin'] & {
|
||
|
|
editorOptions?: object // Monaco editor options
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Stored as-is (any valid JSON)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Unstructured or highly variable data
|
||
|
|
- Third-party API responses to store
|
||
|
|
- Data that doesn't fit other types
|
||
|
|
- Temporary/flexible storage during migration
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{ "metadata": { "arbitrary": "data", "nested": { "values": true } } }
|
||
|
|
{ "apiResponse": { ... } }
|
||
|
|
{ "config": { "settings": [...] } }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{ name: 'metadata', type: 'json' }
|
||
|
|
|
||
|
|
// With JSON Schema validation
|
||
|
|
{
|
||
|
|
name: 'settings',
|
||
|
|
type: 'json',
|
||
|
|
jsonSchema: {
|
||
|
|
type: 'object',
|
||
|
|
properties: {
|
||
|
|
theme: { type: 'string' },
|
||
|
|
notifications: { type: 'boolean' },
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Migration notes:**
|
||
|
|
- Use as fallback when data structure is unknown or highly variable
|
||
|
|
- Consider converting to proper fields later for better querying
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### point
|
||
|
|
|
||
|
|
Geographic coordinates (longitude, latitude).
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type PointField = BaseField & {
|
||
|
|
type: 'point'
|
||
|
|
admin?: BaseField['admin'] // No additional point-specific admin options
|
||
|
|
}
|
||
|
|
|
||
|
|
// Stored as GeoJSON Point:
|
||
|
|
// [longitude, latitude] // Note: longitude first!
|
||
|
|
// e.g., [-74.0060, 40.7128] for New York City
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use when:**
|
||
|
|
- Latitude/longitude pairs
|
||
|
|
- Map locations
|
||
|
|
- Geolocation data
|
||
|
|
|
||
|
|
**Source patterns:**
|
||
|
|
```json
|
||
|
|
{ "location": { "lat": 40.7128, "lng": -74.0060 } }
|
||
|
|
{ "coordinates": [40.7128, -74.0060] }
|
||
|
|
{ "geo": { "latitude": 40.7128, "longitude": -74.0060 } }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config examples:**
|
||
|
|
```typescript
|
||
|
|
{ name: 'location', type: 'point' }
|
||
|
|
{ name: 'coordinates', type: 'point', required: true }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Migration notes:**
|
||
|
|
- Payload uses GeoJSON format: `[longitude, latitude]`
|
||
|
|
- Many sources use `[latitude, longitude]` - swap if needed!
|
||
|
|
- Convert from `{ lat, lng }` objects to `[lng, lat]` array
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### row (Layout)
|
||
|
|
|
||
|
|
Horizontal layout for placing fields side-by-side.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type RowField = {
|
||
|
|
type: 'row'
|
||
|
|
fields: Field[] // Required. Fields to display in row
|
||
|
|
admin?: {
|
||
|
|
condition?: Function // Conditionally show/hide
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// No name required - purely layout
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config example:**
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
type: 'row',
|
||
|
|
fields: [
|
||
|
|
{ name: 'firstName', type: 'text', admin: { width: '50%' } },
|
||
|
|
{ name: 'lastName', type: 'text', admin: { width: '50%' } },
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### collapsible (Layout)
|
||
|
|
|
||
|
|
Collapsible section for grouping fields.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type CollapsibleField = {
|
||
|
|
type: 'collapsible'
|
||
|
|
label: string | Function // Required. Section header
|
||
|
|
fields: Field[] // Required. Fields inside
|
||
|
|
admin?: {
|
||
|
|
initCollapsed?: boolean // Start collapsed (default: false)
|
||
|
|
condition?: Function
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// No name required - purely layout
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config example:**
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
type: 'collapsible',
|
||
|
|
label: 'Advanced Settings',
|
||
|
|
admin: { initCollapsed: true },
|
||
|
|
fields: [
|
||
|
|
{ name: 'customCSS', type: 'textarea' },
|
||
|
|
{ name: 'customJS', type: 'textarea' },
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### tabs (Layout)
|
||
|
|
|
||
|
|
Tabbed interface for organizing fields.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type TabsField = {
|
||
|
|
type: 'tabs'
|
||
|
|
tabs: Tab[] // Required. Array of tabs
|
||
|
|
admin?: {
|
||
|
|
condition?: Function
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
type Tab = {
|
||
|
|
label: string // Required. Tab label
|
||
|
|
name?: string // If set, fields are nested under this key
|
||
|
|
fields: Field[] // Required. Fields in this tab
|
||
|
|
description?: string // Help text for tab
|
||
|
|
}
|
||
|
|
// No name on parent - tabs are layout only (unless tab has name)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Payload config example:**
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
type: 'tabs',
|
||
|
|
tabs: [
|
||
|
|
{
|
||
|
|
label: 'Content',
|
||
|
|
fields: [
|
||
|
|
{ name: 'title', type: 'text' },
|
||
|
|
{ name: 'body', type: 'richText' },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
label: 'SEO',
|
||
|
|
name: 'seo', // Fields nested under 'seo' key
|
||
|
|
fields: [
|
||
|
|
{ name: 'title', type: 'text' },
|
||
|
|
{ name: 'description', type: 'textarea' },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### ui (Custom Component)
|
||
|
|
|
||
|
|
Render custom React component without storing data.
|
||
|
|
|
||
|
|
**Full schema:**
|
||
|
|
```typescript
|
||
|
|
type UIField = {
|
||
|
|
type: 'ui'
|
||
|
|
name: string // Required (for key, not storage)
|
||
|
|
admin: {
|
||
|
|
components: {
|
||
|
|
Field: Component // Required. React component to render
|
||
|
|
Cell?: Component // List view component
|
||
|
|
}
|
||
|
|
condition?: Function
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Does NOT store data - purely visual
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Collection-Level Configuration
|
||
|
|
|
||
|
|
### Basic Collection
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const posts: CollectionConfig = {
|
||
|
|
slug: 'posts',
|
||
|
|
labels: {
|
||
|
|
singular: 'Post',
|
||
|
|
plural: 'Posts',
|
||
|
|
},
|
||
|
|
fields: [
|
||
|
|
{ name: 'title', type: 'text', required: true },
|
||
|
|
{ name: 'slug', type: 'text', unique: true },
|
||
|
|
{ name: 'content', type: 'richText' },
|
||
|
|
{ name: 'author', type: 'relationship', relationTo: 'users' },
|
||
|
|
{ name: 'publishedAt', type: 'date' },
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Upload Collection
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const media: CollectionConfig = {
|
||
|
|
slug: 'media',
|
||
|
|
labels: {
|
||
|
|
singular: 'Media',
|
||
|
|
plural: 'Media',
|
||
|
|
},
|
||
|
|
upload: {
|
||
|
|
staticDir: 'media', // Directory for files (relative to project)
|
||
|
|
staticURL: '/media', // URL path prefix
|
||
|
|
mimeTypes: ['image/*', 'application/pdf'], // Allowed types
|
||
|
|
filesRequiredOnCreate: true, // Require file on create (default: true)
|
||
|
|
|
||
|
|
// Image-specific options:
|
||
|
|
imageSizes: [ // Auto-generate resized versions
|
||
|
|
{ name: 'thumbnail', width: 300, height: 300, position: 'centre' },
|
||
|
|
{ name: 'card', width: 768, height: 1024, position: 'centre' },
|
||
|
|
{ name: 'tablet', width: 1024 }, // Height auto
|
||
|
|
],
|
||
|
|
adminThumbnail: 'thumbnail', // Size to show in admin
|
||
|
|
focalPoint: true, // Enable focal point selection
|
||
|
|
crop: true, // Enable cropping
|
||
|
|
|
||
|
|
// Storage adapter (optional - defaults to local):
|
||
|
|
// adapter: s3Adapter({ ... })
|
||
|
|
},
|
||
|
|
fields: [
|
||
|
|
{ name: 'alt', type: 'text', required: true },
|
||
|
|
{ name: 'caption', type: 'textarea' },
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Upload document auto-fields:**
|
||
|
|
When you create an upload collection, Payload automatically adds these fields:
|
||
|
|
- `filename` - Original filename
|
||
|
|
- `mimeType` - File MIME type
|
||
|
|
- `filesize` - Size in bytes
|
||
|
|
- `width` - Image width (images only)
|
||
|
|
- `height` - Image height (images only)
|
||
|
|
- `url` - Public URL to file
|
||
|
|
- `thumbnailURL` - URL to thumbnail (if imageSizes configured)
|
||
|
|
- `sizes` - Object with all generated size URLs
|
||
|
|
|
||
|
|
### Auth Collection
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const users: CollectionConfig = {
|
||
|
|
slug: 'users',
|
||
|
|
auth: true,
|
||
|
|
fields: [
|
||
|
|
{ name: 'name', type: 'text' },
|
||
|
|
{ name: 'role', type: 'select', options: ['admin', 'editor', 'user'] },
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Common Migration Patterns
|
||
|
|
|
||
|
|
### WordPress to Payload
|
||
|
|
|
||
|
|
| WordPress | Payload |
|
||
|
|
|-----------|---------|
|
||
|
|
| `post_title` | `text` (title) |
|
||
|
|
| `post_content` | `richText` (HTML) |
|
||
|
|
| `post_excerpt` | `textarea` |
|
||
|
|
| `post_status` | `select` (draft/published) |
|
||
|
|
| `post_author` | `relationship` to users |
|
||
|
|
| `featured_media` | `upload` to media |
|
||
|
|
| `post_date` | `date` |
|
||
|
|
| ACF Repeater | `array` |
|
||
|
|
| ACF Group | `group` |
|
||
|
|
| ACF Flexible Content | `blocks` |
|
||
|
|
|
||
|
|
### Contentful to Payload
|
||
|
|
|
||
|
|
| Contentful | Payload |
|
||
|
|
|------------|---------|
|
||
|
|
| Short Text | `text` |
|
||
|
|
| Long Text | `textarea` |
|
||
|
|
| Rich Text | `richText` (needs conversion) |
|
||
|
|
| Number | `number` |
|
||
|
|
| Date | `date` |
|
||
|
|
| Boolean | `checkbox` |
|
||
|
|
| Media | `upload` |
|
||
|
|
| Reference | `relationship` |
|
||
|
|
| Array of References | `relationship` (hasMany) |
|
||
|
|
|
||
|
|
### Strapi to Payload
|
||
|
|
|
||
|
|
| Strapi | Payload |
|
||
|
|
|--------|---------|
|
||
|
|
| string | `text` |
|
||
|
|
| text | `textarea` |
|
||
|
|
| richtext/blocks | `richText` |
|
||
|
|
| integer/float/decimal | `number` |
|
||
|
|
| boolean | `checkbox` |
|
||
|
|
| date/datetime | `date` |
|
||
|
|
| enumeration | `select` |
|
||
|
|
| media | `upload` |
|
||
|
|
| relation | `relationship` |
|
||
|
|
| component | `group` or `array` |
|
||
|
|
| dynamiczone | `blocks` |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## AI Instructions
|
||
|
|
|
||
|
|
When analyzing source data to generate Payload config:
|
||
|
|
|
||
|
|
1. **Identify collections** - Each distinct content type becomes a collection
|
||
|
|
2. **Detect relationships** - ID references between types become `relationship` fields
|
||
|
|
3. **Infer field types** - Use the patterns above to match data to Payload types
|
||
|
|
4. **Preserve structure** - Nested objects become `group`, arrays of objects become `array`
|
||
|
|
5. **Flag unknowns** - If data doesn't match patterns, suggest `json` as fallback and add a warning
|
||
|
|
6. **Generate valid TypeScript** - Output should be copy-paste ready
|
||
|
|
|
||
|
|
**Output format:**
|
||
|
|
```typescript
|
||
|
|
import type { CollectionConfig } from 'payload'
|
||
|
|
|
||
|
|
export const collectionName: CollectionConfig = {
|
||
|
|
slug: 'collection-name',
|
||
|
|
fields: [
|
||
|
|
// fields here
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|