Every project saved with “Save to Computer…” is a tt_export file: one
plain-text file holding the whole project — passages, sentences and
templates. This page describes what is in that file, what will not
change about it, and how to read it with your own tools.
A saved project is ndjson: UTF-8 text, one JSON object per line, with no brackets or commas holding the whole file together. Read it one line at a time — each line stands on its own, so a large project can be streamed rather than loaded whole.
The first line names the project. Every line after it is one document,
labelled with the collection it belongs to: a template (meta), a passage,
or a sentence.
{"doctype":"tt_export","version":2,"name":"Florble","description":"Fieldwork, spring","exported_at":1787350000.0}
{"collection":"meta","doc":{"id":"template_1787253162080_Default template","data":{"kind":"template","name":"Default template","tracks":{"word_tracks":[{"name":"IPA","type":"T2IPA"},{"name":"Gloss","type":"Text"}],"sentence_tracks":[{"name":"Translation"}]}}},"internal":{"…":"…"}}
{"collection":"passages","doc":{"id":"nY1oxUoyOi9dxjF","data":{"name":"P1","word_tracks":[{"name":"IPA","type":"T2IPA"},{"name":"Gloss","type":"Text"}],"sentence_tracks":[{"name":"Translation"}],"track_template":"template_1787253162080_Default template"}},"internal":{"…":"…"}}
{"collection":"sentences","doc":{"id":"nY1oxUoyOi9dxjF-0000000000","data":{"words":[{"IPA":"ɲɔnwɔ","Gloss":"dog"}],"sentences":{"Translation":"the dog"},"grammatical":true}},"internal":{"…":"…"}}
Two things about that shape matter most:
Read doc; ignore internal. Every document line carries both. doc is
the external view — the one this page describes and the schema constrains.
internal is the app's own working state, kept in the file so that loading
the project back restores it exactly as it was. It is deliberately
undocumented, it may change shape at any time without the version changing,
and nothing outside the app should read it.
A sentence knows its passage by its id. A sentence's id is its
passage's id, then a hyphen, then a counter — nY1oxUoyOi9dxjF-0000000000
belongs to passage nY1oxUoyOi9dxjF. That prefix is the one link between
collections, and it is stable.
doc.datadata holds the linguistic content, arranged for reading rather than for
the app's convenience:
words is one object per word:
{"IPA": "ɲɔnwɔ", "Gloss": "dog"}. Sentence-level tracks work the same
way, in sentences.ɲɔnwɔ, not \textltailn On^{w}O.grammatical and infelicitous, matching
the checkboxes in the app.word_tracks and
sentence_tracks, plus track_template: the id of the template it was
created from, which is how you join a passage back to the meta collection.unicodedata.normalize('NFD', s) in Python, s.normalize('NFD') in
JavaScript).jq recipes, to copy or adapt. IPA and Translation are track names
from the example above — substitute the ones your project uses:
# every IPA line in the project, one sentence per line
jq -r 'select(.collection=="sentences") | [.doc.data.words[].IPA] | join(" ")' 'My Project.json'
# sentences with their translations, as TSV for a spreadsheet
jq -r 'select(.collection=="sentences") | [([.doc.data.words[].IPA] | join(" ")), .doc.data.sentences.Translation] | @tsv' 'My Project.json' > sentences.tsv
# the passages, with the id each sentence id is prefixed by
jq -r 'select(.collection=="passages") | [.doc.id, .doc.data.name] | @tsv' 'My Project.json'
The same walk with the Python reader below:
import tt_export
export = tt_export.load('My Project.json')
print(export.header.name, len(export.passages), 'passages')
for passage in export.passages:
print(passage.data['name'])
for sentence in export.sentences_for(passage):
words = [w.get('IPA', '') for w in sentence.data.get('words', [])]
print(' ', ' '.join(words), '--', sentence.data['sentences'].get('Translation', ''))
tt-export.schema.jsonThe authoritative description of every line and every field. Point a validator at it, or read it as the reference.
Downloadtt_export.pyStandard library only, one file to copy next to your script. Gives you passages, sentences, and the links between them.
Downloadtt-export.d.tsTypes for every shape in the format, for editors and type-checkers. No runtime code.
DownloadFour rules:
doc, never internal. The internal half of a document line
carries no promises.version
number, which is on the first line of every file.rev (and imported.rev) as opaque tokens. They can be
compared for equality and nothing else — no ordering, no structure, no
meaning outside the database they came from.creator and modifier as approximate, unverified provenance.
They are opaque ids, useful for telling two contributors apart and for
knowing roughly who to ask about a strange entry. They are not identity
anyone vouches for — a file can be edited by anybody who has it.Files whose first line begins {"v":1, were written by an earlier version of
TT, before this format existed. They are a different thing — a database
replication dump — and they do not follow anything on this page. TT still
loads them, and re-saving the loaded project converts it to the current
format. Note that those older files did not include templates, so templates
cannot be recovered from one.
Everything a file may contain, from the schema itself:
Project name.
Project description.
Seconds since the Unix epoch, at export time. Optional.
The project this file came from. Written by a server export; absent from a file the browser wrote, which has no id to give.
An opaque cursor for the moment this snapshot was read. Hand it back to a change feed and nothing between the two is missed. Server exports only.
meta holds project-level documents such as track templates; passages and sentences hold the linguistic data. A sentence's id is prefixed by its passage's id.
Document id, unique within its collection. Sentence ids are '<passage id>-<10-digit counter>'.
Opaque revision token of the source database at export time. Comparable for equality only; carries no ordering or structure.
Seconds since the Unix epoch.
Seconds since the Unix epoch.
Opaque user id: unverified approximate provenance, not identity the system vouches for.
Opaque user id; see creator.
Present when this document arrived in its project via a file import. creator/modifier upstream of an import are file-supplied and unverifiable.
The external view of the document's payload: every string NFD-normalized Unicode, with per-collection shapes below. Every field the writer emits here is documented in this schema — undocumented internal fields live only in internal until deliberately promoted. Derived at export and ignored on import — a hand-edited data never enters a database.
The app's own document state, 1:1 with its database, and the only thing the app reads on import. Its shape is deliberately UNSPECIFIED: it may change without a version bump and may drift toward doc over time. Nothing outside the app may depend on it — everything meant for reading is in doc.
The id of the meta template this passage was created from — the join key to the meta collection's template documents.
One object per word: track name -> NFD display value.
Sentence-level track values, keyed by track name — e.g. this sentence's English translation and its French translation.
The grammaticality judgment for this sentence.
The felicity judgment for this sentence.
Project-level documents. Track templates carry kind/name/tracks; a meta document of no known kind exports an empty data shell (its content is in internal only).
The last line of a signed file, vouching for every byte above it. Written by a server that holds a signing key; a file the browser wrote has none, and an unsigned file is perfectly valid. Verifying needs only the public key named by kid.
The construction. Ed25519-SHA256 means: SHA-256 over every byte above this line, then Ed25519 over the canonical JSON of {alg, kid, project_id, exported_at, sha256} prefixed with "tt_export_sig.v1\n". The claims are signed too, so alg and kid cannot be rewritten on a file that still verifies.
Which public key verifies this, so keys can rotate without old files becoming unverifiable.
Epoch seconds, fractional — the same value the header carries.
Lowercase hex SHA-256 of every byte above this line.
base64url, unpadded, of the Ed25519 signature.
Rendered from https://www.twisted-tongues.com/schemas/tt-export-v2.json, the schema itself.