Fusor File Reference (.fusor)
Fusor File Reference (.fusor)
A .fusor file is a JSON document holding one piece of automation: the domains it drives, the curve on each parameter, and the actions that run around it. It is what the player loads and what the Domain Editor writes.
This page is the reference for the format. For the editor that produces these files, see Authoring Overview. For the parameters a binding may target, see Supported Parameters.
You never have to write one by hand. Reading one is useful when you are sharing a fusor, comparing two versions, or working out why a file will not load.
File location and naming
Fusors live flat in your library's fusors/ folder, and the filename tracks the fusor's name. The folder rules, the renaming behavior, and how collections group fusors without moving files are covered in Authoring Overview → Where files live.
Top-level shape
{
"version": "2.0",
"meta": { ... },
"domains": [ ... ],
"actions": { ... }
}
| Field | Type | Required | Notes |
|---|---|---|---|
version | string | Yes | Must be "2.0". |
meta | object | Yes | See Meta block. |
domains | array | Yes | At least one. See Domains. |
actions | object | No | Pre-run and post-run action lists. See Actions block. |
Every level of the file rejects fields it does not recognize. A misspelled key fails the load, and the error carries the path it was found at.
Meta block
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | Yes | The fusor's name. The filename follows it. |
apps | string[] | Yes | DJ apps this fusor targets. Each entry is checked against the app registry. An empty array means universal. |
id | string | Auto | Permanent identity, stamped on first save and never rewritten. Collections reference it, so renaming or moving the file changes nothing. Don't hand-edit. |
author | string | No | Who wrote the fusor. |
version | string | No | The author's own version label, free text. Distinct from the top-level version, which is the format's. |
description | string | No | What the fusor does. |
intent | string | No | A concise statement of what the fusor is for, shown at the top of Session View before the transition runs. |
notes | string | No | The reader's personal notes, edited on the Info tab. |
mixInPoint | string | No | "start", "middle" or "end". Advisory: the engine never reads it. Absent means unset. |
created | ISO timestamp | Auto | Written on first save. |
modified | ISO timestamp | Auto | Rewritten on every save. |
The apps list does more than label the file. It gates which domain types can be added to the fusor, and which fusors can be imported from. See Add Domain.
Domains
Each entry in domains is one control surface, on one or both sides of the transition.
{
"domainTypeId": "eq",
"instanceId": "eq_outgoing",
"enabled": true,
"bindings": [ ... ]
}
| Field | Type | Required | Notes |
|---|---|---|---|
domainTypeId | string | Yes | Which domain type this is: volume, eq, fx_unit, fx_bank, stems_volume, stems_filter, stems_fx_send, mixer_fx. |
instanceId | string | Yes | Identifier for this domain within the fusor. Starts with a letter; letters, digits and underscores after that. Must be unique across the file. |
bindings | array | Yes | At least one. See Bindings. |
enabled | boolean | No | Defaults to true. Can be switched off for one run from Session View without touching the file. |
role | string | For FX Unit | "incoming" or "outgoing". Which deck the FX unit serves. |
fxUnitId | integer | For FX Unit | 1 to 4. Which FX unit the fusor drives. |
preflight | object | No | Per-domain reminder text. See Preflight. |
Most domains take their side from their bindings' addresses and need no role. FX Unit is the exception: its addresses carry no role prefix, so the domain has to say which deck it belongs to.
Preflight
"preflight": {
"snapshot": { "default": "...", "traktor": "..." },
"override": null,
"hide": false
}
snapshot is a frozen copy of the domain type's reminder text, keyed by app with a default fallback. override replaces it when non-null. hide suppresses the reminder entirely. The editor for all three, and the order they resolve in, is in Domains → Per-card preflight.
Bindings
One binding is one automated parameter.
{
"id": "out_volume_cos",
"target": "outgoing.volume",
"curve": { "anchors": [ ... ] }
}
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | Yes | Identifier for this binding. Same character rules as instanceId. |
target | string | Yes | The parameter's address. Must be one the domain type owns. See Supported Parameters. |
curve | object | Yes | See Curves. |
effectClass | string | No | The author's name for the effect in an FX slot, such as "ECHO". The editor uses it as the slot's tab label, falling back to "Slot N" when it is absent. |
effectInstanceId | string | No | Groups the bindings that belong to the same effect instance. |
A parameter with no curve is not written to the file, so a domain holds bindings only for the parameters you actually authored.
Curves
"curve": {
"anchors": [ ... ],
"stepValues": [ ... ]
}
| Field | Type | Required | Notes |
|---|---|---|---|
anchors | array | Yes | 2 to 16 anchor objects, in ascending progress order. |
stepValues | array | No | 2 to 16 named levels. Its presence makes the lane stepped. See Step values. |
Anchors
{
"progress": 0.5,
"value": 0.9,
"handleIn": { "dx": -0.2, "dy": 0 },
"handleOut": { "dx": 0.2, "dy": 0 },
"handleMode": "linked",
"interp": "bezier"
}
| Field | Type | Required | Notes |
|---|---|---|---|
progress | number | Yes | Position along the transition, 0 to 1. |
value | number | Yes | Normalized parameter value, 0 to 1. |
handleOut | object | No | Bezier control point leaving the anchor, as dx / dy offsets from it. |
handleIn | object | No | Bezier control point entering the anchor. dx is normally negative. |
handleMode | string | No | "linked" or "independent". Defaults to "linked". Only matters where an adjoining segment is bezier. See Linked vs Independent handles. |
interp | string | No | "bezier", "linear" or "hold". Defaults to "bezier". Governs the segment to the right of this anchor, so it is ignored on the last one. See Interpolation. |
An anchor with no handles is a plain bezier anchor with straight tangents. A two-anchor ramp usually carries none.
Step values
"stepValues": [
{ "label": "Off", "value": 0 },
{ "label": "On", "value": 1 }
]
| Field | Type | Notes |
|---|---|---|
label | string | 1 to 32 characters. The name shown on the canvas ladder. |
value | number | 0 to 1. The level this step sits at. |
While stepValues is present, every segment holds regardless of what its anchor's interp says, and anchor values snap to one of the levels. The stored interp is left untouched and takes effect again if the step set is removed. The last anchor is also released from the usual "must end at 1.0" rule, because a stepped lane holds its final level to the end. See Stepped Output.
Actions block
"actions": {
"before": [
{ "ref": "8f14e45f-ceea-467a-9a36-dedd4bea2543", "enabled": true }
],
"after": []
}
before runs during pre-run, ahead of the automation. after runs once the transition completes.
| Field | Type | Required | Notes |
|---|---|---|---|
ref | string | Yes | The action's meta.actionId, not a path. Fusor finds the file by looking the id up in the library, so the action's file can be renamed or moved into a subfolder and every fusor using it keeps working. |
enabled | boolean | No | Defaults to true. A disabled entry is skipped without being removed. |
preflight | object | No | Same three-field shape as a domain's preflight block. |
Entry order is run order. An entry whose action is missing or invalid is skipped and reported rather than stranding the run — including one whose id no longer matches any action in the library, which is what you get if the action was deleted. For the runtime behavior of both lists, see Pre-run Actions; for the .faction format itself, see the Action File Reference.
Validation rules
A fusor is validated when it loads and again when it is saved. The rules beyond the field types above:
Structure
versionmust be exactly"2.0".domainsmust hold at least one domain, and every domain at least one binding.- Every
instanceIdmust be unique within the file. domainTypeIdmust name a domain type that exists.- An
fx_unitdomain must carry aroleofincomingoroutgoingand anfxUnitIdfrom 1 to 4. - A binding's
targetmust be an address its own domain type owns. An EQ domain holdingincoming.bogus.xfails validation, so it never reaches MIDI time and silently does nothing. This applies to role addresses (incoming.eq.low), unit-less FX addresses (fx.slot1.enable) and deck-pinned addresses (deck.a.eq.low) alike. - A target that belongs to no domain type — a global such as
mixer.crossfader— must name an address the canonical mapping defines.mixer.zzzis well-formed and means nothing, so it is rejected rather than accepted and left dead. - Every entry in
meta.appsmust be a known app tag.
Curves
- Between 2 and 16 anchors.
- The first anchor must sit at
progress: 0. - The last anchor must sit at
progress: 1, unless the curve carriesstepValues. - Anchor
progressmust strictly increase. Two anchors at the same progress are rejected. handleModemust belinkedorindependent;interpmust bebezier,linearorhold.stepValues, when present, must hold 2 to 16 entries with a 1–32 characterlabeland avaluebetween 0 and 1.
A file that fails any of these does not load, and the error names the domain, binding and anchor it failed at. Nothing else changes when a fusor is refused: whatever was loaded stays loaded, and if it was armed it stays armed.
Worked example
The bundled Constant-Power Crossfade, in full. One Channel Fader domain with two sides — the file stores one domains entry per side, each with its own instanceId, and Studio groups them into a single row — each side carrying a binding whose two anchors and bezier handles approximate a cosine and a sine.
{
"version": "2.0",
"meta": {
"name": "Constant-Power Crossfade",
"author": "Fusor",
"created": "2026-06-12T00:00:00Z",
"description": "Canonical constant-power volume crossfade.",
"apps": []
},
"domains": [
{
"domainTypeId": "volume",
"instanceId": "vol_outgoing",
"enabled": true,
"bindings": [
{
"id": "out_volume_cos",
"target": "outgoing.volume",
"curve": {
"anchors": [
{ "progress": 0.0, "value": 1.0, "handleOut": { "dx": 0.4477, "dy": 0.0 } },
{ "progress": 1.0, "value": 0.0, "handleIn": { "dx": -0.4477, "dy": 0.5523 } }
]
}
}
]
},
{
"domainTypeId": "volume",
"instanceId": "vol_incoming",
"enabled": true,
"bindings": [
{
"id": "in_volume_sin",
"target": "incoming.volume",
"curve": {
"anchors": [
{ "progress": 0.0, "value": 0.0, "handleOut": { "dx": 0.4477, "dy": 0.5523 } },
{ "progress": 1.0, "value": 1.0, "handleIn": { "dx": -0.4477, "dy": 0.0 } }
]
}
}
]
}
]
}
"apps": [] makes it universal, so it is offered on every DJ app. Neither domain declares a role, because the binding addresses already say which side each drives.