Documents and the file format
Description: Narratyr stores project data in text files for source control and team editing.
Prerelease. This page describes the files for document schema 0.0.80.
A Narratyr project uses a main .ntproj JSON file and a matching .ntproj.data folder. The
main file contains definitions shared across the project. The data folder contains separate
JSON files for quests, dialogues, entities, and variables.
Separate text files reduce source control conflicts across a team. They also let you inspect, compare, and merge authored data with existing source control tools.
A project on disk
A project uses a .ntproj file and a sibling folder with the same name plus a .data suffix:
MyGame.ntproj project-level metadata
MyGame.ntproj.data/
├── dialogues/
│ └── supoh-suvam-basut-vivod.ngraph one file per dialogue graph
├── quests/
│ └── lakur-tusoz-rigav-hazos.ngraph one file per quest graph
├── entities/
│ ├── character/
│ │ └── nidoh-tabit-ropat-bodiv.nentity one file per entity, under its type
│ └── item/
│ └── default-item-1.nentity
├── variables/
│ ├── globals.nvars variables, grouped by namespace
│ └── player.nvars
├── folders/
│ ├── graph/
│ └── entity/
│ └── performances-path.nstub one marker per empty editor folder
├── assets/
│ └── portraits/female-2.png binaries, arranged by asset path
├── docs/
│ └── lore/organizations/white_glove.md your Markdown files
├── _index generated, ignored by Git
└── .gitignore generated
Open the .ntproj file in Narratyr. When you move, rename, or copy a project, move both the
.ntproj file and its .ntproj.data folder. Their base names must match.
Combined files. A .ntprojc file stores the authored document in one file. Binary assets
and Markdown documents remain in a .data folder. This format works well for backups and
solo projects, but the default split format reduces conflicts for teams.
Project file contents
The .ntproj file contains project-wide definitions that other files reference.
| Key | Contents |
|---|---|
id, version |
Project ID and document schema version |
entityTypes, propertySets |
Entity definitions and reusable groups of fields |
enumerations, functionDefinitions |
Named value sets and functions available to expressions |
assets, assetPaths |
Asset metadata, including ID, path, hash, size, and media type |
propertySetPaths |
Folder tree for property sets |
projectInfo |
Project name, AI prompts, and rich-text settings |
Narratyr stores the collections that grow with your story in the data folder.
Compatibility fields. The project file contains empty arrays for graphs, entities,
variables, graphPaths, and entityPaths. Narratyr stores those collections as individual
files in the data folder.
Data folder contents
-
Graphs:
dialogues/<id>.ngraphandquests/<id>.ngraphstore one graph per file. Each file contains the graph'sname,title,description,notes, editorpath,nodes, andedges. -
Entities:
entities/<entityTypeId>/<id>.nentitystores one entity per file, inside a folder for its type. An existing entity keeps its original type, so its file stays in that folder. -
Variables:
variables/<namespace>.nvarsstores a JSON array for each variable group. The filename uses the part of the variable name before its last underscore and converts it to lowercase.Variable name File Player_Levelplayer.nvarsAct1_JarlQuest_KilledJarlact1_jarlquest.nvarsjarl_quest_startedjarl_quest.nvarsPlayerLevelglobals.nvarsThe last underscore preserves longer prefixes, such as
act1_jarlquest. Narratyr routes names without a usable prefix toglobals.nvars. The variable editor shows the destination file while you type a name. -
Empty folders:
folders/graph/<id>.nstubandfolders/entity/<id>.nstubpreserve empty folders from the editor. Folders with items don't require a marker because each item records its editor path. -
Assets:
assets/contains the binary files and follows the asset paths shown in the editor. The main project file contains their metadata. -
Docs:
docs/contains your Markdown files without conversion. An external editor can edit the same files that appear in Narratyr.
Generated files
These are files you should leave out of version control.
_index: a tab-separatedid → kind → path + namelist of project items. Narratyr rewrites it during each save. Use it to identify an ID from a source control diff. Git ignores the file, so it doesn't create conflicts..fs-log.json: a record of pending asset operations. Narratyr uses it to undo disk changes when you cancel an asset edit before saving..save-journal.json: a temporary record that supports recovery from an interrupted save.
Narratyr writes a .gitignore file into the data folder for convenience. You can use any
source control system.
Why projects use separate files in the data folder
Separate files help your team avoid merge conflicts and preserve useful version history for individual items.
Per-item storage reduces the conflict boundary to one graph, entity, or variable group. People working on different dialogues usually edit different files.
This layout also helps Perforce teams. Perforce locks individual files and provides less merge support than Git. A large project file can force the writing team to share one lock.
Entities use separate files for the same reason. File size isn't the concern. Different people can edit different characters without changing the same file.
Identifier-based filenames
Graph and entity filenames use the item's stable ID instead of its human-readable name. The file path doesn't change when the name changes.
Renaming a quest changes a field inside its file but doesn't move the file. If another person edits the quest concurrently, source control can merge the name change with edits to other fields.
Name-based filenames can create duplicate paths when a rename overlaps another edit. Git usually detects a file move, but large changes can appear as a deletion and a new file. Perforce doesn't detect moves during a merge. ID-based filenames avoid both cases.
- Renames change file contents without changing the path.
- Concurrent edits can merge when they affect different parts of the file.
- Duplicate display names work because the ID identifies each item. Separate quests can each contain a fragment named "Intro."
Narratyr generates IDs from 64 random bits and encodes them as four pronounceable chunks,
such as nidoh-tabit-ropat-bodiv. This encoding, called a
proquint, makes IDs easier to read aloud and recognize in a diff.
Default IDs. Default project items can use readable IDs such as character and
default-item-1. The editor assigns proquints to new items. The document format also accepts
unique plain-text IDs.
Reading a file
Files use UTF-8 JSON with two-space indentation. Narratyr sorts object keys alphabetically at each level. Stable key order produces consistent text for the same data, which keeps diffs focused on content changes. Within a graph, Narratyr also sorts nodes and edges by ID because their array order has no effect.
This example shows entities/character/nidoh-tabit-ropat-bodiv.nentity:
{
"externalId": "MiraThorne",
"fieldValues": [
{ "value": "Mira Thorne", "varId": "full-name-field" },
{ "value": "Female", "varId": "character-gender-field" }
],
"id": "nidoh-tabit-ropat-bodiv",
"name": "Mira Thorne",
"path": "/characters/npcs/",
"profileImage": "gumur-rahab-vapor-jupub",
"typeId": "character"
}
Each field value uses a field ID instead of its label. Renaming a field in an entity type
doesn't rewrite the entities that use it. profileImage contains an asset ID from the project
file's assets array. typeId identifies an entry in entityTypes, and externalId provides
the stable value used by game code.
This example shows a dialogue node inside a .ngraph file's graphData.nodes array:
{
"data": {
"choiceSlots": [],
"shortText": "Fair enough",
"speakerEntityId": "nidoh-tabit-ropat-bodiv",
"text": "Fair enough. Fire's warm, ale's cheap, and I don't ask twice.",
"type": "dialogue"
},
"id": "bizog-nikov-vojul-ribug",
"position": { "x": 1377, "y": 166.82 },
"type": "dialogueNode"
}
The outer object contains canvas details such as id, type, position, width, height,
and parentId. The data object contains the authored content, and the node type determines
its fields. Each edge stores source, target, and sourceHandle. The handle identifies the
output used by a branch.
This example shows an empty-folder marker at folders/entity/performances-path.nstub:
{
"id": "performances-path",
"path": "/performances/"
}
When merge markers or invalid JSON prevent Narratyr from reading a file, the project fails to open and the error names that file. Narratyr won't skip the file because a later save could then remove the missing item from the project.
Editor folders and disk folders
The editor uses virtual folders. Each graph and entity stores a path value such as
/characters/npcs/, and Narratyr rebuilds the editor tree from those values. Each graph
directory uses a flat list of files on disk.
Moving a quest to another editor folder changes one field in its graph file. Its location on disk stays the same, so source control doesn't need to detect a file move.
Save behavior
Narratyr serializes and hashes each item during a save. It compares each hash with the prior
save and writes files with changed contents. It also removes files for deleted items. Editing
a line of dialogue changes one .ngraph file.
This comparison prevents a save from rewriting unchanged files and adding them to a diff.
For a multi-file save, Narratyr first writes each item to a temporary file and records the
operation in .save-journal.json. It then moves each temporary file into place with an atomic
rename. If the process stops during a save, Narratyr uses the journal and temporary files to
finish the operation when the project opens again.
Git workflow
Commit the .ntproj file and its complete .ntproj.data folder. The generated .gitignore
already excludes temporary files.
Split storage changes how source control reveals a broken cross-graph reference. One change might delete a dialogue while another change retains a quest node that references it. Git can merge those changes without a text conflict. Narratyr validates references when the project opens and reports the missing target. Check validation results after large merges.
Perforce workflow
Narratyr writes project files directly without calling p4 edit. Configure the workspace for
external tools:
- Enable the
allwriteclient option so files stay writable after sync. The defaultnoallwriteoption makes synced files read-only and causes saves to fail. - Run
p4 reconcile, or "Reconcile Offline Work" in P4V, before you submit changes. This command opens changed, added, and deleted files.
ID-based filenames make reconciliation more direct because edits don't move files. Perforce only needs to find edits, additions, and deletions.
Combined projects
The split format is the default, but Narratyr can also store the authored document in one JSON file. Save As offers these formats:
| Extension | Format |
|---|---|
.ntproj |
Project file plus a separate data folder |
.ntprojc |
Authored document combined into one file |
The extension selects the file format. Narratyr also supports the legacy .rpgproj combined
format. It can open and save existing .rpgproj files but doesn't create new ones.
The combined file contains the same authored data as the split files, so conversion preserves the document. Use this format for backups or tools that require one input file. Teams should use the split format to reduce source control conflicts.
Binary assets and Markdown documents stay in the .data folder for both formats.
Schema versions
The version field in the project file identifies the document schema. When the schema gains
a field, its definition includes a default value. Newer Narratyr builds use these defaults to
open older projects, then save the new fields during the next write.
Before version 1.0, document fields can still change. Keep projects in source control. Tools that read these files should ignore unfamiliar fields and handle missing fields.
Next
Read Dialogues and Quests for details about graph files.
Variables and conditions explains shared state and the naming rule for each
.nvars file.