# PayloadCMS Field Mapping

Frontmatter fields in the markdown source are mapped to PayloadCMS document fields by the publishing script.

## Standard fields

| Frontmatter Key | PayloadCMS Field | Type | Auto-populated | Notes |
|-----------------|-----------------|------|----------------|-------|
| `title` | `title` | string | No | Required — must be set by user |
| `slug` | `slug` | string | Yes | Kebab-case from title, max 80 chars |
| `excerpt` | `excerpt` | textarea | Yes | 1-2 sentence summary, max 160 chars |
| `meta.title` | `meta.title` | string | Yes | SEO title with primary keyword, max 60 chars |
| `meta.description` | `meta.description` | string | Yes | Search snippet with keyword + CTA, max 155 chars |
| `heroImage` | `heroImage` | upload | Yes | Promoted from first content image if standalone |
| `tags` | `tags` | relationship[] | No | Array of tag names or IDs |
| `category` | `category` | relationship | No | Category name or ID |
| `status` | `status` | string | No | Overridden by `--status` CLI flag |

**Auto-populated** fields are generated by Claude during the publishing workflow (Step 2) when missing from frontmatter. User-provided values always take precedence.

## How mapping works

The script uses `gray-matter` to parse YAML frontmatter, then spreads all frontmatter fields directly into the PayloadCMS document payload:

```yaml
---
title: My Post
customField: some value
nestedField:
  subField: nested value
---
```

Becomes:

```json
{
  "title": "My Post",
  "customField": "some value",
  "nestedField": { "subField": "nested value" },
  "content": { "root": { ... } },
  "status": "draft"
}
```

Any custom fields defined in your PayloadCMS collection schema can be set via frontmatter — the script passes them through as-is.

## Hero image (featured image)

PayloadCMS handles hero/featured images as a collection-level `upload` field — not inside the Lexical editor. This is the same pattern as WordPress featured images.

```yaml
---
title: My Post
heroImage: 64a1b2c3d4e5f6   # media document ID
---
```

The field name depends on your collection schema (common names: `heroImage`, `featuredImage`, `image`, `hero.media`). Provide the media ID directly. If you need to upload first, use the `uploadMedia()` utility and reference the returned ID.

For nested hero fields:

```yaml
---
hero:
  type: highImpact
  media: 64a1b2c3d4e5f6
---
```

## SEO / Meta fields

If your PayloadCMS instance uses `@payloadcms/plugin-seo`, SEO fields are collection-level (not Lexical nodes). The plugin adds a `meta` group field:

```yaml
---
title: My Post
meta:
  title: SEO Title Override
  description: Meta description for search engines
  image: 64a1b2c3d4e5f6   # media ID for og:image
---
```

If your schema uses flat field names instead (no plugin), adjust accordingly:

```yaml
---
meta_title: SEO Title
meta_description: Meta description
---
```

## Excerpt

Excerpts are typically a plain `textarea` field on the collection, not part of the Lexical editor:

```yaml
---
title: My Post
excerpt: A brief summary of the article shown in listing pages and social cards.
---
```

## Glossary

PayloadCMS has no built-in glossary feature. Glossaries are implemented as either:

1. **A separate `glossary` collection** with `term` and `definition` fields — reference terms via a relationship field on your post:

```yaml
---
glossaryTerms: [term-id-1, term-id-2]
---
```

2. **A custom Lexical block** via `BlocksFeature` — these are rendered inside the rich text editor but require a custom block definition on the PayloadCMS instance. The markdown converter cannot produce custom blocks; they must be injected programmatically after conversion.

## Relationship fields

Tags and categories are typically relationship fields in PayloadCMS. Provide either:
- **Names** (if your schema has a `name` field): `tags: [javascript, tutorial]`
- **IDs** (for direct references): `tags: [abc123, def456]`

The exact behavior depends on your PayloadCMS collection schema configuration.

## Media uploads (in content body)

The publishing script automatically uploads images referenced in the markdown content body. During `publishContent()`:

1. `<div class="infographic" ...>` and `</div>` wrapper lines are stripped
2. Standalone `![alt](path)` image lines are detected
3. Each image file is uploaded to `/api/media` via `uploadMedia()`
4. The image reference is replaced with an `upload` Lexical node pointing to the media ID

Image paths are resolved relative to the markdown file's directory. If an image file doesn't exist or upload fails, a warning is logged and the image markdown is left as-is (rendered as text).

Media referenced in frontmatter fields (e.g. `heroImage`) is **not** auto-uploaded — provide a media ID directly for those fields.

## Inline images

PayloadCMS Lexical does **not** support inline images. The `UploadNode` is block-level only (`isInline()` returns `false`). Images in content are always rendered as standalone blocks between paragraphs, never inline with text. This is a Lexical architecture constraint, not a limitation of this script.
