# Body Block Shapes

`Body` is always `{ version: 1, content: BodyBlock[] }`.

Use builders for new content, but know the stored shapes when accepting JSON:

```ts
type Body = {
  version: 1;
  content: BodyBlock[];
};
```

## Block Type Names

- `text`: rich text paragraph, optional child blocks
- `heading_1`, `heading_2`, `heading_3`, `heading_4`: rich text headings
- `bulleted_list`: `items: ListItem[]`
- `numbered_list`: optional `start`, `items: ListItem[]`
- `toggle_list`: `items: ToggleListItem[]`
- `callout`: rich text, optional icon/color/backgroundColor/children
- `quote`: rich text, optional child blocks
- `table`: rows of cells, optional row/column header flags
- `divider`: no content payload
- `page_link`: `page` reference, optional title
- `image`: image source, optional caption and alt
- `gallery`: image collection, optional caption

## Rich Text

```ts
type BodyRichText =
  | { type: "text"; text: string; annotations?: TextAnnotations; link?: TextLink }
  | { type: "line_break" };
```

`text()` creates text inlines. `lineBreak()` creates line breaks. Builders that
accept rich text inputs convert string arguments into text inlines.

## Nested Content

Only these containers can hold child blocks:

- `text.children`
- `callout.children`
- `quote.children`
- `ListItem.children`
- `ToggleListItem.children`

Tables contain rows and cells, not child blocks. Galleries contain images, not
child blocks.

## Page and Image References

```ts
type PageReference = { type: "page_id"; pageId: string } | { type: "url"; url: string };

type ImageSource =
  | { type: "external"; url: string }
  | { type: "file"; url: string; expiryTime?: string };
```

`external` image URLs should be absolute `http` or `https` URLs when
`lintBody` URL validation is enabled. Local upload paths belong to `file`
image sources.
