# Make a site Orbitable — a guide for AI assistants

You're helping a developer set up their website so it can be edited in **Orbit MD**,
a desktop app that lets non-technical people edit a site's content through forms.
Follow this guide when someone asks you to "make my site Orbitable", "set my site up
for Orbit" or "configure Orbit".

Orbit's rule: **editors change the words and the data, never the design.** A site is
*Orbitable* when its words and data live in files in its git repository — Markdown
or MDX pages, JSON or YAML data — and its design lives in templates and components
that read those files. Orbit edits the files; it never touches code.

Your job is to produce an `orbit.config.yaml` at the root of the site and, where
needed, small changes that move words out of code into content files.

- Full documentation: https://orbitmarkdown.com/docs
- Settings reference: https://orbitmarkdown.com/docs/orbit-config
- Content map: https://orbitmarkdown.com/docs/content-map
- JSON Schema for `orbit.config.yaml`: https://orbitmarkdown.com/schema/orbit.config.json

---

## Ground rules

1. **Don't change the design.** Layout, styling, components and behaviour stay as
   they are. Moving words out of code is fine; redesigning is not.
2. **Never delete or rewrite content** beyond what the move needs. Keep every word,
   link and image the site has today.
3. **Small, reviewable changes.** Prefer a short `orbit.config.yaml` and a few
   targeted edits to a big refactor. If a change is large (a new content folder,
   replacing a CMS, rewriting routes), **describe it and ask first**.
4. **Keep the site building.** After each code change, run the site's build
   (`npm run build`, `hugo`, …) if you can, and fix what you broke.
5. **No secrets** in `orbit.config.yaml` or content — it's committed to git.
6. **Finish with a summary**: what editors can now change, what stays in code and
   why, anything you didn't do and how to do it later.

---

## Step 1 — Find the site and its generator

Work from the folder that holds the site (in a monorepo, the site's own folder —
`orbit.config.yaml` goes next to its `package.json`). Then identify the generator:

| Look for | Generator | Orbit needs |
|---|---|---|
| `astro` in `package.json` | Astro | Nothing — Orbit reads `content.config.ts`. `orbit.config.yaml` is optional, for rules. |
| `gatsby` in `package.json` | Gatsby | A content map |
| `next` in `package.json` | Next.js | A content map |
| `nuxt` in `package.json` | Nuxt (Nuxt Content, Docus) | A content map |
| `@11ty/eleventy` in `package.json` | Eleventy | A content map (not yet tested) |
| `hugo.toml` / `hugo.yaml` | Hugo | A content map (YAML front matter only — not yet tested) |
| `_config.yml` (Jekyll) | Jekyll | A content map (editing works; new posts need date-prefixed names Orbit doesn't add yet) |

If the content isn't in the repository at all — a headless CMS (Contentful, Sanity,
WordPress API…) or a database — Orbit can't edit it. Tell the developer; moving the
content into Markdown files is possible but is a bigger job to agree first.

## Step 2 — Find the content

Make three lists:

1. **Pages and posts** — folders of `.md` / `.mdx` files, and what renders them (a
   template, a route, a catch-all page).
2. **Data** — JSON or YAML files of words or settings (navigation, footer, site
   details, team, prices).
3. **Words in code** — headings, intros, button labels, site titles written inside
   components, pages or config (`gatsby-config.js` `siteMetadata`, a `.ts` data
   file, an `.astro`/`.tsx`/`.vue` page full of text).

Lists 1 and 2 become Orbit sections and data files. List 3 is where you'll move words
into content (Step 5).

## Step 3a — Astro sites

Orbit reads the site's content config (`src/content.config.ts`, or
`src/content/config.ts` on Astro 2–4) and turns each collection's Zod schema into a
form. You usually only need `orbit.config.yaml` for rules.

- Collections must use a `glob()` loader over Markdown/MDX (any folder) or
  `type: 'content'` in `src/content/<name>/`. A `file()` collection (one JSON/YAML
  file) is edited as data automatically.
- **Keep schemas inside `content.config.ts`** — Orbit evaluates that file but doesn't
  follow imports from your own modules, so an imported schema shows no fields. Small
  shared helpers defined in the same file are fine.
- A collection whose `pattern` names one file (`home.md`) is a fixed page — editable,
  no new pages.
- Markdown pages in `src/pages/` appear as a **site pages** section (new pages off
  until you allow them).
- Not editable: collections of separate JSON/YAML entry files, custom or remote
  loaders, live collections.

Astro guides: https://orbitmarkdown.com/docs/astro/where-to-put-content and
https://orbitmarkdown.com/docs/astro/editable-pages

## Step 3b — Every other generator: the content map

Add a `sections:` map to `orbit.config.yaml`. Each entry becomes a section in Orbit:

```yaml
# yaml-language-server: $schema=https://orbitmarkdown.com/schema/orbit.config.json
sections:
  posts:
    files: content/blog/*.md                 # the pages: a pattern starting in a folder
    template: src/templates/blog-post.js     # the file that renders them
  home:
    files: content/pages/home.md             # one file = a fixed page
```

- **`files`** — a glob from the site's root, starting in a folder (`*.md` at the root
  isn't allowed). `**` reaches sub-folders. One file = a fixed page.
- **`template`** — the file that turns each file into a page: a Gatsby template used
  in `createPages`, a Next.js route (`app/blog/[slug]/page.tsx`), a Nuxt catch-all
  page, a Hugo layout. **Editors can add pages only when this is set and the file
  exists.** Only name it if a new file in that folder really becomes a page — check
  the code (e.g. `generateStaticParams` reads every file in the folder, or
  `createPages` creates one per Markdown node). For Docus, whose catch-all page lives
  inside the theme, name `nuxt.config.ts`.
- **`newPage`** — where a new page goes, from the folder `files` starts in, with
  `{slug}` for its name. Default `{slug}` + the pages' extension. Use
  `"{slug}/index.md"` when each page is a folder with its images beside it (Gatsby
  page bundles, Hugo leaf bundles). For those pages Orbit's image picker shows the
  page's own images first and **uploads into the page's folder** (`./photo.jpg`), not
  into `assetsDir`.
- **`fields`** — optional. Leave it out and Orbit works the fields out from the pages
  (every field, all optional, dates kept as text). List them only to get required
  fields, dropdowns or date pickers:

  ```yaml
  fields:
    - { name: title, required: true }
    - { name: category, type: enum, options: [news, guides, releases] }
    - { name: date, type: date }
    - { name: cover, type: image }
    - { name: tags, type: array, items: { type: string } }
  ```

  Types: `string` (default), `number`, `boolean`, `date`, `enum` (+ `options`),
  `image`, `array` (+ `items`), `object` (+ `fields`). Or point at a JSON Schema
  file: `fields: schemas/post.schema.json`. When fields are listed, any other field
  a page has is shown locked and kept as written — list everything editors should
  change.

Generator notes:

- **Gatsby** — `files` is the folder `gatsby-source-filesystem` reads. Templates are
  in `src/templates/`. Images in `static/` are served from the root. Example:
  https://orbitmarkdown.com/docs/gatsby/setup
- **Next.js** — find where posts are read (`fs.readdirSync` of a folder). **Make sure
  frontmatter is parsed with a YAML parser** (`gray-matter`), not by splitting lines on
  `: ` — Orbit writes any valid YAML (quoted text, lists, groups). Images in `public/`.
  Example: https://orbitmarkdown.com/docs/nextjs/setup
- **Nuxt / Docus** — pages in `content/`. Use `content/*/*.md` to keep pages in
  section folders and map `content/index.md` as its own fixed page. Folder menus
  (`.navigation.yml`) can be listed as data. Component syntax (`::name`) is kept as
  written but editors see it as text. Example: https://orbitmarkdown.com/docs/nuxt/setup
- **Hugo** — `content/<section>/*.md`; front matter must be YAML (`---`), not TOML
  (`+++`) — say so if the site uses TOML. Images in `static/`.

## Step 4 — Data files

List JSON or YAML files editors may change; each gets a form:

```yaml
data:
  - path: src/data/nav.json
    label: Main navigation
  - path: content/site.json
    label: Site details
```

Only listed files are editable. Data written as code (`export const nav = [...]` in
`.ts`, `siteMetadata` in `gatsby-config.js`) isn't — move the values to a JSON file
and import it:

```js
// gatsby-config.js
module.exports = { siteMetadata: require("./content/site.json"), plugins: [/* … */] }
```

## Step 5 — Move words out of code (only where it helps)

For each page in list 3 that editors should change:

1. Create a content file for the words — a Markdown file with frontmatter fields
   (`content/home.md`: `heading`, `intro`, `buttonLabel`, …) or a JSON file.
2. Change the page to read it (a content query, `getEntry`, `fs` + `gray-matter`,
   a GraphQL query) and pass the same values to the same components. **Markup, class
   names and layout stay exactly as they were.**
3. Add the file to the map (a fixed-page section, or `data:`).
4. If page titles and descriptions for search and sharing are set in code, add an
   optional `seo` group to each collection's fields (`title`, `description`,
   `image`) and have the layout use it, falling back to the page's own title and
   intro — so editors can change how pages appear in search results.
5. Leave design-only text (aria labels, decorative symbols, developer messages) in
   code.

Do the obvious ones (home page heading and intro, site title and description, footer
text); list the rest for the developer rather than converting everything.

## Step 6 — Images, publishing and the rest (optional)

```yaml
assetsDir: public/images     # shared image library; inside public/ or static/ → /images/x.jpg
                             # (a page in its own folder keeps its uploads beside it)
site:
  title: Fernway Coffee      # the site's name in Orbit
  liveUrl: https://example.com           # Live ↗ buttons (ask the developer)
  previewUrl: https://preview.example.com  # Preview ↗ buttons, if there's a preview site
branches:                    # only if they want a preview site and approvals
  default: preview           #   saves go to the preview branch…
  live: main                 #   …publishing takes them live
collections:                 # rules per section
  posts:
    canAdd: true
    noAddTo: [archive]       # folders that never take new pages
    url: /blog/{path}        # each page's address: {slug} = its name, {path} = with sub-folders
snippets:
  compileOnSave: true        # [[name]] snippets, if the build doesn't expand them
```

Set `url` for each section whose addresses you can read from the routes (a
`[slug]` route under `blog/` → `/blog/{slug}`; a fixed page → its address, e.g.
`/about`). Leave it out when the address comes from a title or a date.

Ask before setting `branches` — it changes how publishing works for the whole team.
If the site is on GitHub, point them at
[Setting up your GitHub repository](https://orbitmarkdown.com/docs/github-setup/step-by-step):
protect the live branch, put approvers on its bypass list, and add the Orbit MD
GitHub App to the repository — editors then **Sign in with GitHub** in Orbit, and
their role (viewer, editor, approver) comes from GitHub. Nothing in the
repository sets roles.

## Step 7 — Check your work

1. Validate `orbit.config.yaml` against the schema (the `yaml-language-server`
   comment above makes editors like VS Code check it as you type).
2. Every `files` pattern matches the files you expect, and every `template` exists.
3. The site still builds.
4. Tell the developer to open the site in Orbit and look at **Site settings ▸ What
   Orbit edits**: it lists every section with its page count and whether editors
   can add pages, plus Markdown folders the map missed and fields left out of a
   section's list. Mistakes in `orbit.config.yaml` show at the top of Site settings
   with line numbers.

## A complete example (Gatsby)

```yaml
# yaml-language-server: $schema=https://orbitmarkdown.com/schema/orbit.config.json
site:
  title: Barcadia
assetsDir: content/pages/images
sections:
  news:
    files: content/news/*.md
    template: src/templates/post-template.js
  products:
    files: content/products/*/index.md
    newPage: "{slug}/index.md"
    template: src/templates/product-template.js
  pages:
    files: content/pages/*.md
    template: src/templates/page-template.js
    fields:
      - { name: title, required: true }
      - { name: headerImage, type: image }
      - { name: template, type: enum, options: [default, feed] }
  home:
    files: content/home.md
data:
  - path: content/site.json
    label: Site details
```
