Dot Hub JSON format
How to read and write Dot Hub animation files in your own app, and where they differ from the Glyph Museum format they are built on. Every field is specified in full below.
- Base format
- Glyph Museum v1 / v4
- dotHubMeta
- 1.1
- Encoding
- UTF-8 JSON
Summary
A Dot Hub file is a Glyph Museum file with two extra root blocks. Anything that reads one reads the other. If you implement nothing else, implement these five things:
- Frames are the only required part.
frames[].pholds LED brightness 0–255 in reading order; array length alone tells you the resolution. - Never fail an import over metadata. Parse frames first, metadata second. A broken block is an absent block.
- Carry blocks you do not own through untouched.
metabelongs to Glyph Museum anddotHubMetato Dot Hub. Read them, write them back, never author them. toolsis the block that is yours. Append one entry for your own app; that is where you record what you did.- A stamp is not a credit. Only claim a user name on work your app actually created or edited.
Why support this format
Dot Hub is the most downloaded Glyph app, so its file format is the one designs already travel in. Supporting it is worth your while twice over:
- Your work stays attributed to you. The
toolschain travels with every file. Your app's name and your users' handles stay on a design wherever it ends up, which is how your tool gets discovered by the people who see the result. - Dot Hub users can actually use your app. A shared format means import and export just work — no converter, no re-drawing, no metadata lost on the way in or out.
Most downloaded based on AppBrain data for the search term “Glyph”, excluding pre-installed apps.
Object model
One JSON object at the root. Five possible keys, of which one is required.
Structure at a glance
{
"v": number // target matrix
"frames": [ { "p", "d" } ] // required, ordered
"meta": { … } // Glyph Museum's — carry it
"dotHubMeta": { … } // Dot Hub's — carry it
"tools": [ { … } ] // yours — append yourself
}
A complete file, all blocks present
{
"v": 4,
"meta": { "author": "pauwma", "postId": 123 },
"dotHubMeta": { "version": "1.1", "user": "paulg", "edited": true },
"tools": [
{ "id": "app.glyphmuseum.com", "name": "Glyph Museum", "user": "pauwma", "created": true },
{ "id": "com.gesekus.dothub", "name": "Dot Hub", "user": "paulg", "edited": true }
],
"frames": [
{ "d": 100, "p": [0, 0, 80, 120, 255, …137 or 489 values] }
]
}
Field reference
Every element of the format. The your app column is the short answer to what to do with each block when you save a file.
Root object
| Key | Type | Requirement | Your app | Notes |
|---|---|---|---|---|
v | number | optional | write | 1 = 25×25, 4 = 13×13. Write it correctly; Dot Hub does not read it. |
frames | array | required | write | Ordered, non-empty. One frame is a static design, more is an animation. |
meta | object | optional | carry | Glyph Museum attribution. Absent on files never published there. |
dotHubMeta | object | optional | carry | Dot Hub's own stamp. Only Dot Hub writes it. |
tools | array | optional | append | Chain of applications, oldest first. Omit the key entirely when empty. |
frames[ ]
| Key | Type | Requirement | Default | Notes |
|---|---|---|---|---|
p | number[] | required | — | Brightness per LED, 0–255, reading order. See chapter 3. |
d | number | optional | 100 | Frame duration in milliseconds. Only meaningful in animations. |
meta { }
Defined and written by Glyph Museum. Read these fields to display credit; never author the block yourself.
| Key | Type | Requirement | Notes |
|---|---|---|---|
author | string | optional | Creator's handle, without the @. May be absent when unresolvable. |
postId | number | optional | The stable pointer; prefer it over url. Must be a JSON number greater than 0 — "123" as a string is dropped, not coerced. |
url | string | optional | Canonical post page. Only http and https are ever opened. |
dotHubMeta { }
Written only by Dot Hub. Read it to know a file passed through Dot Hub and who worked on it there; carry it through unchanged when you re-save. Record your own involvement in tools instead.
| Key | Type | Requirement | Notes |
|---|---|---|---|
version | string | optional | Version of this block, currently "1.1". Not the same thing as v. Dot Hub writes it on every save. |
user | string | optional | A handle the user typed in Dot Hub. Never an account, never an email. Present only alongside edited. |
edited | boolean | optional | true when the frames were created or modified inside Dot Hub. |
tools[ ] { }
The block your app writes into. One entry per application, oldest first.
| Key | Type | Requirement | Notes |
|---|---|---|---|
id | string | required | Your package name, or the host of your website. An entry without it is dropped. |
name | string | optional | Human-readable application name, for display. |
user | string | optional | The user's handle in your app. Write only alongside created or edited. |
created | boolean | optional | true when the frames came into existence in your app. |
edited | boolean | optional | true when you modified existing frames. Neither flag = passed through. |
Limits
| Limit | Value | On breach |
|---|---|---|
| Frames per file | 240 | Rejected, too_many_frames |
| Import file size | 4 MiB | Rejected before parsing |
Entries in tools | 16 | Trimmed from the middle, never rejected |
| Distinct resolutions per file | 1 | Rejected, mixed_sizes |
The p array
The Glyph Matrix is a circle cut out of a square grid, so p holds only the LEDs that physically exist. Each row is centred inside the grid. Index 0 is the start of the top row; values continue left to right, then down.
- 137
- values — real LEDs only
- 0
- wasted positions
The form to write
13×13 rows, top to bottom: [5, 9, 11, 11, 13, 13, 13, 13, 13, 11, 11, 9, 5]. 25×25: [7, 11, 15, 17, 19, 21, 21, 23, 23, 25, 25, 25, 25, 25, 25, 25, 23, 23, 21, 21, 19, 17, 15, 11, 7].
| Length | Grid | Form | Device | Accepted by |
|---|---|---|---|---|
| 137 | 13 × 13 | compact | Phone (4a) Pro | both |
| 489 | 25 × 25 | compact | Phone (3) | both |
| 169 | 13 × 13 | full square | Phone (4a) Pro | Dot Hub only |
| 625 | 25 × 25 | full square | Phone (3) | Dot Hub only |
Walking a flat p array back onto the grid
let i = 0;
for (let row = 0; row < gridSize; row++) {
const leds = rowPattern[row];
const colStart = (gridSize - leds) / 2; // rows are centred
for (let col = 0; col < leds; col++) {
draw(colStart + col, row, p[i++]);
}
}
Writing p
Always write the compact form. Dot Hub accepts the full square grid on read as a convenience, but a Glyph Museum reader does not — 169 or 625 values will not open there.
What your app needs to implement
Work down these until one stops describing your app. Each level assumes the ones above it.
Do you open files a user exported themselves?
Baseline · everyoneThis is the whole requirement for reading. Parse frames, take p and d from each, infer the resolution from p.length, and render. Nothing else is needed and no permission is involved.
Add this
Do you show designs to anyone but their author?
Display · any shared or browsable viewThen you need to say where a design came from. Read meta.author for the original creator, and tools for the apps involved. Show the credit wherever the design appears.
Add this
- A credit line from
meta.author, falling back to the newesttoolsentry that has auser. - Link back to the post when
meta.postIdis present. - Show nothing rather than guessing when neither is available.
Do you support Glyph Museum designs?
Glyph Museum · designs published by the communityLetting people bring in designs published by the community — as opposed to files they exported themselves — puts you under Glyph Museum's conditions, written into their Terms of Service §5.4 and summarised on their developer page:
- Credit the author. Show the handle wherever the design appears and link back to its post. The
metablock hands you both. - Keep community designs free. No paywall on other people's work. Charging for your own tools and features is entirely your call.
- No bulk collection. No scraping, mirroring or bundling the catalog. One design at a time, because a user asked for it. Removed posts must stay gone.
- Publishing stays with them. There is no third-party publish path. Users can always export a file and bring it over themselves.
Add this to every file you write
// Read it, keep the original object, write it straight back.
// Including keys you do not recognise. Never build one yourself.
"meta": { "author": "pauwma", "postId": 123, "url": "…", …unknown keys… }
If url and postId disagree, trust postId. Once a file leaves Glyph Museum's own apps this block is the only thing holding credit to the work, and dropping it on a re-save is the one thing their format asks you not to do.
Do you save, edit or export files?
Writing · the point at which you join the chainThen add yourself to tools. This is the block that exists for you: it is how a design records which apps shaped it, and it is the only place you should describe your own involvement.
Add this
"tools": [
// …every entry that was already there, unknown keys intact…
{
"id": "com.yourcompany.yourapp", // package name, or your site's host
"name": "Your App",
"user": "their handle", // only with created or edited
"created": true // or "edited": true, or neither
}
]
- Carry
metaanddotHubMetathrough unchanged. Neither is yours to write, anddotHubMeta.editedwould claim Dot Hub made your edit. - Set
createdwhen the frames originated in your app,editedwhen you changed existing ones, neither when you only re-saved. - Write
useronly alongside one of those flags. - Follow the merge rule in chapter 5 so repeated saves do not stack up entries.
The tools chain
meta and dotHubMeta answer who made this. tools answers what it was made with, and is explicitly allowed to grow as a design travels between apps.
The merge rule
When you write a file, append an entry for yourself. If the newest entry is already your id with a compatible user — equal, or one of the two absent, in which case the absent one is filled in — merge your flags into it instead of appending.
| Sequence | Resulting chain | Rationale |
|---|---|---|
| A, A, A | 1 entry | Repeated saves in one app are one visit. |
| A, B, A | 3 entries | The file genuinely went out and came back. |
| A(ann), A(—) | 1 entry, still ann | An anonymous stamp does not fork the chain. |
| A(ann), A(bo) | 2 entries | A different person is a different link. |
Rules
- Order is oldest first. Append at the end.
- Neither flag set means the file only passed through — opened, re-saved, exported, not changed. Still worth recording.
- Cap at 16 entries. Drop from the middle: index 0 is always kept, and so are the newest.
- Omit the key entirely when the chain is empty. Never write
"tools": []. - Preserve unknown keys inside every entry you did not write. This is where other apps record things, so a re-save must not trim them.
- Never back-fill. A file with a
metablock but notoolsdoes not get a synthesised Glyph Museum entry, obvious though the origin is. The chain holds only what apps wrote about themselves.
Reading a file
Metadata must never get between a user and their file. These are the rules Dot Hub's own importer follows, taken from Glyph Museum's guidance.
- Parse frames first, metadata second. A broken metadata block can then never turn a good file into an error.
- Treat a block that is missing, malformed, or the wrong shape as absent.
- Validate field by field. Drop what does not fit, keep what does.
- Validate
toolsentry by entry. One bad entry is skipped; the rest of the chain survives. - Treat an empty or whitespace-only string as absent.
- Ignore keys you do not recognise — and preserve them when writing.
- Infer resolution from
p.length, not fromv.
Rejection reasons
| Code | Cause |
|---|---|
empty | Input was null or blank. |
no_frames | frames missing, unparseable, not an array, or empty. |
bad_frame | An element is not an object, or has no p array. |
bad_size | p length is not 137, 489, 169 or 625. |
mixed_sizes | Frames in one file resolve to different matrices. |
too_many_frames | More than 240 frames. |
Less information beats wrong information. A design with no author shown is fine; a design credited to the wrong person is not.
Writing a file
A checklist for anything that produces one of these files.
- Write
vfrom the matrix size — 25 → 1, 13 → 4 — even though Dot Hub ignores it. Other readers use it. - Write
pin the compact form, values clamped to 0–255. - Write
dper frame, or omit it to accept the 100 ms default. - Copy
metaanddotHubMetathrough byte for byte, including keys you do not recognise. Do not create either. - Append your own
toolsentry per chapter 5, carrying every existing entry through with its unknown keys intact. - Only write a
userwhere your app genuinely created or edited the frames. - Omit optional keys rather than writing
nullor"".
Brightness scale
On disk brightness is always 0–255. Dot Hub scales to 0–4095 in memory by 16.0588; that factor is an implementation detail and never appears in a file.
Differences from Glyph Museum
Everything that is not identical between the two formats, in one table. The base format is documented at glyphmuseum.com/developers.
| Element | Glyph Museum | Dot Hub |
|---|---|---|
v, frames, p, d |
Defines them | Identical — except v is written but never read |
p length |
489 or 137 only | Also accepts 625 / 169; always writes compact |
meta |
Owns and writes it | Reads and re-emits verbatim; never creates it |
dotHubMeta |
Unknown key, ignored | Owns it; written on every save |
tools |
Unknown key, ignored | Defines the rules; any app may append |
| Unknown keys | Ignore on read, keep on write | Kept in meta and tools[]; rebuilt in dotHubMeta |
| Publishing | Official apps only | Does not publish or fetch |
| Document scope | Specification and licence | Specification only |
Forward compatibility
Both formats grow by adding keys, never by removing or repurposing them. Ignore what you do not recognise instead of rejecting the file, never assume a metadata block is there, and carry through whatever you were handed. New keys in dotHubMeta raise its version; a new resolution arrives as a new v with its own row pattern.
Questions about the format, or shipped something that reads it? Write to contact@nostream.de — it goes straight to the developer.