Get the most out of your keyboard. Offline, open source, yours.
Read your Naya Create, change any key, layer, light or module, and flash it back. No account, no cloud.
Install · Quick start · Release notes · Knowledge base · Report a bug · Buy me a coffee
A Create on USB, both halves and their modules showing: pick a key, browse the action palette by layers, extended keys and app shortcuts, and switch the module profile a bay uses right on the board.
Naya has shut down, so its own app, NayaFlow, is no longer maintained, and the keyboards outlive it. OpenFlow is an independent, open-source rebuild that keeps the Create configurable. It talks to the keyboard directly over USB and needs nothing else: no account, no server, no internet.
| Feature | What it means for you |
|---|---|
| Read, back up, flash | Read what the keyboard holds, keep backups (automatic and by hand), and flash your changes back. Nothing is written until you press Flash, and the preview lists anything the keyboard cannot store. |
| OneKey: four behaviors per key | Tap, Hold, Double Tap and Tap + Hold on the same key, with the timing the keyboard uses. |
| Layers | Add layers and move between them with keys that hold, toggle, force or stick a layer. |
| LED Map | Color every key per layer with a brush, fill and pipette, see the board as the keyboard will light it, and pick a Solid, Breathe, Swirl or Spectrum animation. |
| Modules | Touch, Tune and Track profiles: taps, swipes, the dial and scrolling by finger count, with a module profile per layer. |
| Actions by name | Keys, mouse, media, Bluetooth, lighting and layer actions, plus shortcuts for 150 apps, searchable in one palette. |
| Devices | What each half runs and how charged it is, Bluetooth slots, a split-link check for a half that will not connect, and a read-only check of a half sitting in its bootloader. |
| Signed, and it updates itself | The Windows installer is signed and the macOS app is notarized. OpenFlow asks once whether to look for updates weekly, and installs one only when you click Install, never during a flash. |
OpenFlow ──USB──► Naya Create (each half is a USB serial port)
read what the halves hold: keymap, layers, lighting, module profiles, settings
edit on your computer, in OpenFlow's own database; nothing touches the keyboard
flash a preview of what will be written, then the write
- Read. Read from keyboard asks both halves what they hold and brings it into a profile you can edit. A board that has never been read starts from Naya's stock profile, so the app never opens empty.
- Edit. Bindings, layers, the LED Map and module profiles are edited offline. The keyboard does not need to be attached, and nothing is sent to it while you work.
- Flash. Flash to keyboard shows what will be written and lists anything the keyboard cannot store, then writes it. OpenFlow backs up its own data every 30 minutes, and Back up saves a copy whenever you like.
- Install OpenFlow for your system (Install).
- Plug in both halves over USB and open OpenFlow. The Hub shows the keyboard and its modules; press Configure, then Read from keyboard.
- Change a key. Click it on the board, pick a behavior (Tap, Hold, Double Tap, Tap + Hold) and choose an action from the palette below: type to search, or browse by Keyboard, Mouse, Layers, Apps and the rest.
- Light it. On LED Map, pick a color and brush or fill keys, layer by layer.
- Set up a module. On Modules, choose a Touch, Tune or Track profile and give each gesture an action. On the Bindings page, the bay icons above the board choose which module profile each bay uses on that layer.
- Flash to keyboard. Check the preview, then write it. To keep the setup you started from, press Back up before you change anything.
![]() |
![]() |
| The Hub | Bindings, with a Create on USB |
![]() |
![]() |
| LED Map | Modules |
OpenFlow can already do more than this release switches on. These parts stay off until each has been proven on more keyboards, and the app says so where they appear:
- Firmware updates for the keyboard and for modules. You can see what each half and module runs, browse the firmware library and plan an update; writing firmware is held back.
- Recovery procedures and the split-link repair's repair step. Every read-only check works: the split-link check, the repair's plan, and the bootloader check.
- Macros are app-only. You can record and keep them, but the keyboard's firmware has no macro table, so they cannot run on the keyboard yet.
Download the file for your system from the latest release.
- Windows 10 or 11:
OpenFlow-<version>-win-x64-setup.exeinstalls for your user, no administrator needed.OpenFlow-<version>-win-x64-portable.exeruns without installing (it cannot update itself). Windows shows the publisher as Travis Wye; SmartScreen may still ask for a few days after a new release while Microsoft's download reputation builds up. - macOS:
OpenFlow-<version>-mac-arm64.dmgfor Apple Silicon,-mac-x64.dmgfor Intel. Open it and drag OpenFlow into Applications. The app is notarized, so it opens on a double-click. - Linux: the
.debfor Debian, Ubuntu and their relatives, the AppImage for everything else. See Installing on Linux: the keyboard needs a udev rule.
Plug in both halves over USB and press Read from keyboard. The keyboard does not have to be attached to explore or edit; nothing is written to it until you flash.
Updates. The first time the Hub opens, OpenFlow asks whether to check for updates once a week.
A check downloads one small file from GitHub naming the newest release; nothing about you or your
keyboard is sent. An update installs only when you click Install, in the Hub or Settings › About,
and never while a flash is running. The portable build and a .deb install show a download link
instead.
Report a bug from inside the app (More › Report a bug), which attaches what it needs, or open an issue.
Each half of the keyboard is a USB serial port (/dev/ttyACM*) that belongs to root and the
dialout group, so a normal user cannot open it until OpenFlow's udev rule
(build/linux/70-openflow.rules) is installed. The rule also keeps ModemManager from probing the
keyboard, which otherwise collides with its protocol for several seconds after every plug-in.
- Debian, Ubuntu, Mint, Pop!_OS: use the .deb. It installs the rule and reloads udev:
sudo apt install ./OpenFlow-<version>-linux-amd64.deb, then start OpenFlow from the menu. - Anything else: use the AppImage (
OpenFlow-<version>-linux-x86_64.AppImage).chmod +xit and run it. The first time, the Hub shows a warning with the three commands that install the rule, and a button to copy them. Run them once; the keyboard appears on the next poll (unplug and replug it if not). The same commands:
printf '%s\n' 'ACTION!="remove", SUBSYSTEMS=="usb", ATTRS{idVendor}=="37d1", ENV{ID_MM_DEVICE_IGNORE}="1"' 'ACTION!="remove", SUBSYSTEM=="tty", ATTRS{idVendor}=="37d1", TAG+="uaccess"' | sudo tee /etc/udev/rules.d/70-openflow.rules >/dev/null
sudo udevadm control --reload-rules
sudo udevadm trigger --subsystem-match=ttyThe rule grants access to whoever is logged in at the machine's own screen (systemd-logind's
uaccess). Over SSH or on a system without logind, add yourself to the group instead and log
in again: sudo usermod -aG dialout $USER (uucp on Arch).
An AppImage that will not start usually means one of two things. On Ubuntu 22.04 and later
without libfuse2, run it with --appimage-extract-and-run or install libfuse2t64
(libfuse2 on 22.04). On Ubuntu 24.04 and later, AppArmor blocks Electron's sandbox for an
AppImage; the .deb installs the AppArmor profile that allows it, so prefer the .deb there.
The database is not in the repository. It goes to %APPDATA%/OpenFlow on Windows,
~/Library/Application Support/OpenFlow on macOS, $XDG_DATA_HOME/OpenFlow on Linux, or
wherever OPENFLOW_DATA_DIR points if you set it. So you can throw it away and re-seed freely:
OPENFLOW_DATA_DIR=/tmp/openflow-scratch python -m openflow_backend 3001 # seeds itself on first runNext to user-data.db you will find backups/ (the automatic and manual backups),
logs/backend.log (the backend's own log, rotated) and, when run as the desktop app,
logs/sidecar.log (what the shell captured from the backend process) and shell/ (Chromium's
cache and preferences, kept apart from your data on purpose). Uninstalling leaves all of it in
place.
OpenFlow ships read-first. Everything that writes beyond your keymap and lighting, the operations that can leave a half unable to start if they go wrong, is already built and tested, and switched off in this release. Each comes on in a later update once it has been proven on more keyboards, and some wait for the Create's firmware to be opened up.
Built, held back for now
- Keyboard firmware updates for both halves, up or down to any catalogued version, with the plan, the checks and the recovery steps built in.
- Module firmware updates and Force Update for the Touch, Tune and Track.
- Recovery procedures: restarting a half into its bootloader, unpairing, clearing split links, clearing keymap data, and formatting or erasing flash. Thirteen in all, each behind a confirmation.
- The split-link repair's repair step. Its check and its plan already run.
- Recovery-mode keymap flash for a board OpenFlow cannot read, and bringing a half out of its bootloader from the app.
Waiting on open firmware
- Macros that run on the keyboard. The current firmware reserves a macro type but has no macro table, so macros stay app-only until a firmware has one.
- A fifth OneKey behavior (Double Tap + Hold) and the timing options the firmware does not expose today.
- Community firmware: an open firmware and a community-signed bootloader, so the Create can keep improving after Naya.
Later
- More languages, and a tray showing the battery levels.
OpenFlow is an Electron shell around a Python backend (FastAPI) and a React renderer.
backend/ Python (FastAPI). Device protocol, flash planning, the action catalogue.
frontend/ React + Vite. The renderer -- this is the part to look at for styling.
electron/ Desktop shell. Spawns the backend, hosts the renderer.
docs/ Reference data the backend loads at runtime, plus notes.
device/ A few captured device reads, used as test fixtures.
No TypeScript; the renderer is plain JSX.
# backend (needs Python 3.12+)
cd backend
python -m venv .venv && .venv/Scripts/activate # or source .venv/bin/activate
pip install -e ".[dev]"
python -m openflow_backend 3001 # the port is POSITIONAL, not --port
# A first run seeds itself from the captured stock profile that ships in device/, so the app
# never opens empty. To start over, delete the database (see below) or re-seed by hand:
# python -m openflow_backend.seed --force
# frontend
cd frontend
npm install
npm run dev # http://localhost:5173npm run build runs tools/check-undefined.mjs first — it catches undefined hooks, components
and state setters, which render as a blank page rather than a build error.
The backend test suite runs without a keyboard attached:
cd backend && python -m pytest -q # runs against a temporary data dirThe frontend has its own gates: npm run verify (undefined-reference check, the style budget in
tools/style-budget.json, the vitest unit tests including the api-contract test against
backend/tests/route-manifest.json, then the build) and npm run e2e (a Playwright walk of every
page in both themes against a backend of its own on port 3011, seeded from the snapshot; it
leaves a screenshot per page under tests/e2e/shots/).
Nothing here talks to the keyboard unless you explicitly flash; the app is read-only until then, and it runs perfectly well with no keyboard attached -- which is the expected setup for design work.
The app is the Electron shell in electron/ around the backend frozen with PyInstaller
(backend/openflow_backend.spec) and the built renderer, which the frozen backend serves itself
at http://127.0.0.1:<port>/ so page and API share one origin. The root package.json drives it:
# once: Node 22+, the backend venv with the build extra, the shell's dependencies
cd backend && pip install -e ".[dev,build]" && cd ..
npm install
npm run build:win # frontend -> sidecar -> sidecar smoke test -> NSIS installer + portable exe
npm run build:mac # dmg (unsigned on a developer machine: right-click > Open the first time)
npm run build:linux # AppImage + deb (see "Installing on Linux" below)Artefacts land in release/ as OpenFlow-<version>-win-x64-setup.exe,
OpenFlow-<version>-win-x64-portable.exe, and so on. npm run smoke:sidecar starts the frozen
backend on a free port with a scratch data dir and checks every bundled resource; it runs inside
build:app so a broken bundle never reaches an installer. npm run start:dev runs the shell
against the Vite dev server and the venv backend (OPENFLOW_DEV=1).
The one version number is __version__ in backend/openflow_backend/__init__.py; npm run version:sync stamps it into the two package.json files and version:check (part of
build:app and CI) refuses to build when they drift.
Local builds are unsigned; release builds are signed. scripts/ci-signing.mjs runs before
electron-builder and, with the Apple secrets present (a Developer ID certificate and an App
Store Connect key), turns on signing and notarization for the two Mac rows; with the Azure
Artifact Signing secrets present it signs the Windows executables, the installer and the
portable build. Both sets are in place: Windows releases are signed since 0.6.0, and macOS
releases are signed and notarized from the first release after 0.6.0, so the dmg's app opens
on a double-click. Without the secrets (a fork, say) the build is unsigned, as before, and
Windows shows its "unknown publisher" prompt on first launch. A partial set of secrets fails
the job rather than shipping something that only looks signed. The script's header lists
every secret and variable by name; they are repository secrets here and in Create Companion,
whose release reads the same names.
The release workflow builds Linux on the oldest Ubuntu runner on purpose: the frozen backend needs a glibc at least as new as the build machine's, so the CI build runs on Ubuntu 22.04, Debian 12, Fedora 36 and anything newer. A build from a newer machine only runs on that machine's generation and newer; do not hand one to a tester.
frontend/src/styles/—app.css(shell, buttons, tokens) andeditor.css(the keyboard board, palette, LED views)frontend/src/components/KeymapBoard.jsx— the virtual keyboard, drawn as SVG key shapesfrontend/src/pages/— Bindings, Color, Modules, Macros, Troubleshooting
Colours come from CSS custom properties (--neutral*, --accent, --text, --border-strong),
so a theme is mostly a matter of redefining those.
docs/reference/app-shortcuts.json is derived from ShortcutMapper (MIT) — see
docs/reference/ATTRIBUTION.md, which must travel with it.
backend/openflow_backend/_vendor/nayactl is a vendored copy of nayactl.
Apache-2.0; see LICENSE.




