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

# Editing pages with JSON Patch

> Apply RFC 6902 operations to a page document — replace a headline, add a block, remove a section.

export const APIBadge = ({method = "GET", path, scope}) => {
  const m = method.toUpperCase();
  return <span className="gv-api-badge">
      <span className={`gv-api-badge__method gv-api-badge__method--${m.toLowerCase()}`}>
        {m}
      </span>
      <code className="gv-api-badge__path">{path}</code>
      {scope && <span className="gv-api-badge__scope">
          <span className="gv-api-badge__scope-label">scope</span>
          <code>{scope}</code>
        </span>}
    </span>;
};

export const Def = ({term, type, defaultValue, required, children}) => <>
    <dt className="gv-def-list__term">
      <span className="gv-def-list__name">{term}</span>
      {type && <span className="gv-def-list__type">{type}</span>}
      {required && <span className="gv-def-list__required">required</span>}
      {defaultValue !== undefined && <span className="gv-def-list__default">
          default <code>{defaultValue}</code>
        </span>}
    </dt>
    <dd className="gv-def-list__description">{children}</dd>
  </>;

export const DefList = ({children}) => <dl className="gv-def-list">{children}</dl>;

export const BrandCard = ({eyebrow, title, href, icon, badge, cta, featured = false, horizontal = false, children}) => {
  const classes = ["gv-card", featured && "gv-card--featured", horizontal && "gv-card--horizontal"].filter(Boolean).join(" ");
  const inner = <>
      {icon && <span className="gv-card__icon" aria-hidden="true">
          <Icon icon={icon} />
        </span>}
      <div className="gv-card__main">
        {(eyebrow || badge) && <div className="gv-card__meta">
            {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
            {badge && <span className="gv-card__badge">{badge}</span>}
          </div>}
        <h3 className="gv-card__title">{title}</h3>
        {children && <div className="gv-card__body">{children}</div>}
        {cta && <span className="gv-card__cta">
            {cta}
            <svg className="gv-card__cta-arrow" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <line x1="7" y1="17" x2="17" y2="7" />
              <polyline points="7 7 17 7 17 17" />
            </svg>
          </span>}
      </div>
    </>;
  return href ? <a className={classes} href={href}>
      {inner}
    </a> : <div className={classes}>{inner}</div>;
};

export const FeatureTable = ({columns, rows, featuredRow, caption, variant}) => <div className={`gv-feature-table ${variant ? `gv-feature-table--${variant}` : ""}`}>
    <table>
      {caption && <caption className="gv-feature-table__caption">{caption}</caption>}
      <thead>
        <tr>
          {columns.map(col => <th key={col.key} scope="col">
              <span className="gv-feature-table__header">{col.label}</span>
            </th>)}
        </tr>
      </thead>
      <tbody>
        {rows.map((row, i) => {
  const rowClass = [i === featuredRow ? "gv-feature-table__row--featured" : "", row.chosen ? "gv-feature-table__row--chosen" : ""].filter(Boolean).join(" ");
  return <tr key={i} className={rowClass}>
              {columns.map(col => {
    const v = row[col.key];
    if (variant === "tradeoff" && Array.isArray(v)) {
      const pill = col.key === "pros" ? "gv-feature-table__pill--pro" : col.key === "cons" ? "gv-feature-table__pill--con" : "";
      return <td key={col.key}>
                      {v.map((item, j) => <span key={j} className={`gv-feature-table__pill ${pill}`}>{item}</span>)}
                    </td>;
    }
    return <td key={col.key}>{v}</td>;
  })}
            </tr>;
})}
      </tbody>
    </table>
  </div>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="EDITING" />

Every edit to a page document is expressed as a JSON Patch — an array of operations following RFC 6902. Each operation names an `op`, a `path` into the document, and a `value` or `from`. You send the array to <APIBadge method="PATCH" path="/v1/workspaces/:slug/pages/:id" scope="pages:write" /> (or call `gv.pages.patch(...)` from the SDK). By the end of this page you will know how to replace a headline, insert a block, remove a section, and guard against concurrent edits.

## A working patch

Replacing the headline on the first child of the page's NavShell:

```bash patch.sh theme={null}
curl -X PATCH https://api.gavai.app/v1/workspaces/acme/pages/page_01JABCDEFGHIJKLMNOPQRSTUV \
  -H "Authorization: Bearer gak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "operations": [
      {
        "op": "replace",
        "path": "/tree/children/0/props/headline",
        "value": "Welcome to Acme"
      }
    ]
  }'
```

That is the whole edit. The patch is applied to the **draft** — the live page does not change until you call `POST /v1/workspaces/{slug}/pages-publish`. You can chain a series of patches, preview the draft in the Builder, and publish only when satisfied.

## Reading a path

The `path` field is an [RFC 6901 JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901) into the document tree. It is a slash-separated string. Each segment is either a JSON object key or an array index.

```text path-anatomy.txt theme={null}
/tree/children/0/props/headline
│    │        │ │     │
│    │        │ │     └── property name on the block's props object
│    │        │ └──────── the props key
│    │        └────────── array index of the first child
│    └──────────────────── children array on the root block
└───────────────────────── the document's tree key
```

So `/tree/children/0/props/headline` reads as "the `headline` property of the props of the first child of the page tree." Indexing starts at zero.

One special token: `-` as the final array segment means "append." Use it with `add` to push a new element to the end of an array without computing its index.

## Operations

<DefList>
  <Def term="add" type="op">Insert a value at the given path. Use `"-"` as the final path token to append to an array.</Def>
  <Def term="remove" type="op">Delete the value at the given path.</Def>
  <Def term="replace" type="op">Set a new value at an existing path. The path must already exist.</Def>
  <Def term="move" type="op">Remove the value at `from` and insert it at `path`.</Def>
  <Def term="copy" type="op">Copy the value at `from` to `path` without removing the source.</Def>
  <Def term="test" type="op">Assert that the value at `path` equals the given value. The whole patch is rejected if any `test` fails.</Def>
</DefList>

## Common patches

### Replace a headline

Target the `props` of the block you want to update. The path mirrors the document tree.

```json replace-headline.json theme={null}
[
  {
    "op": "replace",
    "path": "/tree/children/0/props/headline",
    "value": "Welcome to Acme"
  }
]
```

`/tree/children/0` addresses the first child of the NavShell — typically the Hero. Adjust the index for whichever block you are targeting.

### Append a block

Use `add` with `"-"` to append to the `children` array without computing the next index.

```json append-cta.json theme={null}
[
  {
    "op": "add",
    "path": "/tree/children/-",
    "value": {
      "block": "Cta",
      "props": {
        "headline": "Ready to start?",
        "cta_label": "Get started",
        "cta_href": "/signup",
        "variant": "centered"
      }
    }
  }
]
```

To insert at a specific position instead of appending, use a numeric index — `"/tree/children/2"` inserts at position 2 and shifts existing siblings right.

### Remove a block

`remove` deletes the value at the path. For a block, it deletes the entire subtree.

```json remove-block.json theme={null}
[
  {
    "op": "remove",
    "path": "/tree/children/1"
  }
]
```

After a `remove`, the remaining siblings shift down to fill the gap. If your next operation targets a sibling by index, recalculate the index — what was at `/tree/children/2` is now at `/tree/children/1`.

### Guard against concurrent edits

Lead with a `test` operation that asserts the current value. If another writer has changed the document since you fetched it, your `test` fails and the entire patch is rejected — the document stays unchanged.

```json guarded-patch.json theme={null}
[
  { "op": "test",    "path": "/tree/children/0/props/headline", "value": "Welcome to Acme" },
  { "op": "replace", "path": "/tree/children/0/props/headline", "value": "Welcome back" }
]
```

This is the safest way to apply an edit when multiple agents or humans may be patching the same page.

## Applying patches

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl -X PATCH https://api.gavai.app/v1/workspaces/acme/pages/page_01JABCDEFGHIJKLMNOPQRSTUV \
      -H "Authorization: Bearer gak_test_..." \
      -H "Content-Type: application/json" \
      -d '{
        "operations": [
          {
            "op": "replace",
            "path": "/tree/children/0/props/headline",
            "value": "Welcome to Acme"
          }
        ]
      }'
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    import { Gavai } from "@gavai/sdk";

    const gv = new Gavai({ apiKey: process.env.GAVAI_API_KEY });

    await gv.pages.patch({
      workspace: "acme",
      id: "page_01JABCDEFGHIJKLMNOPQRSTUV",
      operations: [
        {
          op: "replace",
          path: "/tree/children/0/props/headline",
          value: "Welcome to Acme"
        }
      ]
    });
    ```
  </Tab>
</Tabs>

## Draft vs. live

`PATCH` writes to the draft. Live visitors do not see the change until you publish. This is deliberate — it lets you batch edits, preview in the Builder, and publish atomically. The publish call creates a version snapshot, so any prior live version remains reachable for rollback.

## Where to go next

<Columns cols={2}>
  <BrandCard eyebrow="API" title="API reference → Pages" href="/api-reference/resources/pages">
    Full endpoint details for listing, fetching, patching, publishing, and rolling back pages.
  </BrandCard>

  <BrandCard eyebrow="THEME" title="Theme" href="/pageblocks/theme">
    The workspace theme uses the same patch model — change a token, every page picks it up.
  </BrandCard>
</Columns>
