Macro import drops the ScanCode field — this is why macros show raw key codes | Razer Insider
Skip to main content
Question

Macro import drops the ScanCode field — this is why macros show raw key codes

  • October 22, 2026
  • 1 reply
  • 13 views

Synapse for Windows V4.0.86.2610200951 / Windows 11 / Razer Tartarus V2

Lots of us have reported macros showing numbers like "49", "50" or "71"
instead of key names. I think I found the cause. It is the import path,
and it is reproducible in about two minutes on any device.

MINIMAL REPRO
1. Create a new macro in Synapse 4 (any keys). The editor shows key names.
2. Export it to XML.
3. Delete that macro.
4. Import the file you just exported.
5. The editor now shows raw numbers instead of key names.

Same macro, same file, one round trip.

WHAT IMPORT CHANGES
Export the macro again after step 4 and diff it against the file from
step 2. One key event, before and after:

  BEFORE (created in Synapse 4)        AFTER (re-exported post-import)
  <MacroEvent>                         <MacroEvent>
    <Type>1</Type>                       <Type>1</Type>
    <Id>1792666983228</Id>               <Id>1</Id>
    <KeyEvent>                           <KeyEvent>
      <Makecode>16</Makecode>              <Makecode>16</Makecode>
      <State>0</State>                     <State>0</State>
    </KeyEvent>                          </KeyEvent>
    <flag>0</flag>
    <selected>false</selected>           <selected>false</selected>
    <isPairing>false</isPairing>         <isPairing>false</isPairing>
    <ScanCode>42</ScanCode>
  </MacroEvent>                         </MacroEvent>

Import drops <ScanCode> and <flag> entirely, and renumbers <Id>.

WHY THAT BREAKS THE DISPLAY
<Makecode> is not a scan code, it is a Windows virtual-key code.
Verified against the pairs in my own macros:

  Makecode 112 + ScanCode 59  = F1
  Makecode  49 + ScanCode  2  = 1
  Makecode  50 + ScanCode  3  = 2
  Makecode 114 + ScanCode 61  = F3
  Makecode  32 + ScanCode 57  = Space
  Makecode  16 + ScanCode 42  = Left Shift
  Makecode  17 + ScanCode 29  = Left Ctrl

The editor resolves the key name from <ScanCode>. With <ScanCode> gone it
falls back to printing the raw <Makecode> value. That is exactly the
number people see. rolandihasz reported "instead of key G I have 71" —
71 is the virtual-key code for G, not ASCII.

For right Shift the value is 310, which is 256 + 54 (54 being the right
Shift scan code), so it is the same mechanism in an extended form.

WHY OLD MACROS ARE ALL BROKEN AND NEW ONES ARE NOT
Migrating from Synapse 3 goes through the same import path. Every macro
that came across lost its <ScanCode>. Macros created fresh in Synapse 4
keep their key names indefinitely, including across app restarts and
account logout/login. That matches what everyone is describing.

SECOND, SEPARATE BUG IN THE SAME IMPORT
When a macro has a nested hold (hold Space, press 1, release Space), the
release event of the enclosing key comes back with an empty <Id/> while
its press event has a normal id. Three occurrences in my test files, no
exceptions — Space, Left Shift and Left Ctrl, each time the outer key's
release:

  <Id>1</Id>   Makecode 16  State 0   (Shift down)
  <Id/>        Makecode 16  State 1   (Shift up)   <- pairing lost

IMPACT: DISPLAY ONLY, AS FAR AS I CAN TELL
I logged the actual key events of an imported macro in a browser key
tester. Playback is correct: every press pairs with its release, nesting
works, left/right modifiers survive (ShiftLeft and ControlLeft, not the
generic or wrong side), and the 0.01 s delays are honoured.

  DOWN 1     Digit1      VK 49
  UP   1     Digit1      VK 49   11ms
  DOWN Shift ShiftLeft   VK 16
  DOWN %     Digit5      VK 53
  UP   %     Digit5      VK 53   12ms
  UP   Shift ShiftLeft   VK 16   30ms

So the macros still run. What is lost is the ability to read or edit
them: a long macro is an unreadable list of numbers. For anyone who
migrated from Synapse 3, that is every macro they own.

The 8 September release notes list "Sequence macros reverted key
assignments to raw scan codes on app relaunch" as fixed, and
"Synapse 3 profile imports could result in corrupted profile states or
missing macro bindings" as fixed. Neither covers this: already-imported
macros are never repaired, and the import path still strips <ScanCode>
today on V4.0.86.2610200951.

Two things would help:
1. Stop import from discarding <ScanCode> and <flag>.
2. Give the editor a fallback that maps <Makecode> back to a key name
   when <ScanCode> is absent, so existing broken macros become readable
   again without anyone having to rebuild them by hand.

Happy to supply the full before/after XML files and the event logs.

1 Reply

  • Author
  • Insider Mini
  • October 22, 2026

Follow-up: I found where the data lives and what is actually happening.
It is not quite what I said in the first post, and the real picture is
better news — nothing is lost, and the fix is straightforward.

WHERE IT IS STORED
Chromium IndexedDB inside the app's own profile, origin
https://apps.razer.com:

  %LOCALAPPDATA%\Razer\RazerAppEngine\User Data\Default\IndexedDB\
    https_apps.razer.com_0.indexeddb.leveldb\

THERE ARE TWO PARALLEL REPRESENTATIONS PER MACRO

1. The editor representation, macroEvents[]:
   {"Type":1,"Id":1750070208931,"KeyEvent":{"Makecode":117,"State":0},
    "flag":null,"selected":false,"isPairing":false}

2. The playback representation, appEngine.events[]:
   "appEngine":{"hash":"17ee83...","events":[
     {"type":"keyboard","scancode":28,"flag":0},
     {"type":"delay","ms":50},
     {"type":"keyboard","scancode":28,"flag":1}]}

The second one keeps the scan code in a lowercase "scancode" field. In
my store, all 110 macros have an appEngine block, covering 920 keyboard
events, every one with its scan code intact — including every macro the
editor displays as numbers.

That explains the thing I could not explain before: playback is perfect
while the display is broken. The engine never looks at Makecode. It
reads appEngine.events. My earlier guess that VK 16 happens to resolve
to the left modifier was wrong — the engine simply uses scancode 42.

THE PARSER IS FINE; THE IMPORT CONVERSION IS NOT
The app logs the parsed XML. At 20:04:27.598 in macro3.log, immediately
after I imported a file:

  {"Macro":{"Name":["1111"],"MacroEvents":[{"MacroEvent":[ ...
   {"Type":["1"],"Id":["1792666983227"],
    "KeyEvent":[{"Makecode":["49"],"State":["0"]}],
    "flag":["0"],"selected":["false"],"isPairing":["false"],
    "ScanCode":["2"]}, ...

ScanCode and flag are both there. Six milliseconds later the log shows
"macroReducer import", and the resulting state no longer has them. The
XML parser reads the fields correctly. The conversion into the internal
object is what discards them.

A CASE MISMATCH IN ONE PATH
16 events in my store carry the field as capital "ScanCode" inside the
editor representation:

  {"Type":1,"Id":1791976658092,"KeyEvent":{"Makecode":32,"State":0},
   "flag":0,"selected":false,"isPairing":false,"ScanCode":57}

Everywhere else the internal field is lowercase "scancode". So at least
one code path copies the XML element name verbatim instead of mapping it
to the internal name. The value is present but under a key nothing reads.

THREE DAMAGE SHAPES, ALL VISIBLE IN MY STORE
  recorded in Synapse 4   flag: 0      scancode present   displays names
  migrated from Synapse 3 flag: null   no scancode        displays numbers
  freshly imported XML    flag absent  no scancode        displays numbers

WHAT USERS CAN AND CANNOT DO
I tested every in-app route on a broken macro and exported after each to
check whether ScanCode came back:

  Duplicate the macro          no
  Edit it and save             no (the delay change saved; the key
                               events were copied across untouched)
  Re-record one key            yes, but only for that one event

The last test is the clearest evidence. One macro, one key (Makecode 49),
two entries side by side in the same exported file:

  <Id>1771334394577</Id> ... <flag/>          (pre-existing)
  <Id>1792682018087</Id> ... <flag>0</flag>
                             <ScanCode>2</ScanCode>   (just re-recorded)

Only the recording path writes these fields. Saving rewrites the whole
record but copies key events verbatim, so nothing repopulates them. The
only workaround available to a user is rebuilding every macro by hand.

THE FIX
Since appEngine.events already holds the correct scan code for every
keyboard event in every macro, a one-time migration can repopulate
macroEvents[].scancode from it. No virtual-key mapping table, no
guessing, no asking users to rebuild anything — the authoritative value
is already sitting in the same record.

Alongside that, the import conversion should carry ScanCode and flag
through, and write the internal field in the case the rest of the app
reads.