Something Else

cms-edit · HopSkipDrive

← All playbooks · Raw API

task-media-reuse-and-upload

# Capability: Media — reuse and upload

## Intent phrases

- upload image
- find existing asset
- add photo to page or article
- update person headshot / portrait

## Requires capabilities

`assetSearch` and `assetUpload`

## Prerequisites

- **Always run `index sync` first** if the catalog may be stale
- On **hosted MCP**, the server has **no filesystem** — use **staged upload** for all binary uploads (never `--base64` or a local file path in `args`)

## Reuse-first workflow (default)

1. `cms_edit ["index", "sync"]`
2. **Search existing assets:**
   - `cms_edit ["asset", "search", "--filename-match", "<partial-name>"]`
   - `cms_edit ["list", "--type", "media", "--asset-filename-match", "<partial>"]`
3. If match found — link by asset ID or filename in `set` / `create from-json` (`featuredImageAssetFilename`, `visualAssetFilename`, etc.)
4. For **`visuals`** (Media entry **array** on page/article): prefer an **existing Media entry** — `cms_edit ["list", "--type", "media", "--asset-id", "<assetId>"]` — then `set @root visuals <mediaEntryId> --links`. Do **not** `create media` or use `asset upload --with-media` when a wrapper already exists.
5. For **singular** component fields (e.g. `visual` on a component):  
   `cms_edit ["set", "@cN", "visual", "<mediaEntryId>", "--link"]`  
   **Never** `add media --parent @cN --target visual` — `add --target` only works for Entry-link **arrays** (`content`, `contents`, …). Bad `add --target` is rejected; it used to orphan Media drafts.
6. `create media` only when no Media entry wraps that asset (RTF embeds, or first article in a new composite batch — then **reuse that media ID** on sibling entries)
7. Bare Contentful entry IDs work with `set` (no `@` required) for mid-session Media field tweaks (width/autoplay/loop) after import — `set <mediaEntryId> width 75`.

## Upload only when no match

### Raster images and video (required)

Encode via **`task-media-pipeline-prepare`**. Intake can be:

1. A **Drive folder**, or
2. **Staged upload** (4 MB): `cms_edit_request_staged_upload` → curl → `cms_edit_media` prepare with `stagedUploadIds` (see returned `consumeArgs`), or
3. **httpsUrls** on pipeline-allowed hosts

Then wait → catalog → **import** with `overrides[].alt`. Do **not** `asset upload --staged/--base64` for jpeg/png/webp/gif/mp4. Do **not** `asset upload --url` for **video** (import only). Raster `--url` only for pipeline ready image hosts.

Animated GIFs become **MP4** (`gif-converted`) — attach as mute-loop Media / video visual, not `featuredImage`.

### SVG, PDF, Lottie

These may skip the pipeline. On hosted MCP use staged upload then `asset upload --staged`.

### Hosted MCP (SVG/PDF/Lottie only)

**Always use staged upload** for ad-hoc binaries. Do not pass base64 in `cms_edit` args — hosted MCP rejects `asset upload --base64`.

1. `cms_edit_request_staged_upload` with `fileName`, `mimeType`, optional `byteLength`, optional `title`
2. Run the returned `curlCommand` in a shell (POST file to `uploadUrl`)
3. Follow `consumeArgs`: **media prepare** for photos/video; **asset upload --staged** for SVG/PDF/Lottie

Rules:

- Staged upload keeps binary **out of MCP args**
- Hosted limit: **4 MB** per file; **1000 upload URLs per user per hour**; TTL **60 minutes**
- Raster/video staged files stay in Redis until pipeline fetches them (GET); they are not written to CMA
- Upload/import alone does **not** attach the asset — follow with **link + save** (below)
- Use `--if-exists-by-filename` (with `--file-name`) to avoid duplicate assets when re-running

After import, create a Media wrapper **only when none exists** for that asset:

```
cms_edit ["list", "--type", "media", "--asset-id", "<newPortraitAssetId>"]
cms_edit ["create", "media", "--asset-id", "<newPortraitAssetId>"]
```

**Media entry `name` (CMS list label):** defaults to the **linked asset title**. Only pass `--name` when you deliberately need a different label (e.g. two Media wrappers on one asset with distinct roles). Never use `visual-{assetId}` or `Wrapper for {assetId}` — those are migration leftovers.

To rename an existing Media wrapper: `cms_edit ["batch", "set", "<media-entry-id>:name=<asset-title>"]` then `batch save`.

Avoid `asset upload --with-media` when multiple entries will share the same image (e.g. news composites) — it creates duplicate Media entries. Upload the asset only, then one shared `create media`, then link the media ID on each entry.

**Prefer pipeline import** for encoded masters. **Video** must use import. `asset upload --url` is only for **pipeline ready images** (or SVG/PDF/Lottie). A random PNG or Contentful CDN master is rejected.

### Local CLI (only when user approves)

Hosted MCP uses `cms_edit_request_staged_upload` then **media prepare** for raster/video (or `asset upload --staged` for SVG/PDF/Lottie). Do not use the local `cms-edit` CLI for uploads.

## Link uploaded asset to an entry

Upload returns an **asset ID**. Attach it, then save:

**Person portrait (`media` is an Asset link):**

```
cms_edit ["open", "--id", "<person-entry-id>"]
cms_edit ["asset", "set", "@root", "media", "<new-asset-id>"]
cms_edit ["diff"]
cms_edit ["save"]
```

**Component `visual` (Media Entry link, singular):**

```
cms_edit ["set", "@cN", "visual", "<media-entry-id>", "--link"]
```

**Article featured image / asset fields:** `asset set <ref> <field> <asset-id>` (or `set … --asset`) when the field is an Asset link.

Always `diff` → `save`. Verify with `preview urls` on the public path.

## Confirmation gates

1. Report search results before uploading
2. Confirm upload filename/title with user if ambiguous
3. After upload, confirm `asset info <id>` shows expected width/height/size before linking

## Figma exports (photos as JPEG)

Do **not** upload PNGs exported from a Figma **frame** that includes transparent padding around the photo. That pad reads as a white border next to white cards (CareDrivers landscape bands).

1. Export the **image node**, not the wrapping frame; or trim to the opaque bounding box before import.
2. Photography should be **JPEG** (or WebP). Flatten remaining alpha onto white. PNG is only for graphics that need real transparency.
3. If an editor already uploaded a framed PNG: trim, convert to JPEG, staged-upload a replacement, and keep the same Media entry. Do not “fix” this with CSS crop on the site.

## Out of scope

- Publish assets (draft upload; human publishes in Contentful UI if needed)
- Deleting orphan assets from failed uploads (human cleanup in Contentful)

## Related resources

- `task-media-pipeline-prepare`

- `cms-edit://customer/defaults` (featured image rules)
- `cms-edit://customer/people` (person `media` field, when present)