---
title: "Ember Archive Format · Ember Help"
description: "The structure of Ember's completely open JSON export archive. Parse your data, build your own tools, or migrate elsewhere. No vendor lock-in."
url: https://ember.mov/help/archive-format
language: en
---

[Help](https://ember.mov/help/)/Archive Format

# Ember Archive Format

Ember is designed with zero lock-in. When you export your data, you get a clean, human-readable JSON archive inside a standard `.zip` file. This reference outlines the structure of that export so you can build your own tools, parse your data, or migrate elsewhere.

## Archive Layout

The export is a single ZIP archive containing your data split into independently-parseable JSON array files, a schema, a manifest, and all your media attachments.

```
ember-export-YYYY-MM-DD.zip
├── manifest.json              ← producer, schema version, exportedAt, counts
├── schema.json                ← JSON Schema for every DTO below
├── README.md                  ← human-readable format guide
│
├── worlds.json                ← [World]
├── people.json                ← [Person]
├── how_we_met_tags.json       ← [HowWeMetTag]
├── interaction_media.json     ← [InteractionMedium]
├── interactions.json          ← [Interaction]
├── stories.json               ← [Story]
├── journal_entries.json       ← [JournalEntry]
├── tags.json                  ← [Tag]
├── media_assets.json          ← [MediaAsset]
├── person_attributes.json     ← [PersonAttribute]
├── person_connections.json    ← [PersonConnection]
├── we_good.json               ← [WeGood]
├── world_stints.json          ← [WorldStint]
├── world_layouts.json         ← [WorldLayout]
├── writing_prompts.json       ← [WritingPrompt]
│
└── media/                     ← Binary attachments referenced by UUID
    ├── Photos/<uuid>.<ext>
    ├── Audio/<uuid>.<ext>
    ├── Video/<uuid>.<ext>
    ├── Songs/<uuid>.<ext>
    ├── People/<uuid>.<ext>
    └── Stories/<uuid>.<ext>
```

## Identity & Relationships

Ember flattens all relationships to ensure parsing is simple and memory-efficient.

- **Primary key everywhere:** `uuid` (8-4-4-4-12 hex with hyphens).
- **Relationships are flat:** Never nested. Each side stores the other's UUID. A many-to-many relationship like Story &harr; Person lives as a `peopleIDs: [UUID]` array on the Story side.
- **Dates:** All dates are formatted as ISO 8601 / RFC 3339 in UTC, e.g. `2026-03-12T14:30:00Z`. When a moment has fractional seconds they're included (`2026-03-12T14:30:00.123456Z`) so timestamps round-trip exactly, so accept both forms.
- **Enums:** Encoded as human-readable string values (e.g., `"Tending"`, `"Rooted"`), stable across schema changes.

## Derived Fields

The archive includes fields that are pre-computed for fidelity, such as `lastContactDate` on a Person or `previewSnippet` on a Journal Entry. If you are importing this data into your own tool, it is recommended to recompute these fields rather than trust the snapshot completely.

## Markdown Mentions

Fields like `contentMarkdown`, `noteMarkdown`, and `narrativeMarkdown` are written in CommonMark. Ember adds inline "pill" extensions for mentions: a one-character prefix on a standard Markdown link to `https://ember.mov/<kind>/<uuid>`, so other parsers see an ordinary link:

| Mention Type | Syntax | Target Row |
| --- | --- | --- |
| Person | `@[Display Name](https://ember.mov/person/<uuid>)` | Person |
| Tag | `#[Tag name](https://ember.mov/tag/<uuid>)` | Tag |
| World | `~[World name](https://ember.mov/world/<uuid>)` | World |
| Story | `&[Title](https://ember.mov/story/<uuid>)` | Story |
| Interaction | `+[Display](https://ember.mov/interaction/<uuid>)` | Interaction |
| Journal Entry | `%[Title](https://ember.mov/journal/<uuid>)` | JournalEntry |

Story and journal pills also have a "card" form with a doubled prefix (`&&[…](…)`, `%%[…](…)`) that shows as a block instead of inline. The label is display text only: Ember finds the target by its uuid. Person labels keep the name used at the time; tag and world labels follow renames. A converter should match the URL, not the label.

## Dates & Time Zones

Every date is an ISO 8601 instant in UTC. Journal entries, interactions, stories, world stints and person connections also carry `timeZoneIdentifier`: the IANA zone (like `Asia/Tokyo`) their dates were **entered in**. To get the day and time a person remembers, read the instant in that zone.

This matters for days picked from a calendar. A story picked as March 5 in Kolkata is stored as March 5 at 00:00 Kolkata time, which is `2020-03-04T18:30:00Z`. Read in UTC or in New York it looks like March 4; read in `Asia/Kolkata` it is March 5, as intended.

All dates on one record share its one zone. `Person.timeZoneIdentifier` is different: it's where that person lives. When importing an archive without the field, Ember uses the importing Mac's zone.

## All Schemas

### World (`worlds.json`)

```
{
  "uuid": "UUID",
  "name": "String",
  "colorHex": "String",
  "summary": "String",
  "decayThresholdDays": 90,
  "fadingDurationDays": 30,
  "nudgePolicy": "String? (fading | dormant | never)",
  "defaultIntention": "String",
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)",
  "deletedAt": "Date (ISO 8601)?",
  "importSource": "String?",
  "importBatchID": "UUID?"
}
```

### Person (`people.json`)

```
{
  "uuid": "UUID",
  "name": "String",
  "nicknames": ["String"]?,
  "locationName": "String?",
  "locationLatitude": "Double?",
  "locationLongitude": "Double?",
  "locationCountry": "String?",
  "locationCountryCode": "String? (ISO 3166-1 alpha-2)",
  "locationAdminArea": "String?",
  "locationLocality": "String?",
  "locationContinent": "String?",
  "timeZoneIdentifier": "String?",
  "phone": "String?",
  "email": "String?",
  "birthMonth": 1,
  "birthDay": 28,
  "birthYear": 1990,
  "metYear": 2019,
  "metMonth": 6,
  "metDay": 14,
  "metIsApproximate": false,
  "intention": "String",
  "customDecayThresholdDays": 60,
  "decayResolution": "String",
  "isMemoriesPaused": false,
  "lastContactDate": "Date (ISO 8601)?",
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)",
  "deletedAt": "Date (ISO 8601)?",
  "importSource": "String?",
  "importBatchID": "UUID?",
  "colorHex": "String?",
  "photoRelativePath": "String?",
  "howWeMetTagID": "UUID?",
  "worldIDs": ["UUID"],
  "primaryWorldID": "UUID?"
}
```

### How We Met Tag (`how_we_met_tags.json`)

```
{
  "uuid": "UUID",
  "value": "String",
  "isDefault": false,
  "isHidden": false,
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)"
}
```

### Interaction Medium (`interaction_media.json`)

```
{
  "uuid": "UUID",
  "value": "String",
  "icon": "String",
  "promptDuration": false,
  "isDefault": false,
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)"
}
```

### Interaction (`interactions.json`)

```
{
  "uuid": "UUID",
  "date": "Date (ISO 8601)",
  "timeZoneIdentifier": "String?",
  "initiatedByUser": true,
  "presenceScoreUser": 5,
  "presenceScoreThem": 5,
  "noteMarkdown": "String?",
  "previewSnippet": "String?",
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)",
  "deletedAt": "Date (ISO 8601)?",
  "conflictParentUUID": "UUID? (set on a conflict copy)",
  "importSource": "String?",
  "importBatchID": "UUID?",
  "mediumID": "UUID?",
  "peopleIDs": ["UUID"],
  "tagIDs": ["UUID"],
  "writeMoreJournalEntryID": "UUID?"
}
```

### Story (`stories.json`)

```
{
  "uuid": "UUID",
  "title": "String",
  "startDate": "Date (ISO 8601)",
  "endDate": "Date (ISO 8601)",
  "timeZoneIdentifier": "String?",
  "narrativeMarkdown": "String?",
  "previewSnippet": "String?",
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)",
  "deletedAt": "Date (ISO 8601)?",
  "importSource": "String?",
  "importBatchID": "UUID?",
  "conflictParentUUID": "UUID? (set on a conflict copy)",
  "coverRelativePath": "String?",
  "places": [{ "id": "UUID", "name": "String", "latitude": "Double?", "longitude": "Double?", "country": "String?", "countryCode": "String?", "adminArea": "String?", "locality": "String?", "continent": "String?" }]?,
  "peopleIDs": ["UUID"],
  "tagIDs": ["UUID"],
  "worldIDs": ["UUID"],
  "assetIDs": ["UUID"],
  "journalEntryIDs": ["UUID"]
}
```

### Journal Entry (`journal_entries.json`)

```
{
  "uuid": "UUID",
  "entryDate": "Date (ISO 8601)?",
  "timeZoneIdentifier": "String?",
  "title": "String?",
  "contentMarkdown": "String?",
  "previewSnippet": "String?",
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)",
  "deletedAt": "Date (ISO 8601)?",
  "isPinned": false,
  "locationName": "String?",
  "locationLatitude": "Double?",
  "locationLongitude": "Double?",
  "locationCountry": "String?",
  "locationCountryCode": "String? (ISO 3166-1 alpha-2)",
  "locationAdminArea": "String?",
  "locationLocality": "String?",
  "locationContinent": "String?",
  "weatherCondition": "String? (SF Symbol name)",
  "weatherTemperatureCelsius": "Double? (always °C)",
  "weatherNotes": "String?",
  "isTemplate": "Bool? (true: a template to start entries from, not an entry)",
  "conflictParentUUID": "UUID? (set on a conflict copy)",
  "importSource": "String?",
  "importBatchID": "UUID?",
  "tagIDs": ["UUID"],
  "mentionedPeopleIDs": ["UUID"],
  "taggedStoryIDs": ["UUID"],
  "taggedWorldIDs": ["UUID"],
  "mentionedInteractionIDs": ["UUID"],
  "mentionedJournalEntryIDs": ["UUID"],
  "assetIDs": ["UUID"],
  "belongsToInteractionID": "UUID?",
  "belongsToPersonHowWeMetID": "UUID?",
  "belongsToPersonNoteID": "UUID?"
}
```

### Tag (`tags.json`)

```
{
  "uuid": "UUID",
  "name": "String",
  "isStarred": false,
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)",
  "deletedAt": "Date (ISO 8601)?"
}
```

### Media Asset (`media_assets.json`)

```
{
  "uuid": "UUID",
  "originalRelativePath": "String?",
  "displayRelativePath": "String?",
  "mimeType": "String",
  "filename": "String",
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)",
  "entryID": "UUID?",
  "storyID": "UUID?",
  "songTrackID": "String?",
  "songArtist": "String?",
  "songAlbumTitle": "String?",
  "songPreviewURL": "String?",
  "songDuration": 210.5?
}
```

### Person Attribute (`person_attributes.json`)

```
{
  "uuid": "UUID",
  "key": "String",
  "value": "String",
  "type": "String",
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)",
  "personID": "UUID?"
}
```

### Person Connection (`person_connections.json`)

```
{
  "uuid": "UUID",
  "fromPersonID": "UUID?",
  "toPersonID": "UUID?",
  "note": "String?",
  "connectionDate": "Date (ISO 8601)",
  "timeZoneIdentifier": "String?",
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)",
  "deletedAt": "Date (ISO 8601)?",
  "endedAt": "Date (ISO 8601)?"
}
```

### We Good (`we_good.json`)

```
{
  "uuid": "UUID",
  "personID": "UUID?",
  "date": "Date (ISO 8601)",
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)"
}
```

### World Stint (`world_stints.json`)

```
{
  "uuid": "UUID",
  "personID": "UUID?",
  "worldID": "UUID?",
  "startDate": "Date (ISO 8601)",
  "endDate": "Date (ISO 8601)?",
  "timeZoneIdentifier": "String?",
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)"
}
```

### World Layout (`world_layouts.json`)

```
{
  "uuid": "UUID",
  "worldID": "UUID",
  "x": 100.0,
  "y": 100.0,
  "radius": 50.0,
  "updatedAt": "Date (ISO 8601)"
}
```

### Writing Prompt (`writing_prompts.json`)

```
{
  "uuid": "UUID",
  "text": "String (a question shown on a blank journal page)",
  "isDefault": false,
  "isHidden": false,
  "createdAt": "Date (ISO 8601)",
  "updatedAt": "Date (ISO 8601)"
}
```

## Importing and Re-importing

- **Same uuid, same row:** importing an archive into a library that already has a row with that uuid keeps whichever copy has the later `updatedAt`, so an older backup never undoes newer edits, and a newer one brings its changes in (renamed tags and labels included).
- **Undo Import:** moves that import's rows to the Bin. Importing the same archive again brings them back.
- **The Bin and permanent delete:** rows in the Bin are exported with `deletedAt` set and arrive in the Bin. Permanently deleted rows aren't exported.
- **Conflict copies:** when the same note was edited on two devices at once, Ember keeps both. The extra copy has `conflictParentUUID` pointing at the original.

## Media Files

Images, audio, video, and other attachments are stored in the `media/` folder. Models reference these files using relative paths.

- `MediaAsset.originalRelativePath` and `displayRelativePath` indicate where the file is inside the `media/` folder (e.g., `Photos/abc.jpg`).
- Avatars (`Person.photoRelativePath`) and Story Covers (`Story.coverRelativePath`) follow the same convention but do not have dedicated `MediaAsset` rows.

Still stuck? [Ask on the feedback board →](https://feedback.ember.mov/) [All guides](https://ember.mov/help/)
