> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stage.systems/llms.txt
> Use this file to discover all available pages before exploring further.

# Add a page

> Declare the page in the schema. It renders straight away, editors fill it in, and it ships when you remove the draft flag.

The schema says which pages exist and which sections each has. The database holds the content. A page renders from the schema before any content exists. The first edit creates its row.

Goal: `/careers/`, a hero and a list of open roles editors can add to. A draft until it is ready.

## 1. Declare the page

`src/lib/admin-schema.ts`:

```ts theme={null}
pages: {
  careers: {
    title: "Careers",
    draft: true,
    sections: {
      hero:  { type: "hero", props: { title: "Careers", body: "Come build with us.", image: "", imageVideo: "" } },
      roles: { type: "role-list", props: { heading: "Open roles", roles: [] } },
    },
  },
},
```

`hero` already exists in the template. `role-list` does not, so:

## 2. Build the component

`src/components/careers/RoleList.astro`:

```astro theme={null}
---
import { stageAttrs, stagePath, stageArray } from "../../lib/stage-anchors";

interface Role { title?: string; location?: string; href?: string }
interface Props {
  heading?: string;
  roles?: Role[];
  _stageId?: string;    // "roles", from the schema key
  _stageType?: string;  // "role-list"
}
const { heading, roles = [], _stageId, _stageType } = Astro.props;
---

<section class="mx-auto max-w-content px-6 py-24" {...stageAttrs(_stageId, _stageType)}>
  <h2 class="text-3xl text-foreground" {...stagePath("props.heading")}>{heading}</h2>
  <ul class="mt-10 flex flex-col divide-y divide-border" {...stageArray("props.roles", "Add role")}>
    {roles.map((r, i) => (
      <li class="flex items-center justify-between py-5">
        <div>
          <h3 class="text-lg text-foreground" {...stagePath(`props.roles.${i}.title`)}>{r.title}</h3>
          <p class="text-sm text-muted" {...stagePath(`props.roles.${i}.location`)}>{r.location}</p>
        </div>
        <a href={r.href} class="rounded-full border border-border px-4 py-2 text-sm">Apply</a>
      </li>
    ))}
  </ul>
</section>
```

## 3. Register it

`src/lib/components.ts`:

```ts theme={null}
import RoleList from "../components/careers/RoleList.astro";

const registry = {
  hero: Hero,
  "role-list": RoleList,
  // …
};
```

## 4. Make roles addable

`src/lib/admin-schema.ts`, under `components`:

```ts theme={null}
"role-list": {
  arrays: {
    "props.roles": {
      kind: "modal",
      addLabel: "Add role",
      title: "New role",
      noun: "role",
      reorderable: true,
      form: [
        { key: "title",    label: "Title",    type: "text", required: true, placeholder: "Senior engineer" },
        { key: "location", label: "Location", type: "text", placeholder: "Auckland or remote" },
        { key: "href",     label: "Apply link", type: "url" },
      ],
    },
  },
},
```

The `href` has no anchor on the page, so it is set in this form and edited from the item's cog.

## 5. Run dev

`pnpm dev`, open `/careers/`. The page renders from the schema. No row exists yet.

```html theme={null}
<main data-e-sections="">
  <section data-e-id="hero" data-e-type="hero">
    <h1 data-e-path="props.title">Careers</h1>
    <div data-e-path="props.body" data-e-rich="">Come build with us.</div>
  </section>
  <section data-e-id="roles" data-e-type="role-list">
    <h2 data-e-path="props.heading">Open roles</h2>
    <ul data-e-array="props.roles" data-e-add-label="Add role"></ul>
  </section>
</main>
```

## 6. Edit

Open `/admin`, go to Careers, press Edit. The bar reads "Draft page". Press the tab on the roles list, **Add role**, fill the form. Click the hero title and change it. Publish.

That first Publish creates the row:

```json theme={null}
{
  "slug": "careers",
  "title": "Careers",
  "version": 1,
  "components": [
    { "id": "hero", "type": "hero",
      "props": { "title": "Careers at Acme", "body": "Come build with us.", "image": "", "imageVideo": "" } },
    { "id": "roles", "type": "role-list",
      "props": { "heading": "Open roles", "roles": [
        { "id": "c_k2Xp9Qm1", "title": "Senior engineer", "location": "Auckland or remote", "href": "https://jobs.example.com/123" }
      ] } }
  ]
}
```

`hero` and `roles` are the schema keys. The role's id was generated when it was added.

## 7. Ship

Remove `draft: true`, merge. The next production build includes `/careers/`.

## 8. Add a section later

One line under `sections`:

```ts theme={null}
benefits: { type: "icon-grid", props: { title: "Why Acme", items: [] } },
```

`icon-grid` already exists, so nothing else to build. It renders on the next dev start. The row is untouched until someone edits the new section.

Remove a line and the section stops rendering. Its content stays in the row.

## The keys

| Key        | Does                                                                                                      |
| ---------- | --------------------------------------------------------------------------------------------------------- |
| `title`    | The page's title. Marks the entry as a page                                                               |
| `draft`    | Out of the production build. Remove to ship                                                               |
| `sections` | The page's sections, in order. The key is the component id. `props` fill a section until someone edits it |
| `palette`  | Optional. Section types editors may add from the tab on the right                                         |

Slugs may nest: `"help/faq"`. Keys ending in `*` are palettes for a family of pages, not pages.

## More examples

A fixed page with three sections and nothing addable:

```ts theme={null}
about: {
  title: "About",
  sections: {
    hero:    { type: "hero", props: { title: "About us", body: "", image: "", imageVideo: "" } },
    story:   { type: "prose", props: { body: "<p>Founded in 2019…</p>" } },
    team:    { type: "team-grid", props: { heading: "The team", members: [] } },
  },
},
```

A page editors compose. One fixed hero, then whatever they add:

```ts theme={null}
"landing/spring": {
  title: "Spring launch",
  draft: true,
  sections: {
    hero: { type: "hero" },
  },
  palette: [
    { type: "feature-grid", label: "Features",    starter: { heading: "", items: [] } },
    { type: "testimonial",  label: "Testimonial", starter: { quote: "", name: "" } },
    { type: "cta",          label: "Call to action", starter: { heading: "", button_label: "", button_href: "" } },
  ],
},
```

A family of pages with a shared palette:

```ts theme={null}
"help/*": {
  palette: [
    { type: "prose", label: "Text", starter: { body: "" } },
    { type: "faq",   label: "FAQ",  starter: { items: [] } },
  ],
},
"help/billing":  { title: "Billing",  sections: { hero: { type: "hero", props: { title: "Billing" } } } },
"help/shipping": { title: "Shipping", sections: { hero: { type: "hero", props: { title: "Shipping" } } } },
```

## Ids

The section key is the component id. The page template passes it to the renderer, the renderer puts it on its wrapper, and the editor saves under it. You never type an id anywhere else.

```
schema     team: { type: "team-grid" }
page       <Component _stageId="team" _stageType="team-grid" … />
html       <section data-e-id="team" data-e-type="team-grid">
row        { "id": "team", "type": "team-grid", "props": { … } }
```

Sections editors add get generated ids with a `c_` prefix.

Another page can point at a section by name:

```astro theme={null}
<span {...stageRef({ kind: "page", key: "about", id: "team", path: "props.heading" })}>{heading}</span>
```

## A page that already exists

Its sections have generated ids. Declare it by `title` only, or declare only the sections you are adding:

```ts theme={null}
about: {
  title: "About",                                   // existing sections untouched
  sections: {
    awards: { type: "logo-row", props: { title: "Awards", items: [] } },   // the new one
  },
},
```

Declaring an existing section under a new key adds an empty copy beside it.

## Drafts

| Where          | Shown |
| -------------- | ----- |
| Local dev      | Yes   |
| Preview deploy | Yes   |
| Production     | No    |
| `/admin`       | Yes   |

Public routes read `getSitePages()` from `lib/pages.ts`. The preview route reads `getPreviewPages()`. Both come with the template.

`admin`, `preview` and `api` cannot be pages.

## Next steps

<CardGroup cols={2}>
  <Card title="Add a component" href="/developers/recipes/add-a-component">
    A renderer, then one line in `sections`.
  </Card>

  <Card title="Page schemas" href="/developers/structure/schemas">
    Everything the schema can declare.
  </Card>
</CardGroup>
