# RelationMap JSON format (data_json v1.2)

> The JSON a RelationMap chart is saved and opened as. Required and optional fields, defaults, relationship color keywords, groups, tags and custom color rules, with a minimal and a full example.

A RelationMap chart is one JSON object. The same format is used everywhere a chart goes in or out of the editor:

- **Export → JSON Data** downloads it. JSON export is free on every plan.
- **Open**, or dragging a `.json` file onto the canvas, opens it.
- The paste box at [/edit/?paste=1](https://relationmap.io/edit/?paste=1) opens it when you paste JSON instead of a [cast list](https://relationmap.io/docs/cast-list/).
- A [`#data=` link](https://relationmap.io/docs/ai/#3-make-the-link-in-code) opens it.
- Charts saved to the cloud are stored in it (the `data_json` column).

The current version is `"1.2"`.

## Minimal example

Only `nodes` is required. This opens a chart with two people and one relationship:

```json
{
  "nodes": [
    { "id": "kai", "name": "Kai" },
    { "id": "mira", "name": "Mira" }
  ],
  "edges": [
    { "id": "e1", "source": "kai", "target": "mira", "label": "mentor" }
  ]
}
```

## Top level

| Key | Required | What it is |
| --- | --- | --- |
| `nodes` | yes | The characters. A list with at least one entry. |
| `edges` | no | The relationships between them. |
| `meta` | no | Title, subtitle, lore, cover and display settings. |
| `groups` | no | Groups a character can belong to, drawn as a tinted area around the members. |
| `tags` | no | Labels shown as small colored dots under a character. |
| `customColorRules` | no | Your own relationship words and their colors. |

A file that is not a JSON object, or whose `nodes` list is missing or empty, does not open. Keys not listed on this page are dropped the next time the editor saves the chart.

## nodes

| Key | Required | Default | What it is |
| --- | --- | --- | --- |
| `id` | recommended | `"n0"`, `"n1"`, … by position | Unique id. Numbers are turned into strings. If two nodes share an id, only the first one is kept. |
| `name` | no | `"Untitled"` | The name under the circle. |
| `subtitle` | no | none | A short line under the name, such as a role. It is drawn on one line in small type and is never wrapped or cut, so a long one runs into the characters beside it. Keep it to about 30 characters or fewer (30 characters are about 140 px wide) and put longer text in `description`. |
| `description` | no | none | Longer notes. Line breaks are kept. Not drawn on the chart: it shows in the editor's character panel when you click the character. |
| `groups` | no | `[]` | Ids from `groups`. |
| `tags` | no | `[]` | Ids from `tags`. |
| `color` | no | from a 16-color palette, by position | Color of the circle when there is no picture, as `"#RRGGBB"`. |
| `avatar` | no | none | Picture: an image URL or a `data:` URI. |
| `radius` | no | `meta.settings.nodeRadius` | Size of this one circle, in pixels, 12 to 60. |
| `position` | no | none | `{ "x": 0, "y": 0 }`. Usually leave it out: the editor then lays the chart out itself and zooms to show all of it. Give it when the layout matters, as in a family tree; see [Positions](#positions). |

An older field, `bio`, is read as `subtitle` when `subtitle` is missing.

### Positions

Leave positions out unless the layout carries meaning, such as generations in a family tree or ranks in a chain of command. If you give them:

- Give a position to every node. A node without one is placed by the automatic layout around the fixed ones, ignores your rows, and can end up off screen.
- Units are pixels at 100% zoom: `x` grows to the right and `y` grows downward. Any origin works. When the chart opens and part of it is off screen, the editor centers it, zooming out as far as 20% if it has to, but never in past 100%.
- At the default size a circle is 60 px across, and with its name and subtitle a character is about 90 px tall. Rows about 200 px apart, and characters at least 150 px apart in a row, leave room for names up to about 25 characters, subtitles up to about 30, and the labels on the lines. Space them wider for longer names.
- Characters with a position stay where you put them: the automatic layout doesn't move them. You can still drag them in the editor.

## edges

| Key | Required | Default | What it is |
| --- | --- | --- | --- |
| `id` | recommended | `"e0"`, `"e1"`, … by position | Unique id. |
| `source` | yes | | The `id` of a node. Not a name. |
| `target` | yes | | The `id` of a node. Not a name. |
| `label` | no | `""` | The relationship, read as "source is target's label": `kai → mira`, `"mentor"` means Kai is Mira's mentor. The label also picks the line color, see below. |
| `bidirectional` | no | `false` | `true` draws an arrow at both ends of the line; without it there is one arrow, pointing at the target. Arrows come only from this field, never from the label, so use it with labels that read the same both ways, like "sisters" or "friends". |
| `color` | no | from the label | `"#RRGGBB"`. Overrides the color the label would give. |

An edge whose `source` or `target` doesn't match any node `id` is skipped. The editor then says "Opened, but skipped 1 relationship(s) pointing to missing characters."

Two characters can have more than one relationship. Every edge is kept and drawn; nothing is merged and no warning is shown. But the lines lie on top of each other or nearly so: in the editor, two edges in the same direction are drawn exactly on top of each other, and two in opposite directions about 15 px apart, with their labels almost touching. The shared chart page and embeds also draw them close together. When two characters have two relationships, use one edge whose label says both, like "husband and cousin".

### Family trees

These conventions keep a family tree readable:

- **Parent and child:** one edge from each parent to the child, labeled "father" or "mother". `mr-bennet → jane`, `"father"` reads "Mr. Bennet is Jane's father". A child with both parents in the chart gets two edges.
- **Marriage:** one edge per marriage, from husband to wife, labeled "husband" (or from wife to husband, labeled "wife"). Leave `bidirectional` off: with an arrow at both ends, "husband" would read both ways. For a two-way line, use a label that reads both ways, like "married", and give it a color yourself, since it is gray by default.
- **Siblings:** shared parents already show them. Add a "sisters" or "brothers" edge with `"bidirectional": true` only when their parents aren't in the chart.
- **Layout:** give every character a [position](#positions), one generation per row, spouses side by side.
- **Size:** each child brings two edges, so a family tree often has 1.5 to 2 times as many relationships as people. Split a big family into branches.

The colors of family words are under [Words for family trees](#words-for-family-trees). A complete family tree to copy is in [Make a chart with AI](https://relationmap.io/docs/ai/#family-trees).

## groups and tags

Both are lists of the same shape:

| Key | Required | What it is |
| --- | --- | --- |
| `id` | yes | Referenced from a node's `groups` or `tags`. |
| `name` | yes | Shown in the legend; a group's name is also written on the canvas. |
| `color` | recommended | `"#RRGGBB"`. Without it the group area and the legend dot have no color. |
| `note` | no | What the group or tag means. |

Entries without an `id` or a `name` are skipped. A character can be in several groups and have several tags.

## meta

| Key | Default | What it is |
| --- | --- | --- |
| `title` | none | Chart title. Saved up to 80 characters. |
| `subtitle` | none | Saved up to 120 characters. |
| `lore` | none | Background notes, the text under World & lore. |
| `version` | | Write `"1.2"`. |
| `cover` | none | Image URL shown faintly behind the chart. |
| `coverOpacity` | `5` | Cover opacity in percent, 0 to 15. |
| `settings.nodeRadius` | `30` | Circle size for every node without its own `radius`, 16 to 56. |
| `settings.showGroups` | `true` | Draw the group areas. |
| `settings.avatarStyle` | `"initials"` | Picture for characters without one: `initials`, `notionists`, `lorelei`, `open-peeps`, `pixel-art` or `thumbs` (Initials, Sketch, Illustrated, Hand-drawn, Pixel and Blobs under **Project → Default avatar** in the editor). Each face is drawn from the node's `id`, so the same id always gets the same face. The faces show in the editor and in exported images; the shared chart page and embeds show initials. |

The editor writes a few more keys itself (`generator`, `created_at`, `autosaved_at`, `visibility`, `license`). Leave them out of JSON you write by hand or with an AI.

## Relationship colors

Each line is colored by its label. The editor checks the label against these word lists in this order, and the first match wins. Letter case doesn't matter.

| Category | Color | Words that match |
| --- | --- | --- |
| Romance | `#E91E63` | love, crush, marry, wife, husband, CP, and 爱 恋 喜欢 暗恋 夫妻 情人 嫁 娶 |
| Hostility | `#EF5350` | hate, enemy, betray, kill, jealous, sworn, nemesis, eliminate, and 恨 仇 敌 害 杀 背叛 嫉妒 陷害 |
| Family | `#66BB6A` | family, father, mother, brother, sister, son, daughter, parent, sibling, and 父 母 兄 弟 姐 妹 子 女 祖 孙 叔 姑 亲 |
| Friendship | `#42A5F5` | friend, buddy, comrade, pal, bond, and 友 闺蜜 知己 伙伴 战友 朋友 同窗 同学 |
| Hierarchy | `#FF9800` | master, servant, mentor, student, subordinate, follower, admire, and 主 仆 侍 下属 上司 师 徒 臣 |
| Alliance | `#26C6DA` | ally, partner, alliance, and 盟 党 搭档 合作 同盟 结义 |
| Rivalry | `#FFA726` | rival, and 竞争 对手 宿敌 |
| none | `#78909C` (gray) | anything else |

The check is a plain search for each word inside the label. Letter case doesn't matter, but there are no word boundaries and no plurals:

- A word counts inside a longer word: "loves", "lover" and "in love with" are romance; "grandfather", "stepmother" and "sister-in-law" are family.
- A plural counts only if it still contains the word: "friends", "sisters" and "rivals" are colored, but "enemies", "allies" and "families" stay gray, because "enemy", "ally" and "family" lose their "y". "married" is gray for the same reason: it doesn't contain "marry".
- The first matching list wins: "husband and cousin" is romance, and "sworn brothers" is hostility, because "sworn" comes before family.

Some results that surprise people:

- **Gray, though you might expect a color:** married, spouse, fiancé, engaged, dating, uncle, aunt, cousin, nephew, niece, twin, child, teacher, apprentice, boss, employee, king, colleague, classmate, guardian, competitor, enemies, allies.
- **A different color than you might expect:** girlfriend and boyfriend are friendship (they contain "friend"); romantic partner is alliance ("partner"); person, reason, prison, poisoned and parson are family ("son"); skills is hostility ("kill"); really and finally are alliance ("ally"); principal is friendship ("pal").

To fix a color, use a word from the table, set `color` on the edge, or add a custom color rule.

### Custom color rules

```json
"customColorRules": [
  { "keyword": "girlfriend", "color": "#E91E63" },
  { "keyword": "married", "color": "#E91E63" }
]
```

Custom rules are checked before the table above. A rule whose keyword is the whole label wins first; after that, the first rule whose keyword appears anywhere in the label. Keywords are stored lowercased and trimmed; a rule without a keyword or a color is skipped. In the editor, the same rules are made under **World & lore**, where you pick a color for a relationship label. An edge's own `color` overrides every rule.

### Words for family trees

| Color | Labels |
| --- | --- |
| Family (green) | father, mother, parent, son, daughter, brother, sister, sibling, half-brother, half-sister, stepfather, stepmother, stepson, stepdaughter, foster father, adoptive mother, godfather, godmother, grandfather, grandmother, grandson, granddaughter, great-grandfather, father-in-law, mother-in-law, son-in-law, brother-in-law, sister-in-law, twin brother, twin sister |
| Romance (pink) | husband, wife, ex-husband, ex-wife, first wife, second husband, lover |
| Gray (no category) | married, marriage, spouse, fiancé, fiancée, betrothed, engaged, widow, widower, consort, mistress, uncle, aunt, cousin, nephew, niece, twin, child, children, guardian, ward, heir, ancestor, descendant, relative, in-law |

To color the gray ones, add custom rules to the chart:

```json
"customColorRules": [
  { "keyword": "uncle", "color": "#66BB6A" },
  { "keyword": "aunt", "color": "#66BB6A" },
  { "keyword": "cousin", "color": "#66BB6A" },
  { "keyword": "nephew", "color": "#66BB6A" },
  { "keyword": "niece", "color": "#66BB6A" },
  { "keyword": "married", "color": "#E91E63" }
]
```

## Full example

Every field on this page in one chart:

```json
{
  "meta": {
    "title": "Ship of Fools",
    "subtitle": "Crew of the salvage ship Meridian",
    "lore": "A salvage crew far from home.\nThe old captain still haunts the engine room.",
    "version": "1.2",
    "cover": "",
    "coverOpacity": 5,
    "settings": { "nodeRadius": 30, "showGroups": true, "avatarStyle": "initials" }
  },
  "nodes": [
    { "id": "kai", "name": "Kai", "subtitle": "Captain", "description": "Grew up on Mars.", "groups": ["crew"], "tags": ["pov"], "position": { "x": 0, "y": 0 } },
    { "id": "mira", "name": "Mira", "subtitle": "Pilot", "groups": ["crew"], "color": "#0984E3", "position": { "x": 240, "y": -60 } },
    { "id": "elise", "name": "Élise", "subtitle": "Engineer", "groups": ["crew"], "radius": 36, "position": { "x": 160, "y": 200 } },
    { "id": "jun", "name": "Jun", "subtitle": "Stowaway", "position": { "x": -220, "y": 180 } },
    { "id": "rook", "name": "Rook", "subtitle": "The old captain", "groups": ["ghosts"], "tags": ["deceased"], "position": { "x": -240, "y": -140 } }
  ],
  "edges": [
    { "id": "e1", "source": "kai", "target": "mira", "label": "mentor" },
    { "id": "e2", "source": "kai", "target": "rook", "label": "brothers", "bidirectional": true },
    { "id": "e3", "source": "mira", "target": "elise", "label": "girlfriend", "bidirectional": true },
    { "id": "e4", "source": "jun", "target": "kai", "label": "owes a debt to" },
    { "id": "e5", "source": "rook", "target": "jun", "label": "haunts", "color": "#8E24AA" }
  ],
  "groups": [
    { "id": "crew", "name": "Crew", "color": "#26C6DA", "note": "Everyone still aboard." },
    { "id": "ghosts", "name": "Ghosts", "color": "#90A4AE" }
  ],
  "tags": [
    { "id": "pov", "name": "Point of view", "color": "#FDCB6E" },
    { "id": "deceased", "name": "Deceased", "color": "#636E72" }
  ],
  "customColorRules": [
    { "keyword": "girlfriend", "color": "#E91E63" },
    { "keyword": "owes", "color": "#FF9800" }
  ]
}
```

In this chart "mentor" is orange (hierarchy), "brothers" green (family), "girlfriend" pink through the custom rule instead of blue, "owes a debt to" orange through the second rule, and "haunts" purple from the edge's own color.

## What the editor writes back

When you export a chart (**Export → JSON Data**), every node gets all its fields: `avatar`, `subtitle`, `description` and `radius` (`null` when not set), `color`, `groups`, `tags` and the current `position`. Every edge gets `bidirectional`, and `color` (`null` when not set). `groups`, `tags` and `customColorRules` are left out when they are empty, and `meta.version` is always `"1.2"`. Opening the exported file again gives the same chart.

---

Web page: https://relationmap.io/docs/data-format/ · Updated 2026-11-15 · All docs: https://relationmap.io/llms.txt
