dso/apps/cms/.vibe/skills/cms-migration/reference/PAYLOAD-FIELD-REFERENCE.md

1253 lines
30 KiB
Markdown
Raw Normal View History

2026-09-03 19:51:56 +02:00
# 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
],
}
```