diff --git a/content/guides/01.data-model/3.interfaces.md b/content/guides/01.data-model/3.interfaces.md
index a32f7d9e..dc17218c 100644
--- a/content/guides/01.data-model/3.interfaces.md
+++ b/content/guides/01.data-model/3.interfaces.md
@@ -96,13 +96,17 @@ The What You See Is What You Get (WYSIWYG) editor provides a text area with rich
| Folder | Default folder to store uploaded files. Does not affect existing files. |
| Soft Limit | Used to limit the number of characters within the Data Studio. |
| Static Access Token | Token appended to asset URLs when displaying in the editor. |
-| Custom Formats | JSON array of custom formatting styles to add to the editor's formatting menu. Only inline formats are supported; block, selector, and wrapper formats are ignored. |
+| Custom Formats | JSON array of custom formatting entries added to the editor's Formats menu. Supports inline, block, selector, and grouped entries. `wrapper` entries are not supported. See [Rich Text](/guides/data-model/rich-text#custom-formats). |
| Options Override | Deprecated and no longer applied. See the note below. |
::callout{icon="i-lucide-info"}
**The editor's engine changed in Directus 12.** The WYSIWYG interface is now built on [Tiptap](https://tiptap.dev). Existing fields keep working without migration, but stored HTML is normalized to the editor's supported markup on first edit, and the **Options Override** (`tinymceOverrides`) option is deprecated. See the [Version 12 breaking changes](/releases/breaking-changes/version-12#wysiwyg-editor-rebuilt-on-tiptap) for details.
::
+::callout{icon="i-lucide-book-open" color="primary" to="/guides/data-model/rich-text"}
+Configure custom formats, understand what the editor stores, and style the output on your frontend in the [Rich Text](/guides/data-model/rich-text) guide.
+::
+
### Markdown

diff --git a/content/guides/01.data-model/4.rich-text.md b/content/guides/01.data-model/4.rich-text.md
new file mode 100644
index 00000000..7700b26e
--- /dev/null
+++ b/content/guides/01.data-model/4.rich-text.md
@@ -0,0 +1,307 @@
+---
+stableId: 2947492c-9b05-4261-b4dd-28ba4b0ceaba
+title: Rich Text
+description: Configure custom formats for the WYSIWYG interface, understand what the editor stores, and style the resulting HTML in your app's frontend CSS.
+---
+
+The [WYSIWYG interface](/guides/data-model/interfaces#wysiwyg) stores its value as HTML. This page covers the **Custom Formats** option, which adds your own formatting entries to the editor.
+
+::callout{icon="i-lucide-info"}
+The editor is built on [Tiptap](https://tiptap.dev) as of Directus 12. It stores only the markup its schema models. See [Version 12 breaking changes](/releases/breaking-changes/version-12#wysiwyg-editor-rebuilt-on-tiptap) for the supported HTML and how existing values are normalized.
+::
+
+## Custom Formats
+
+**Custom Formats** takes a JSON array of formatting entries. Set it on the field's interface options. When the array is not empty, a **Formats** dropdown is added to the editor toolbar. There is no toolbar option to enable it.
+
+The format is a subset of TinyMCE's `style_formats`, so existing configurations often carry over unchanged.
+
+Every entry needs a `title` and one of the following keys:
+
+| Key | Applies to |
+| ---------- | --------------------------------------------------------------------------------------------- |
+| `inline` | A tag wrapped around the selected text, such as `span`. |
+| `block` | A single block tag. Converts the block when its tag differs. See [Block formats](#block-formats). |
+| `selector` | One or more comma-separated block tags. Never converts the block. |
+| `items` | An array of entries, grouped into a submenu. See [Grouped formats](#grouped-formats). |
+
+An `inline`, `block`, or `selector` entry also needs at least one of `classes` or `attributes`, and can set both. The editor uses them to recognize the format again when the value is reloaded, so an entry carrying neither is skipped. An `items` group is a container and needs neither.
+
+`styles` is the only optional key.
+
+### Example
+
+```json
+[
+ { "title": "Lead paragraph", "block": "p", "classes": "lead" },
+ { "title": "Subheading", "selector": "h2,h3", "classes": "subheading" },
+ {
+ "title": "Callouts",
+ "items": [
+ { "title": "Info", "block": "p", "classes": "callout callout-info" },
+ { "title": "Warning", "block": "p", "classes": "callout callout-warning" }
+ ]
+ },
+ {
+ "title": "Highlight",
+ "inline": "span",
+ "classes": "highlight",
+ "styles": { "background": "#ffff00" }
+ }
+]
+```
+
+Applying **Lead paragraph** to a paragraph, then **Highlight** to the word `lead` inside it, turns:
+
+```html
+
A lead paragraph.
+```
+
+into:
+
+```html
+
A lead paragraph.
+```
+
+Define `lead`, `subheading`, `callout`, and `highlight` in your frontend's CSS. Directus stores the classes but does not style them. See [Styling the output on your frontend](#styling-the-output-on-your-frontend).
+
+### Inline formats
+
+An `inline` entry renders a tag around the selection, carrying its `classes`, its `attributes`, and its `styles` serialized into a `style` attribute:
+
+```json
+{
+ "title": "Highlight",
+ "inline": "span",
+ "classes": "highlight",
+ "styles": { "background": "#ffff00" },
+ "attributes": { "title": "Highlighted text" }
+}
+```
+
+Selecting the word `text` and applying the format turns:
+
+```html
+
Some text.
+```
+
+into:
+
+```html
+
Some text.
+```
+
+Inline entries are the only formats that write a `style` attribute into the content, so they are also the only ones with a visible effect inside the editor. The trade-off is an inline style in your stored HTML, which is harder to override from your frontend CSS.
+
+### Block formats
+
+A `block` entry applies to one block tag and takes a single tag only. Use `selector` for a list.
+
+```json
+{ "title": "Lead paragraph", "block": "p", "classes": "lead" }
+```
+
+When the selected block's tag differs from the entry's tag, the block is converted. **Conversion works between paragraphs and headings only**, because those are the two block types that hold the same content.
+
+With `{ "title": "Subheading", "block": "h2", "classes": "subheading" }`, applying the format to a paragraph turns:
+
+```html
+
Section title
+```
+
+into:
+
+```html
+
Section title
+```
+
+A `block: 'p'` entry converts the other way, turning a heading back into a paragraph.
+
+Every other tag applies without converting. With `{ "title": "Pull quote", "block": "blockquote", "classes": "pull-quote" }`, applying the format to a block that is already a `
` turns:
+
+```html
+
A quote.
+```
+
+into:
+
+```html
+
A quote.
+```
+
+Applying it to a paragraph leaves the paragraph untouched, and the editor logs a warning in the browser console when it loads the format. Use `selector` when you never want conversion.
+
+### Selector formats
+
+A `selector` entry applies to the block tags you list and never changes the block's type:
+
+```json
+{ "title": "Subheading", "selector": "h2,h3", "classes": "subheading" }
+```
+
+Applying it to an `
` or `
` adds the class, turning:
+
+```html
+
Section title
+```
+
+into:
+
+```html
+
Section title
+```
+
+Applying it to a paragraph does nothing, because `p` is not in the entry's list:
+
+```html
+
Section title
+```
+
+stays:
+
+```html
+
Section title
+```
+
+### Grouped formats
+
+An `items` array groups entries into a submenu in the **Formats** dropdown:
+
+```json
+{
+ "title": "Callouts",
+ "items": [
+ { "title": "Info", "block": "p", "classes": "callout callout-info" },
+ { "title": "Warning", "block": "p", "classes": "callout callout-warning" }
+ ]
+}
+```
+
+Groups support one level. A group nested inside another group is skipped, and a group whose entries are all invalid is dropped along with them.
+
+### Which tags a block format can target
+
+`block` and `selector` accept only tags the editor models as a node:
+
+`p`, `h1` through `h6`, `pre`, `blockquote`, `section`, `article`, `figure`, `figcaption`, `details`, `summary`, `dl`, `dt`, `dd`, `hr`, `img`, `ul`, `ol`, `li`, `table`, `tr`, `td`, `th`, `video`, `audio`, `iframe`.
+
+`div` and `span` are not on the list, so they cannot carry a block format. A `block: 'div'` entry is skipped. This matters when you migrate a TinyMCE configuration, since TinyMCE formats commonly wrap content in a `div`. It is the same reason a stored `
` is unwrapped when the editor loads it. Use a `section`, `article`, or `figure` entry instead, or move the wrapper into your frontend template.
+
+A `selector` entry drops the tags the editor does not model and keeps the rest, so `selector: 'h2,div,p'` still applies to `
` and `
`. The entry is skipped only when none of its tags are modelled.
+
+`selector` takes plain tag names. Compound CSS selectors such as `p.lead`, `div > p`, and `#main` are skipped rather than silently matching nothing.
+
+### Invalid entries are skipped
+
+An entry the editor cannot build is skipped, and the rest of the array still loads. Every skipped entry logs a warning to the **browser console**. Nothing appears in the interface, so check the console when a format does not show up in the dropdown.
+
+Entries are skipped when they:
+
+- Have no `title`.
+- Have none of `inline`, `block`, `selector`, or `items`.
+- Have no `classes` and no `attributes`, unless the entry is an `items` group.
+- Use `wrapper`, which is not supported.
+- Name only tags the editor does not model, such as `block: 'div'` or `selector: 'div,span'`.
+- Pass a compound CSS selector such as `p.lead` or `#main` instead of a plain tag name.
+- Pass a comma-separated list to `block` instead of `selector`.
+- Nest a group inside another group.
+
+## Gotchas
+
+### The editor does not load your frontend CSS
+
+::callout{icon="i-lucide-triangle-alert" color="warning"}
+The editor is not a preview of your site. Classes applied through custom formats have no styling attached in the Data Studio. The exception is [inline formats](#inline-formats), which write a `style` attribute into the content and do render in the editor.
+::
+
+Directus loads no project stylesheet into the editing surface, so a class like `lead`, `float-left`, or `text-center` has no visual effect inside the WYSIWYG. The class is stored and round-trips correctly. It just has nothing styling it in the Data Studio.
+
+This surprises authors, who apply a format, see no change, and assume it is broken. Point them at the **Formats** dropdown instead: it shows the active format in its label and highlights the matching row. That is the confirmation that a format applied, not the rendered content.
+
+The editor's own content styles can also contradict your class. The editor styles `figure` as `display: table; margin: 0.8125rem auto`, so a captioned image with a `float-left` class renders centered in the editor and floated on your frontend. The same applies to anything depending on `display`, `float`, `width`, or a flex or grid context your frontend provides.
+
+There is currently no option to inject custom CSS into the editor. The deprecated **Options Override** (`tinymceOverrides`) option is no longer applied, so TinyMCE's `content_css` has no equivalent.
+
+Because of this, layout classes are the riskiest thing to hand to non-technical authors: no feedback in the editor, and a result that depends entirely on CSS the editor cannot see. Prefer semantic formats such as `lead`, `callout`, or `subheading` over layout ones where you can.
+
+### `styles` on a block entry only styles the dropdown
+
+On an `inline` entry, `styles` is written into the content as a `style` attribute. On a `block` or `selector` entry, `styles` only styles the entry's label inside the **Formats** dropdown. It is not written to the content.
+
+The editor does not round-trip `style` on block nodes. Only `class`, `id`, `title`, `role`, `lang`, `dir`, `data-*`, and `aria-*` survive a save and load cycle. TinyMCE applied block `styles` to the block itself, so this is a behavior change to account for when migrating.
+
+Use `classes` for block styling and define the CSS on your frontend.
+
+### Attributes on a block entry are filtered
+
+`attributes` on a `block` or `selector` entry accepts only `id`, `title`, `role`, `lang`, `dir`, `data-*`, and `aria-*`. Anything else, including `style` and `target`, is dropped with a console warning.
+
+`attributes: { "class": "…" }` is merged into `classes`, so toggling the format off removes those classes too.
+
+Inline entries do not filter `attributes`.
+
+## Toggling behavior
+
+Selecting an active format in the dropdown removes it. Applying and removing a block format behaves as follows:
+
+- Removing a format strips only the classes and attributes that format configured. Unrelated classes, `id`, `data-*`, and `aria-*` on the same block survive.
+- The block keeps its tag. A `block: 'h2'` format does not turn the heading back into a paragraph when you remove it.
+- A format applies to every eligible block in the selection as a single undo step.
+- A format counts as active when every eligible block in the selection carries all of its classes, or all of its attributes for a format anchored on attributes alone.
+
+## Image captions
+
+Adding or editing an image opens a drawer with a **Caption** field. A non-empty caption wraps the image in a ``:
+
+```html
+
+
+ A wind turbine at dusk.
+
+```
+
+Style `figure` and `figcaption` on your frontend to match. The editor centers them, which your own CSS will override.
+
+Behavior worth knowing:
+
+- Clearing the caption reverts to a bare ``, but only when the `` holds nothing but images and carries no attributes of its own. A `` you added deliberately is kept, and its caption is removed on its own.
+- Editing an existing image updates it in place, so its attributes and its surrounding `` survive the edit.
+- Pressing `Enter` inside a caption leaves the figure and starts a new paragraph after it, rather than adding a second ``.
+- Pressing `Backspace` in an empty caption removes the caption.
+- Deleting the image out of a figure removes the orphaned caption too.
+- Stored `` and `` markup round-trips, including a caption placed before the image and a figure holding only a caption.
+
+## Styling the output on your frontend
+
+Directus stores the HTML. Rendering and styling it is your frontend's job.
+
+Add the classes your formats configure to your stylesheet:
+
+```css
+.lead {
+ font-size: 1.25rem;
+ line-height: 1.6;
+}
+
+.subheading {
+ color: #6644ff;
+ text-transform: uppercase;
+}
+
+.callout {
+ border-left: 4px solid;
+ padding: 1rem;
+}
+
+.callout-info { border-color: #3399ff; }
+.callout-warning { border-color: #ffaa00; }
+```
+
+Two things to keep in mind:
+
+- Scope these styles so they do not collide with the rest of your site. Rendering the value inside a wrapper such as `.prose` and prefixing your selectors keeps author-applied classes from leaking.
+- Review your selectors before upgrading to Directus 12 if you style stored HTML by tag, class, or attribute. Markup the editor does not model, such as `
` wrappers, is removed when a field is edited and saved.
+
+## Next Steps
+
+Read about the [WYSIWYG interface options](/guides/data-model/interfaces#wysiwyg), the [supported HTML and normalization behavior](/releases/breaking-changes/version-12#wysiwyg-editor-rebuilt-on-tiptap), and [keyboard shortcuts](/getting-started/accessibility) for the editor.
diff --git a/content/guides/01.data-model/4.relationships.md b/content/guides/01.data-model/5.relationships.md
similarity index 100%
rename from content/guides/01.data-model/4.relationships.md
rename to content/guides/01.data-model/5.relationships.md