Universal Controller MIDI

Templates

Template versioning

How schema_version works, how Universal Controller MIDI migrates older templates, and why your v1 files still load on the latest build.

Updated

The schema_version field is non-negotiable. It tells the app which migration path to apply on load and gates marketplace uploads. Today's value is 2. Older v1 files still load — they get migrated in place on next save — but the marketplace only accepts current-schema uploads.

Treat schema_version like a database migration number: incremented exactly once when the shape of stored data changes, never edited by hand, never skipped. The whole system rests on it being correct.

Why a schema version at all

Without a version, every schema change is a breaking change. With one, the loader can detect 1, run a migration, and hand the app a 2-shaped object. Older files keep working — newer features stay accessible.

The cost of bumping is one switch statement in the loader. The cost of not bumping is angry users with broken libraries after every update. Version your data.

The v1 → v2 migration matrix

Here's every field that changed between schema versions, what it was, what it is, and how the migration fills the gap:

Fieldv1 shapev2 shapeMigration rule
schema_version12Bumped on first save
poll_hz30–250Default 100
deadzoneoptionaloptionalDefault 0.05 if missing
left_stick_corners{ notes: [...] }{ enabled, n, notes, r_enter, r_exit }enabled: true, n from notes length, hysteresis defaults 0.9 / 0.7
right_stick_cornerssame as leftenabled: false
haptics{ l2, r2 }split fieldsl2_haptic_effect + r2_haptic_effect
l2_haptic_effectstring|nullFrom haptics.l2 or null
r2_haptic_effectstring|nullFrom haptics.r2 or null
oscobjectenabled: false
touchpadobjectenabled: false

What changed in plain English

  • Stick corners — v1 had only left_stick_corners.notes. v2 adds n, r_enter, r_exit, and enabled.
  • Haptics — v1 had a single haptics object. v2 splits into l2_haptic_effect and r2_haptic_effect.
  • OSC — v1 had no OSC at all. v2 introduces the osc block.
  • Poll ratepoll_hz was fixed at 60 in v1; v2 lets you override per-template.
  • Touchpad — v1 routed touchpad through axes implicitly; v2 gives it a dedicated block.

Migration is automatic

When the app loads a v1 file it rewrites it in place to v2 on next save. You don't have to do anything. The migration is lossless — every v1 field has a v2 equivalent.

{
  "name": "Old File",
  "schema_version": 1,
  "midi_channel": 0,
  "buttons": { "0": 36 },
  "left_stick_corners": { "notes": [60, 62, 64, 65, 67, 69, 71, 72] },
  "haptics": { "l2": "trigger_resist", "r2": null }
}

...becomes...

{
  "name": "Old File",
  "schema_version": 2,
  "midi_channel": 0,
  "deadzone": 0.05,
  "poll_hz": 100,
  "buttons": { "0": 36 },
  "left_stick_corners": {
    "enabled": true,
    "n": 8,
    "notes": [60, 62, 64, 65, 67, 69, 71, 72],
    "r_enter": 0.9,
    "r_exit": 0.7
  },
  "right_stick_corners": { "enabled": false },
  "touchpad": { "enabled": false },
  "osc": { "enabled": false },
  "l2_haptic_effect": "trigger_resist",
  "r2_haptic_effect": null
}

Forward compatibility

If a future v3 ships and you open a v3 file on an older v2 build, the app refuses to load it rather than guess. The status strip shows a clear schema too new warning. Update the app, the template loads. Forward-incompatible failures are loud on purpose — silently dropping fields you don't understand corrupts user libraries.

Never hand-edit schema_version upward without adding the new fields. The loader trusts the number — set it to 2 on a v1-shaped file and corners will silently break.

If you maintain a personal library of templates across multiple machines, commit them to a private git repo. The schema_version + migration model means diffs stay clean across app updates — your library survives every version bump.

Marketplace and versioning

The marketplace only accepts uploads with the current schema_version. v1 files are migrated client-side before upload, so authors don't have to think about it. The Supabase bridge_presets row stores the migrated v2 JSON, never the original v1 — so the day v3 lands, the marketplace bulk-migrates everything on the server side and you're already current. See share to the marketplace.

Real-world template patterns around versioning

  • Legacy library upgrade — drop your v1 folder into Settings → Reveal library, the app migrates everything on first load.
  • Git-tracked personal library — commit the JSON after migration, diff future schema bumps cleanly.
  • Marketplace re-share — if you find your own v1 template floating around online, re-import locally to v2, then re-upload with the current schema.
  • Cross-version testing — when authoring a template for marketplace, open it on the oldest supported app version to verify the migration is symmetric.
  • Pinning a schema — never edit schema_version downward. To roll back, restore from backup, don't manually decrement.
Edit this page on GitHub Updated
ESC

Type to search.