---
name: payloadcms-publisher
description: Publishes markdown content to PayloadCMS with automatic Lexical rich text conversion, SEO metadata generation, and media uploads. Use when publishing content to a PayloadCMS instance, uploading articles to Payload CMS, or when the user mentions PayloadCMS publishing.
argument-hint: "[file.md] [--status draft|published] [--collection posts] [--dry-run]"
allowed-tools: [Read, Edit, Bash, Glob, Grep]
disable-model-invocation: true
---

# PayloadCMS Publisher

Publish markdown files to PayloadCMS via the bundled script. The script handles authentication, markdown-to-Lexical conversion, image uploads, and API calls.

## Prerequisites

Credentials in `.env`:

```
PAYLOADCMS_URL=https://your-payload-instance.com
PAYLOADCMS_EMAIL=admin@example.com
PAYLOADCMS_PASSWORD=your-password
```

Run `/myaidev-method:configure payloadcms` to set these up.

## Publishing workflow

Follow these steps in order.

### Step 1: Read and validate the source file

Read the markdown file. Verify it has YAML frontmatter with at least a `title`. Stop if missing.

### Step 2: Auto-populate missing fields

Inspect the frontmatter. For any of these fields that are **missing or empty**, generate them from the content body and write them into the frontmatter before publishing:

| Field | How to generate | Max length |
|-------|----------------|------------|
| `slug` | Kebab-case from `title` | 80 chars |
| `excerpt` | Summarize the article in 1-2 sentences. Capture the core value proposition. | 160 chars |
| `meta.title` | SEO-optimized variation of `title`. Include primary keyword near the front. | 60 chars |
| `meta.description` | Compelling search snippet. Include primary keyword, end with implicit CTA. | 155 chars |
| `heroImage` | If the first element in the content body is a standalone image (`![alt](path)` on its own line), **promote** it: remove it from the body and note the path for hero image upload in Step 3. |  |

**Do not overwrite** fields the user already set. Only fill gaps.

After generating, write the updated frontmatter back to the file, then re-read to confirm.

### Step 3: Upload hero image (if needed)

If Step 2 identified a hero image to promote, upload it before publishing:

```bash
node --input-type=module -e "
import { PayloadCMSUtils } from '${CLAUDE_PLUGIN_ROOT}/src/lib/payloadcms-utils.js';
const u = new PayloadCMSUtils(); await u.authenticate();
const r = await u.uploadMedia('<hero-path>', '<hero-alt>');
console.log(JSON.stringify({ id: r.doc?.id || r.id }));
"
```

Substitute `<hero-path>` and `<hero-alt>` with the image path and alt text from the promoted image.

Then set `heroImage` in frontmatter to the returned media ID.

### Step 4: Dry run

```bash
node "${CLAUDE_PLUGIN_ROOT}/src/scripts/payloadcms-publish.js" "<file>" --collection "<collection>" --status "<status>" --dry-run --json --verbose
```

Defaults: collection=`posts`, status=`draft`. Parse stdout as JSON. If `"success": false`, use the [error recovery table](#error-recovery) and stop.

### Step 5: Publish

```bash
node "${CLAUDE_PLUGIN_ROOT}/src/scripts/payloadcms-publish.js" "<file>" --collection "<collection>" --status "<status>" --json --verbose
```

Add `--id <id>` if updating an existing document.

### Step 6: Report result

```
PayloadCMS Publish Result
  Action:     created | updated
  Document:   <document.id>
  Collection: <document.collection>
  Title:      <document.title>
  Auto-generated: <list any fields you populated in Step 2>
```

## Script CLI reference

Script: `${CLAUDE_PLUGIN_ROOT}/src/scripts/payloadcms-publish.js`

| Flag | Description | Default |
|------|-------------|---------|
| `<file>` | Markdown file path (positional, or `--file`/`-f`) | Required |
| `--collection`, `-c` | Target collection | `posts` |
| `--status`, `-s` | `draft` or `published` | `draft` |
| `--id` | Document ID for updates | New document |
| `--dry-run` | Validate health + auth only | Off |
| `--json` | Machine-readable JSON on stdout | Off |
| `--verbose`, `-v` | Progress on stderr | Off |

**Always pass `--json --verbose`**.

## Markdown file format

```markdown
---
title: My Article Title
slug: my-article-title
excerpt: A brief summary for listing pages.
meta:
  title: SEO Title - Primary Keyword
  description: Compelling meta description with keyword and implicit CTA.
heroImage: 64a1b2c3d4e5f6
tags: [tag1, tag2]
category: tutorials
---

Your article content here with **bold**, *italic*, `code`, [links](https://...), lists, headings, and code blocks.
```

Any frontmatter key maps directly to a PayloadCMS field. See [references/field-mapping.md](references/field-mapping.md) for all supported fields and patterns (hero images, SEO, excerpts, glossary, relationships).

See [references/lexical-format.md](references/lexical-format.md) for Lexical conversion details and supported node types.

## Error recovery

| Error | Cause | Fix |
|-------|-------|-----|
| `Failed to load PayloadCMS configuration` | Missing `.env` | Run `/myaidev-method:configure payloadcms` |
| `PayloadCMS is not reachable` | Wrong URL or down | Check `PAYLOADCMS_URL` |
| `Authentication failed (401)` | Bad credentials | Check email/password |
| `HTTP 404` | Collection missing | Verify collection name |
| `HTTP 400` | Validation error | Check required fields against schema |
| `parseEditorState: type "X" not found` | Feature not enabled | Enable in Payload config: `BlocksFeature({ blocks: [CodeBlock()] })`, `ChecklistFeature()`, `TableFeature()` |
| `Media upload failed` | File not found or rejected | Check image paths relative to markdown file |

## Examples

```bash
# Publish as draft (auto-generates missing SEO fields)
/myaidev-method:payloadcms-publisher article.md

# Publish immediately
/myaidev-method:payloadcms-publisher article.md --status published

# Different collection
/myaidev-method:payloadcms-publisher article.md --collection tutorials

# Update existing
/myaidev-method:payloadcms-publisher article.md --id 60d5ec49f8d2e

# Dry run
/myaidev-method:payloadcms-publisher article.md --dry-run
```
