Migrate from repo files
This guide is for your agent. It's the "match": turning the content that lives today in repo files (JSON, markdown with frontmatter, config) into Pluma content types and entries, without losing anything and without breaking the site.
1. Find the content
Search the site's repo, in this order:
| What to look for | Examples | In Pluma |
|---|---|---|
| Site config | site-config.js, config.json, _data/site.yml |
A singleton content type (site_settings) |
| Collections | src/content/blog/*.md, content/events/*.json |
One content type per collection |
| Frontmatter | title, date, slug, tags, image |
One field per key |
| Markdown body | Whatever comes after the frontmatter | A rich_text field |
| Images | public/images/…, src/assets/… |
Files, uploaded once and referenced |
2. Propose the model, and show it before creating anything
Put together the list of types with their fields and show it to the site owner before creating anything. If the owner already told you to go ahead and publish ("I trust you"), that's the approval: say what you're about to create and continue. Mapping rules:
- One-line text →
symbol. Long plain text →text. Markdown →rich_text. - Dates →
date. Numbers →integerornumber.true/false→boolean. - Lists of text (tags, categories) →
tags. - The URL identifier →
slug, required. - One image →
asset; several →assets. - Something that points to other content (author, category with its own page) →
reference, withlink_content_types. - If a key appears only in some files, the field is not required.
- Field names: the same as in the frontmatter, in camelCase. That keeps the change in the site's code to a minimum.
- The type's API ID: singular and in snake_case:
blog_post,event,site_settings(theblog/collection becomes theblog_posttype). - Rows of the same small shape (a menu, opening hours, social links) → a
listfield with its item fields, notjson: the owner edits them row by row. Keepjsonfor data nobody edits by hand. - Dates: send them as they are in the files. A date without a time (
2026-08-14) stays without a time; one with a time zone (-06:00) keeps the zone. Don't convert them to UTC. Within the same field, always use the same format. - Author without their own page: text (
symbol). With their own page: their own type and areference. - Links between pages inside markdown (
[our rye](/menu/rye-seeded)): rewrite them asentry:IDlinks once the target exists, so they don't break when a slug changes. - Menu and social links from the config: a
listfield inside the singleton. If each item needs its own page or photo, a type of its own (menu_item) with references is better. - Site config: a type with
"singleton": true; Pluma rejects a second entry of that type.
3. Create the types
With dry_run=true first, then for real. See Management API. Read your site's doc (GET /api/v1/spaces/:space_id/llms.txt) to confirm it came out the way you wanted.
4. Import
- Images first (
POST /assets), with a title and alt text. Keep the mappingfile path → asset id. - Then the entries, replacing image paths with asset ids: in
assetfields goes the id; inside markdown,. - References need the entry they point to to exist: import first what others reference (authors before posts).
- Use the batch for entries: up to 100 operations per request, all or nothing, and with
"$ref"a post points to the author created in the same batch. The pilot (73 entries, 66 images) took 212 one-at-a-time calls; with batches it's just a few. - Everything starts as a draft. Publish only after the owner has reviewed it.
5. Connect the site
- Create a
deliverykey (see Agents → Keys for the site) and ask the owner to store it in the host's environment variables. - Request the body with
rich_text=html(ormarkdown): images already come with their URL. If your framework renders markdown,markdownchanges less code. - In the site's code, change a single place (the one that reads the files) so it reads from the Delivery API, with the files as a fallback while the trial lasts. Recipes in Frameworks.
- Do it on a branch and open a PR. Never straight to main. If the site isn't in git, edit the file and describe the change in the report.
6. Report
Leave the owner a summary: types created, entries imported per type, files uploaded, and everything you couldn't map, with the file and the reason.