Keymap records¶
This page describes what a layer's key data looks like on the wire: the 156 positions of a layer, the record format, every record type, the two hold-tap forms, the second bank that carries double tap and tap and hold, and the module bay records that sit in the middle of the key data. It also covers how NayaCore and other writers change a layer, and the traps that have corrupted real boards. The one thing to know first: a layer read is lossless only if the reader keeps both banks and the bay records; a writer that drops either one corrupts the board silently.
At a glance
- One layer = 156 positions, all stored on the left half: keys
00-49, module bays4a-51, second bank52-9b. - Every record is
[position][type][len][param]; reads and writes use the same format. 03and10are the two hold-tap forms; the flavor byte and the tapping term live inside each record, not in a device setting.- A key's double tap and tap and hold sit in its second-bank record,
0x52positions later. 30/1004writes take effect at once and persist; there is no commit and no undo.
Byte strings follow the site's byte convention. On this page,
records are quoted as record bytes [position][type][len][param] (no flag byte and no layer
byte) unless a string is labeled "params"; pp stands for any position. Positions written in
prose ("position 62") are decimal; inside byte strings they are hex (62 = 3e). Physical key
positions and labels belong to Layout and positions.
Positions¶
One layer covers the whole keyboard, both halves. Positions 00-49 are the 74
keys, 4a-51 are the eight module bays, and 52-9b are the second bank (one extra record per
key, keys only), so a layer has 82 + 74 = 156 positions. All of it is stored and served by the left
half. MEASURED 3.41.0, 2026-09
| Positions | What they hold |
|---|---|
00-49 |
the 74 keys (tap and hold) |
4a-51 |
the eight module bays (a config slot number, not a binding; see Bays) |
52-9b |
the second bank: double tap and tap and hold for key p at p + 52 |
The position byte is the key's index and equals NayaFlow's database position_id
MEASURED 3.41.0, 2026-09.
Layers are not fixed at three. They come from the layer list and can be added and
deleted (NayaFlow created layers 3 and 4 in one captured flash); NayaCore names a MAX_LAYERS
constant whose value we have not recovered. See Layers.
MEASURED 3.41.0, 2026-09-01 + STATIC2
Layer 0 of the stock profile starts along the top row with Esc (29), Grave (35),
1 (1e) and 2 (1f): the stock read on the owner's board begins 00 01 04 29 00 07 00,
01 01 04 35 00 07 00, 02 01 04 1e 00 07 00, 03 01 04 1f 00 07 00.
MEASURED 3.41.0, 2026-09-01 + STATIC
(NayaFlow's default template3).
The record format¶
Every record is [position][type][len][param x len], so a record is len + 3 bytes.
Writes use exactly the read format. The rule closes over every layer read we have made, whether the
layer returned 82 or 156 records.
MEASURED 3.41.0, 2026-09-01 to 2026-09-17.
The type byte is an index into NayaCore's behavior table: 00 bluetooth, 01
key_press, 02 macro, 03 mod_tap, 04 grave_escape, 05 mo, 06 naya_integrations, 07 none,
08 outputs, 09 rgb_ug, 0a sticky_key, 0b sticky_layer, 0c to_layer, 0d toggle_layer,
0e trans, 0f mouse. 10 (the hold-tap used for multi-behavior keys, "OneKey" in OpenFlow) sits
outside that table, and 78 appears only at bays. Eight of the indices were matched against
captured records. STATIC2 + MEASURED 3.41.0, 2026-09
| Type | NayaCore name | ZMK behavior | Param | Example record | Evidence | In NayaFlow |
|---|---|---|---|---|---|---|
00 |
bluetooth | &bt |
[command u32 LE][argument u32 LE] |
pp 00 08 03 00 00 00 01 00 00 00 (device 1) |
MEASURED | action type "bluetooth" |
01 |
key_press | &kp |
[usage lo][usage hi][page][mods] |
22 01 04 07 00 07 00 (D) |
MEASURED | key action |
02 |
macro | ¯o |
accepted, does nothing | (none flashed) | MEASURED | macro (flashed as 07 00) |
03 |
mod_tap | &mt / < |
the 21-byte hold-tap body | stock layer-tap at 07, 08 |
MEASURED | a key with tap and hold |
04 |
grave_escape | &gresc |
no serializer | (cannot be flashed) | STATIC | none |
05 |
mo | &mo |
target layer u32 LE | pp 05 04 01 00 00 00 |
MEASURED | Hold Layer |
06 |
naya_integrations | (vendor) | command u32 LE | 3e 06 04 91 01 00 00 |
MEASURED | action type "naya" |
07 |
none | &none |
none | pp 07 00 |
MEASURED | an unbound key |
08 |
outputs | &out |
selector u32 LE | pp 08 04 02 00 00 00 |
MEASURED | BT_OUT, USB_DEVICE |
09 |
rgb_ug | &rgb_ug |
[subcommand u32][argument u32] |
see LEDs | MEASURED | action type "LED" |
0a |
sticky_key | &sk |
no serializer | (cannot be flashed) | STATIC | none |
0b |
sticky_layer | &sl |
target layer u32 LE | pp 0b 04 01 00 00 00 |
MEASURED | layer_polite_oneshot |
0c |
to_layer | &to |
target layer u32 LE | pp 0c 04 02 00 00 00 |
MEASURED | layer_rude_toggle |
0d |
toggle_layer | &tog |
target layer u32 LE | pp 0d 04 01 00 00 00 |
MEASURED | layer_polite_toggle |
0e |
trans | &trans |
none | pp 0e 00 |
MEASURED | a new layer's unassigned key |
0f |
mouse | (vendor) | [category u32 LE][selector i32 LE] |
2e 0f 08 03 00 00 00 01 00 00 00 (left click) |
MEASURED | mouse button |
10 |
(outside the table) | hold-tap with term | [term u16 LE][03] + body |
49 10 18 c8 00 03 ... |
MEASURED | tap, hold, double tap, tap and hold |
78 |
(bays only) | none | 4a 78 00 |
MEASURED | a bay that follows layer 0 |
Key press parameters¶
A 01 KEY_PRESS record carries four param bytes, [usage lo][usage hi][page][mods]:
ZMK's u32 (mods<<24)|(page<<16)|usage, stored little-endian. Page 07 is the keyboard page and
0c the consumer page; NayaCore's key table also uses page 01 (Generic Desktop) for
SYSTEM_POWER, SLEEP and WAKE_UP (81-83). Worked record: 22 01 04 07 00 07 00 is position 22,
key D. MEASURED 3.41.0, 2026-09.
mods is the HID modifier bitmap: 01 LCtrl, 02 LShift,
04 LAlt, 08 LGui, 10 RCtrl, 20 RShift, 40 RAlt, 80 RGui. A bare modifier key is its own
usage (e0-e7) with mods 0; a shifted glyph is the base usage plus LShift () is key 0 plus
02). MEASURED 3.41.0, 2026-09. In the stock profile the only
records with modifiers are layer 1 positions 1c and 1d, the parentheses: 26 00 07 02 and
27 00 07 02 (param bytes). MEASURED 3.41.0, 2026-09-01. Edited profiles carry other modifiers, and so do stock
module fields.
Consumer usages seen on the wire include b5 Next, b6 Previous, cd Play/Pause, e2
Mute, e9 Volume Up and ea Volume Down. In the HID Consumer
page b6 is Scan Previous Track and Mute is e25, and the stock Tune fields agree (e2 00 0c 00 for mute, b6 00 0c 00 for previous).
MEASURED 3.41.0, 2026-09 + DOC
A key press of four zero bytes (pp 01 04 00 00 00 00) presses nothing and is not
the same record as NONE (07 00). The board carries it on an unset hold-tap half and on the two
stock Tune gestures NayaFlow labels LED Brightness Up and Down, where it gives no HID output and no
LED change. What it does on a key is open. MEASURED 3.41.0, 2026-09
Modifier-only records exist. NayaFlow flashes "LALT + CLICK" as usage 0, page 0, mods
04 (param 00 00 00 04). A bracketed modifier in NayaFlow's chord syntax ("[LALT] + TAB") marks a
key that is already held and does not set its bit (2b 00 07 00). NayaFlow stores "LALT + ENTER" as
00 00 00 04, losing the Enter: a vendor bug. MEASURED NayaFlow
1.25.1 captures on 3.41.0, 2026-09
The international usages 87-8f and the LANG usages 90-98 are standard and
encodable. NayaCore's 249-entry key table (in ZMK order) adds PIPE2 = LShift + NON_US_BACKSLASH and
TILDE2 = LShift + NON_US_HASH. STATIC2
Record types in detail¶
02 macro. The firmware reserves the type and the macro opcodes but keeps no macro
table: a 02 record is accepted on read-back and does nothing. NayaCore flashes a macro binding as
07 00 (see Command map). MEASURED 3.41.0, 2026-09-07
05 MO (momentary layer, "Hold Layer"). The param is the target layer, u32 LE:
pp 05 04 01 00 00 00 holds layer 1. Stock layer 0 uses 05 layer 1 on the two thumb keys at
positions 67 and 68 (LH4, RH4) and 05 layer 2 on the two bottom-corner keys at 62 and 73 (LA5,
RA5, "hold System" in the vendor template), whose keycap print differs from the action. On the
owner's board the stock read holds 43 05 04 01 00 00 00, 44 05 04 01 00 00 00,
3e 05 04 02 00 00 00 and 49 05 04 02 00 00 00. MEASURED 3.41.0,
2026-09-01 + STATIC (template3).
06 Naya system action. The param is a u32 LE command. The only value known on the
wire is 401 = MODULE_FORCE_CHARGING, on the stock System layer at position 62:
3e 06 04 91 01 00 00 (NayaFlow: "Activate Module Recovery Mode"; see Modules).
NayaCore's table also names TUNE_MODE_L / TUNE_MODE_R (150 / 151), WINDOWS_OS (200), MAC_OS (201),
SCROLL_DIRECTION_L / _R (300 / 301) and MODULE_CHARGING (400); none has been seen on a board.
MEASURED 3.41.0, 2026-09-08; 3.28.7, 2026-09-19 +
STATIC2. NayaFlow's key palette offers no MAC_OS action to
flash: its only naya action is MODULE_FORCE_CHARGING, MAC_OS appears only as an icon (the MacOS tab and the OS badges),
and its macOS-labelled entries are ordinary keys and chords, such as Meta (LGUI, labelled Command
on macOS) and LGUI + C STATIC3.
07 NONE (&none) has no param. Every unbound position reads back as 07 00; a
board read returns about 220 NONE records.
MEASURED 3.41.0, 2026-09. NayaCore's record reader treats a 07 tap as
DISABLED STATIC14.
08 outputs (&out) takes a u32 LE selector: 1 = USB, 2 = wireless. The stock
System layer holds pp 08 04 02 00 00 00 (BT_OUT) at positions 47 and 71 and pp 08 04 01 00 00 00
(USB_DEVICE) at 48 and 72. Decoding 08 as a layer switch and flashing the result back turned those
keys into "force layer 2 / 1": a real corruption. MEASURED 3.41.0,
2026-09-08
0b sticky layer, 0c TO layer ("force"), 0d TOGGLE layer are all live on the
wire, each with the target layer as a u32 LE param. A probe profile read back four sticky records,
and a 2026-09-10 board read held seven TO and four sticky records among 182, all round-tripping byte
for byte. NayaFlow calls them layer_polite_oneshot, layer_rude_toggle and
layer_polite_toggle. MEASURED 3.41.0, 2026-09-08, 2026-09-10.
0e TRANS (transparent) has no param. NayaCore writes a new layer's unassigned keys
as 0e 00. It picks 07 or 0e for an unassigned key from the profile's "transparent as default"
setting: its logs print "transparent as default" per profile and "(Transparent from default)" or
"(Transparent from settings)" per key. MEASURED 3.41.0, 2026-09-01 +
STATIC2.
00 bluetooth (&bt) takes [command u32 LE][argument u32 LE]. (3, n) selects
Bluetooth device n, ONE-based: keys Dev 1-4 read back (3, 1) to (3, 4). (0, 0) is BT_CLEAR, a
real record, not NONE. (1, 0) next and (2, 0) previous come from NayaCore's table and have never
been read off a board. BT_DEVICE_1 as a record: pp 00 08 03 00 00 00 01 00 00 00.
MEASURED 3.41.0, 2026-09-08, 2026-09-10 +
STATIC2
09 rgb_ug (LED keys) takes [subcommand u32][argument u32]; the subcommands and
colors are on LEDs. MEASURED 3.41.0, 2026-09-08
0f two-word takes [category u32 LE][selector i32 LE]. On a key, category 3 with a
mouse-button mask (1, 2, 4, 8, 16 = M1-M5) makes a mouse button: 2e 0f 08 03 00 00 00 01 00 00 00
gave a real left click at position 46. A short param (pp 0f 04 01 00 00 00) is stored, read back
verbatim, and ignored. The categories are on Module fields.
MEASURED 3.41.0, 2026-09-07
03 and 10 are the two hold-tap forms (next section). 03 is the general
two-behavior hold-tap (home-row modifiers, the stock layer-tap).
MEASURED 3.41.0, 2026-09
04 grave_escape and 0a sticky_key are named in NayaCore's table but have no
serializer, so nobody can flash them. STATIC2
The firmware validates the type: a record of unknown type 7e was dropped and the
previous record survived. A stored record is still not necessarily live (the short 0f param above).
MEASURED 3.41.0, 2026-09-07
Hold-tap records and the second bank¶
The hold-tap body is 21 bytes:
| Offset in body | Bytes | Field |
|---|---|---|
| 0 | 1 | hold kind (01 key press, 05 momentary layer) |
| 1 | 1 | tap kind |
| 2 | 1 | flavor (Interrupt Flavor) |
| 3-4 | 2 | tapping term, u16 LE (c8 00 = 200 ms) |
| 5-8 | 4 | hold param |
| 9-12 | 4 | 00 00 00 00 |
| 13-16 | 4 | tap param |
| 17-20 | 4 | 00 00 00 00 |
A type 03 record's param is the body (len 15, a 24-byte record). A type 10 record's param is
[term u16 LE][03] followed by the same body (len 18, a 27-byte record), so the term appears twice.
MEASURED 3.41.0, 2026-09
The two kind bytes are per-slot behavior kinds, not a constant 01 01: 01 is a key
press and 05 a momentary layer (the hold param is then the layer index). The stock layer-tap (hold
for layer 2, tap Return or Backspace, positions 7 and 8) is a 03 record whose body is
05 01 00 c8 00 | 02 00 00 00 | 00 00 00 00 | 28 00 07 00 | 00 00 00 00, i.e. the whole record
07 03 15 05 01 00 c8 00 02 00 00 00 00 00 00 00 28 00 07 00 00 00 00 00. Only kinds 01 and 05
have ever been seen, and the 10 form with a layer hold never. MEASURED
3.41.0, 2026-09-21, checked on a board 2026-09-22
Never sweep the flavor byte
Writing a flavor of 04 or higher into any hold-tap record is accepted and stored, and then
every key stops working until the board is unplugged (a power cycle). Rewriting the record does
not help while it is powered. Measured on 3.41.0, 2026-09-03.
The byte after the two kinds is the flavor, NayaFlow's Interrupt Flavor setting. Valid
values are 0-3; writing 04 or more stops every key until the board is unplugged. 02 is
tap-preferred. MEASURED 3.41.0, 2026-09-01, 2026-09-03. 00 and
01 follow ZMK's order: in a home-row typing test, with 00 holds fired on fast rolls
(hold-preferred), and with 01 fast rolls typed cleanly (balanced)
MEASURED owner's board, 3.41.0, 2026-09-2515. NayaFlow
writes its default "Balanced" as 00, so a stock flash types as hold-preferred; 03 is not
measured. See Settings and timing.
The tapping term lives only inside hold-tap records, per record and per bank. Moving
NayaFlow's slider from 200 to 180 ms rewrote c8 00 to b4 00 in every hold-tap record on every
layer; untouched second-bank records kept an older 200 / flavor 1 while their primaries were
rewritten at 180 / 0. Both copies in a 10 record carry the same term. No double-tap window,
hold-start, wait or overlap parameter exists anywhere on the wire.
MEASURED NayaFlow 1.25.1 capture on 3.41.0, 2026-09-17.
The 03 byte inside every 27-byte 10 param is the inner record type: the 10
wrapper holds [term u16][inner type][inner param], and a bank with two behaviors is a 03 pair
(KM-49), so the 27-byte form is the wrapper around a 03 record STATIC14.
It has read 03 in every capture of a two-behavior bank MEASURED 3.41.0, 2026-09
A key's four behaviors are two records 0x52 apart. The primary (position p) holds tap
and hold; the second-bank record (p + 52) holds double tap in its tap slot and tap and hold in its
hold slot. A captured NayaFlow write of one key, as record bytes:
49 10 18 c8 00 03 01 01 00 c8 00 1d 00 07 00 00 00 00 00 05 00 07 00 00 00 00 00
9b 10 18 c8 00 03 01 01 00 c8 00 1c 00 07 00 00 00 00 00 1b 00 07 00 00 00 00 00
Pressing it gave b, zzz, x and yyy for tap, hold, double tap and tap and hold.
MEASURED NayaFlow 1.25.1 on 3.41.0, 2026-09-03.
| Record | Hold slot | Tap slot |
|---|---|---|
primary, position p |
hold | tap |
second bank, position p + 52 |
tap and hold | double tap |
Double tap and tap and hold fire only if the PRIMARY record waits for a second tap. A
plain 01 primary with a double-tap second-bank record typed the tap twice ("aa") and never the
double-tap binding; a 10 primary with an empty hold half (four zero bytes) fixed it.
MEASURED 3.35.4 or 3.41.0 (the board was upgraded between them
that week), 2026-09-21. NayaCore's own form, the tap alone inside the 10 wrapper (KM-49), also
works MEASURED 3.41.0, 2026-09-26. Whether a 03 primary lets the
second bank fire is untested.
An empty slot half (four zero bytes) means "not bound" in a key-press slot but "layer 0" (a real target) in a layer slot. MEASURED 3.41.0, 2026-09
A key with only a tap and a hold is a 03 record, like the stock layer-tap; only a key
that also has a double tap or a tap and hold gets the 10 wrapper. NayaCore's serializer draws that
line (KM-49) STATIC14, and so does the owner's board as
NayaFlow left it (KM-50): every key with a tap and a hold was 03 except 35, the one with a
second-bank record. The two forms do not type alike: the 10 form's tap waits for a possible double
tap, so in a home-row typing test 10 records at flavor 01 garbled 5 of 5 fast sentences, while
03 records at flavor 01 typed 6 of 7 cleanly MEASURED owner's
board, 3.41.0, 2026-09-2515. A writer that emits 03 where the board holds 10 (or the reverse) makes every later flash rewrite
that key. MEASURED 3.41.0, 2026-09
NayaCore's key serializer decides the record shapes. A bank that holds two behaviors is
one 03 pair, a bank that holds one is that record alone, and when the key has a double tap or a
tap and hold both banks go inside the 10 wrapper; a key without either gets no second-bank record
at all, so a stale second-bank record stays when the key drops back to a plain press
STATIC14. So a key with only a tap and a hold is a single 03 record. A key that adds
a double tap is a [pp] 10 18 ... primary that wraps the tap and hold pair, plus a 10-byte
second-bank record holding the double tap alone, [pp+52] 10 07 c8 00 01 <key press> (term 200,
then the record type 01 and its four-byte param); so NayaCore is the writer of that 10-byte record
(closed question). A key with all four behaviors has a full
27-byte 10 record in both banks, as our USB capture shows MEASURED 3.41.0,
2026-09-03. Keys written in these forms typed as set and read back unchanged
MEASURED 3.41.0, 2026-09-26. NayaCore prints the same shape for a key in its own logs
(wire: 351007c800012a000700) STATIC2. NayaFlow's editor enforces the order tap,
hold, double tap, tap and hold: in its renderer the hold slot requires the tap slot, the double-tap
slot requires both of those, and the tap-and-hold slot requires all three STATIC3.
The hold-tap keys of layer 0 as read from the owner's board on 2026-09-01 (term 194,
flavor 0): 20 hold LCtrl / tap A (03); 21 hold LShift / tap S (03); 25 hold Backspace /
tap Delete (03); 35 hold LCtrl / tap Backspace (10); 3f hold LCtrl / tap LGui (03); 43
hold LAlt / tap Return (03); 87 is the second bank of 35.
MEASURED 3.41.0, 2026-09-01
NayaFlow's verify read-back renders every record at term 200 and flavor 1 (its defaults), so a board whose records carry another term shows "differences" that have no effect on typing. MEASURED NayaFlow 1.25.1 on 3.41.0, 2026-09-17
Module bays in layer data¶
At positions 4a-51 the type byte is a module-config SLOT number and the length is 0:
N means "use config slot N", 78 means "inherit from the BASE layer (layer 0)", and 00 means
disabled. Details are on
Modules. MEASURED 3.41.0, 2026-09-03
| Position | Bay | Position | Bay |
|---|---|---|---|
4a |
Touch left | 4e |
Tune left |
4b |
Touch right | 4f |
Tune right |
4c |
Track left | 50 |
Float left |
4d |
Track right | 51 |
Float right |
4b 02 00 is the Touch-right bay record: "the right Touch uses module-config slot 2".
It changes when a module assignment is flashed, not when a 10 key is added, and setting 4b to
02 by hand repoints that bay at whatever slot 2 holds. MEASURED
3.41.0, 2026-09-03. On the owner's board every bay on layers 1 and 2 held 78
MEASURED 3.41.0, 2026-09-01.
A bay decoded through the behavior table invents bindings: slot 1 reads as a key press
and slot 5 as a layer hold with no target. Decoders must treat 4a-51 separately, and a
full-layer writer must carry the board's bay bytes through, or every module becomes unassigned.
MEASURED 3.41.0, 2026-09 (a real corruption in one tool, since fixed)
Reading a layer¶
Read-only
30/1003 reads change nothing. Layer 0, first chunk:
aa 00 50 00 30 04 10 03 00 00 13 04; every further chunk:
aa 00 50 00 30 04 10 03 01 00 12 04. The chunk loop is on Transport.
How long a read is depends on the firmware and on what is stored. 3.35.4 never returns
the second bank: the read stops at 51 MEASURED 3.35.4,
2026-09-21/22. On 3.41.0 the owner's board returned the whole bank, padded with 07 00 (156
records, highest 9b), even for layers that had no second-bank record at all
MEASURED 3.41.0, 2026-09-01. A third-party 3.41.0 capture of
2026-09-15 returned 82 records per layer for layers that apparently never had a second bank written,
and 156 after NayaFlow flashed them REPORTED
(raw data checked4). So the length most likely follows what is stored, not only the firmware
INFERRED. A reader that stops at 51 drops every double tap and
tap and hold; treat an absent position as NONE when comparing.
A freshly created layer that was never written already reads 156 records (148 unbound),
has its own 136-entry LED map, and has all eight bays at 00.
MEASURED 3.41.0, 2026-09-03
Writing¶
Writes are live and persistent
Every 30/1004 write takes effect at once and survives a reboot; there is no commit and no
undo. Read and save the whole layer (both banks and the bays) before editing, and send REMAP
writes to the left half only.
NayaCore writes changes as sparse diffs: only changed records, several per frame. Layer
1, two keys: params 00 01 24 01 04 04 00 07 00 34 01 04 1e 00 07 00, ack 00 01. A NEW layer is
written in full: 82 records 00-51, unassigned keys 0e 00, bays 78 00; writes stop at 51
while reads pad to 156. MEASURED NayaFlow 1.25.1 on 3.41.0,
2026-09-01.
One key, as a whole frame (layer 0, position 20, key B), with its ack:
This frame is computed, not captured; its shape is the one NayaFlow sends.
Length-changing writes apply: a 7-byte 01 record replaced by a 27-byte 10 record plus
its second-bank record, and back again, both read back correctly with a plain 30/1004.
MEASURED 3.41.0, from 2026-09-03.
When a key loses its double tap and tap and hold, its second-bank record must be
written too. NayaCore writes no second-bank record for such a key (KM-49), so the stale record stays
stored and NayaFlow's verify fails on every flash. Behind a plain 01 primary it no longer fires
(KM-46); behind a 03 primary that is untested. The proven clear is [pp+52] 07 00 (NONE):
orphaned second-bank records cleared this way let NayaFlow's next flash verify. MEASURED 3.41.0, 2026-09-09. This differs from
naya-create-kb, which clears with [pp+52] 00 00 REPORTED1: on a
key position type 00 is the Bluetooth record, and with zero params it is BT_CLEAR (KM-25), so a
zero-length 00 there may act as one INFERRED; it is untested.
The layer byte is mandatory in 30/1004 params: 00 <layer> followed by the records.
Our own captures always carry it. Reads of layer indices that do not exist (81, e4, e5)
answered 19 <index> MEASURED 3.41.0, 2026-09-01, so a write without its layer
byte most likely has its first position read as a layer index: for a position that is not a layer
it answers 19 with that position and applies nothing, and for one that is (00 up to the highest
layer) the effect is untested INFERRED.
30/1004 writes take effect immediately and persist; there is no commit step.
MEASURED 3.41.0, 2026-09.
Layer sizes vary with the bindings. The stock NayaFlow profile reads 764 / 636 / 660 bytes for layers 0 / 1 / 2 on the owner's board MEASURED 3.41.0, 2026-09-01. Whatever the size, the record lengths must close exactly.
Never bind TOGGLE to layer 0
A 0d record that toggles to the base layer strands the keyboard on the current layer until a
power cycle.
A 0d toggle record with target 0 (toggle to base) does not deactivate the layer you
stand on; the keyboard stays stranded until a power cycle. NayaFlow removes the base layer from its
Toggle target list. MEASURED 3.41.0, 2026-09 +
STATIC3
Adding a 10 key writes its primary and its second-bank record and nothing else; the
bays stay as they are MEASURED 3.41.0, 2026-09. This differs from naya-create-kb,
whose writer recipe also sets 4b to 02, which repoints the right Touch's bay at config slot 2
(see Module bays) REPORTED1.
NayaCore's serializer checks the record length for key_press, to_layer, toggle_layer, mo, sticky_layer, bluetooth, rgb_ug, outputs, naya_integrations and mouse, and none for sticky_key, grave_escape and macro; a mod_tap needs at least 21 bytes and is rejected when a half is missing. STATIC2
The stock default profile¶
NayaFlow 1.25.1's default Windows template ("Naya Default Windows") has three layers: Typing (base), Keypad + Arrow Keys, and System. STATIC3 + MEASURED (positions, 3.41.0, 2026-09-10)
| Layer | Contents |
|---|---|
| 0, Typing | Esc, Grave, the number row; positions 7 and 8 tap Return / Backspace and hold System (03 layer-tap); thumbs Space, Backspace, Return; two keys hold layer 1 (67, 68); two keys hold System (62, 73) |
| 1, Keypad + Arrow Keys | keypad and arrow keys; the parentheses at 1c / 1d are the only shifted keys |
| 2, System | C_POWER, BT_DEVICE_1-4 and BT_CLEAR, LED effect and color keys, media keys, BT_OUT and USB_DEVICE, MODULE_FORCE_CHARGING at 62, PrintScreen; a full set of connection keys on EACH half (left 2-6, 47, 48; right 15, 29, 45, 61, 73, 71, 72) |
The other templates are Naya Default MacOS, Naya Japanese Windows and Naya Japanese MacOS, each also as a "(07-18-2026)" variant.
Position numbering (decimal) follows the vendor's key labels row by row: 0 LA1 ... 7 LH1, 8 RH1 ... 15 RA1; 16-22 LA2-LG2, 23-29 RG2-RA2; 30-36, 37 LH2, 38 RH2, 39-45; 46-52, 53 LH3, 54 RH3, 55-61; 62 LA5, 63 LB5, 64 LC5, 65 LD5, 66 LE5, 67 LH4, 68 RH4, 69 RE5, 70 RD5, 71 RC5, 72 RB5, 73 RA5. So the MODULE_FORCE_CHARGING key at 62 is LA5, and the MO keys at 67 / 68 are the LH4 / RH4 thumbs. Full geometry is on Layout and positions. STATIC (vendor labels6) + MEASURED (stock positions, 3.41.0, 2026-09-10)
History¶
Remapping arrived late. The Batch 0 tester firmware (shipped 2025-01) had a fixed QWERTY layout and no remapping, and the first Flow build could only flash firmware11. "Naya Flow Version 1" brought key remapping12. The release notes date remapping to NayaFlow 1.3.8 (press only, no combinations on one key); Double Tap and Tap and Hold arrived in NayaFlow 1.25.0 with NayaCore 6.11.0; keyboard 3.28.7 fixed hold-tap keys sticking when two activated together7. DOC
The keyboard firmware is a customized ZMK fork. The Kickstarter campaign (2023) says
remapping is done "between our custom ZMK firmware and Naya Flow"8, update 9 (2023-12-07)
says the firmware is "based on a customized ZMK base" with features ZMK lacks10, and the FAQ
said building and flashing one's own ZMK layout was "not available for now"9. The wire
records follow ZMK's behaviors[^zmk]: the u32 key encoding, &out (type 08), &rgb_ug (type 09)
and the hold-tap flavors. The fork's source has not been published; see ZMK.
DOC + STATIC
Writer checklist¶
Each item below caused a real corruption or a silent failure in one open-source tool (OpenFlow13) before it was fixed. MEASURED 3.41.0, 2026-09
| Trap | Rule |
|---|---|
08 read as a layer switch |
08 is &out: 1 USB, 2 wireless |
| zero-based Bluetooth devices | (3, n) is one-based |
| bays decoded as bindings | carry 4a-51 through untouched |
a reader that stops at 51 |
keep the second bank; treat absent positions as NONE |
03 versus 10 |
write the form the board already holds |
| four zero bytes | "unbound" in a key-press slot, layer 0 in a layer slot |
| a stale layer reference that defaults to 0 | it flashes "switch to base" |
| continuation acks | accept status 01 on non-final frames |
| another program on the port mid-write | hold the port for every frame of a write |
| an unfamiliar status | it is not "empty"; see the status table on Transport |
A read-then-write round trip can corrupt a board silently when the decoder is wrong (the 08,
bay and second-bank traps above): re-encode what you read and compare it with the raw bytes before
writing anything back.
Open questions¶
- OPEN Whether flavor
03is tap-unless-interrupted, as ZMK's order predicts (00,01and02are measured) (details). - OPEN Whether a per-key or per-bank tapping term behaves differently from the global one (details).
- OPEN Why some 3.41.0 layers read 82 records and others 156 (details).
- OPEN What an empty key press (
01 04 00 00 00 00) does on a key; on the Tune's two LED gestures it does nothing (details). - OPEN What a zero-length
00record on a key position does (see Writing). - OPEN Hold-tap slot kinds other than
01and05; a10record with a layer hold (details). - OPEN The value of
MAX_LAYERS(details).
Sources¶
-
naya-create-kb, protocol/keymap (commit 7668067). ↩↩
-
NayaFlow 1.25.1, NayaCore 6.11.0 strings (static reading): the behavior table, key table, Naya system action names, serializer checks and log formats. ↩↩↩↩↩↩↩↩↩
-
NayaFlow 1.25.1 renderer and flow-bg-server strings, and its default templates (static reading). ↩↩↩↩↩↩
-
The naya-create-kb maintainer's published captures and dumps (USB CDC capture logs and device dumps, including a factory-default layer dump of 2026-09-15 and two probe dumps of 2026-09-17); raw data decoded by us, never copied. ↩
-
USB-IF, HID Usage Tables (Consumer page:
b5Scan Next Track,b6Scan Previous Track,cdPlay/Pause,e2Mute). ↩ -
create-legacy-firmware, vendor release notes, changelogs/ (v1.3.8, v1.14.5, v1.25.0). ↩
-
Kickstarter campaign page, naya-create/naya-create (2023), read 2026-09-23. ↩
-
Kickstarter campaign FAQ, naya-create/naya-create/faqs, read 2026-09-23. ↩
-
Kickstarter update 9, 2023-12-07. ↩
-
Kickstarter update 19, 2025-02-11. ↩
-
Kickstarter update 21, 2025-06-16. ↩
-
NayaFlow 1.25.1 for macOS (x86_64), NayaCore 6.11.0:
Key::serializeBindingData,Key::serializeBindingPairData,Key::wrapDblTapRecordand the record reader, our disassembly (2026-09-26); see Disassembly. ↩↩↩↩ -
OpenFlow, commit 036d04c: a home-row typing test on the owner's board (3.41.0, 2026-09-25) with hold-tap records as
03at flavor00and01, and as10at flavor01. ↩↩