# SEO metadata

| Field | Type | Goes to |
|---|---|---|
| `metaTitle` | `string` | `<title>`. About 60 characters at most. |
| `metaDescription` | `string` | `<meta name="description">`. About 160 characters at most. |
| `canonicalPath` | `string` | `<link rel="canonical">`, resolved against your origin. Site-relative, under your base path (**Articles live under**), no trailing slash: `/blog/my-article`. |
| `keywords` | `string[]` | The queries the article targets, for your own reporting. |
| `openGraph` | `{ title, description, image?, type? }` | `og:` tags. `image` is the cover. |
| `author` | `{ name, role?, url?, sameAs? }?` | A byline. Present only when you name an author (**Settings** → **Site and URLs**); otherwise the byline is your brand. |
| `jsonLd` | `object` | One `<script type="application/ld+json">`, serialized as is. |
| `readingTimeMinutes` | `number?` | Derived from the blocks. |

## Structured data

`jsonLd` is one schema.org `Article`, built from the published blocks. It can also contain:

- an `FAQPage` under `hasPart`, when the article has an FAQ;
- a `HowTo` under `mainEntity`, when the article teaches a procedure in numbered steps;
- an `ItemList` under `mainEntity`, for any other numbered sequence of sections.

The [example article](/docs/api/example) shows the FAQ case. Don't add your own `Article` markup next to it.

Serialize it with `<` escaped, so no string in the data can close the script tag. The [`JsonLd` component](/docs/components) does exactly that:

```ts
JSON.stringify(article.seo.jsonLd).replace(/</g, "\\u003c");
```

## Canonical URLs

`canonicalPath` comes from your base path, set in Dashboard → your site → **Settings** → **Site and URLs** → **Articles live under**, and so does every link between articles. Set it before your first articles. A change applies to articles written or rewritten afterwards; existing articles keep their old path. Only `related` follows at once, because it's computed on each read.
