The shape of a config file
The file lives at %APPDATA%\winremap\config.toml unless you pass
--config <path>. It is TOML, and it has
two kinds of content: a few optional settings sections, and any number of
[[keymap]] blocks.
Installed from the Microsoft Store, the file sits in the package's own folder instead — the settings window always names the folder it is really using, and opens it in Explorer for you (the difference between the two channels).
# ---- settings (all optional) ----
[macro]
delay_ms = 8
[ime_indicator]
enabled = true
# ---- keymaps: as many as you like ----
[[keymap]]
name = "global-emacs" # a label for you and the settings window
application = ["*"] # which apps this block applies to
exclude = ["windowsterminal.exe"]
[keymap.remap] # the rules themselves
"C-p" = "Up"
"C-n" = "Down"
[[keymap]]
name = "jetbrains"
application = ["phpstorm64.exe", "idea64.exe"]
[keymap.remap]
"C-h" = "Back"
Two things about TOML that catch people out here:
[[keymap]]has double brackets because it is a list — each one starts a new block.[keymap.remap]has single brackets because it is a table inside the block above it.- Every rule's left side is quoted.
"C-h" = "Back", notC-h = "Back"— the hyphen would otherwise not be a legal bare key.
After editing, right-click the tray icon and pick Reload config. If the file has a mistake, WinRemap reports it and keeps running the previous config — a typo can never leave you without a keyboard setup.
Editing it without leaving WinRemap
You do not have to open the file at all. Right-click the tray icon, pick Settings, and press Edit: names, target apps, exclusions and rules become fields, keymaps can be added, deleted and reordered, and the general settings sit on sliders and checkboxes.
- Key-notation fields say what they read as while you type —
A-x ushows "Alt+x, then u" — and flag what they cannot parse, suggesting a key name when yours is close to a real one. - Save runs the same validation the command line does. If anything is wrong, nothing is written and the problems are listed with their line numbers.
- A valid save is written atomically and reloaded, so the new rules are in effect immediately.
- Everything you did not edit stays exactly as it was — comments, blank lines, the order of your rules, your spelling, and the file's line endings. Deleting a rule takes its line and the comment on it; comments written above it move to the rule that follows.
- If the file changed outside WinRemap while you were editing, Save asks instead of overwriting. Closing with unsaved changes asks too.
Not sure what to write in application? Press
Capture the foreground app, bring the app you mean to the front
within three seconds, and its exe name lands on the list.
Targeting applications
Every [[keymap]] starts by saying which applications it applies to.
| Key | Meaning |
|---|---|
application |
A list of exe names, matched case-insensitively against the foreground app —
["notepad.exe", "chrome.exe"]. Or ["*"] for every app.
Mixing "*" with names is an error rather than a guess. |
exclude |
Exe names this block does not apply to. Only allowed together with
["*"] — excluding from a list you wrote yourself would just mean
writing a shorter list. |
name |
Optional label. It appears in the settings window and in error messages, so naming blocks pays off as soon as you have more than two. |
Which block wins
When more than one block could apply, an app-specific block beats a
"*" block, regardless of the order they appear in the file. Within
the same class, the first match wins.
[[keymap]]
application = ["*"]
[keymap.remap]
"C-h" = "Back" # everywhere...
[[keymap]]
application = ["code.exe"]
[keymap.remap]
"C-h" = "C-h" # ...except VS Code, where C-h stays C-h
Only one rule ever fires. If you bind the same input in two blocks that both apply, the settings window shows a column naming the other block — that clash is invisible when reading either one alone.
Finding the exe name
Right-click the tray icon, pick Show log, then switch to the app you
want to target. The [window] line prints the exact string to put in
application, and which of your keymaps reach it — the full path is on the
detail line under it. It is always the file name only — a path does not match.
18:01:51.471 [window] application = "notepad.exe" — matching keymaps: emacs-keys
Rules
Inside [keymap.remap], the left side is what you press and the right side
is what the application receives.
Chord rules and bare-key rules
Which one you get depends on whether the left side has a modifier. The difference matters more than it looks.
| Chord rule | Bare-key rule | |
|---|---|---|
| Looks like | "C-h" = "Back" |
"CapsLock" = "LCtrl" |
| Matches | That exact modifier set. Ctrl+Shift+H
does not trigger a C-h rule. |
The key alone, whatever modifiers are held. Ctrl+CapsLock still becomes Ctrl+Ctrl. |
| Modifiers | Replaced to match the output. The app receives a plain Backspace even though
Ctrl is physically down. The output may carry its own:
"C-i" = "S-Tab". |
Left alone. The output must therefore be a single key with no modifiers. |
| Use it for | Emacs-style bindings, per-app fixes, anything where the modifier is part of what you pressed. | Swapping a physical key: CapsLock→Ctrl, Muhenkan→something useful. |
The two coexist. A block can swap CapsLock for LCtrl and
still special-case C-CapsLock, because the chord rule is checked first.
"LCtrl" = ... is rejected). Modifiers are what chords are built from; they
are consumed before any lookup happens. They are fine as outputs.Prefix keys (two-stroke sequences)
"A-x u" = "C-z" # Alt+X, then U → undo
"A-x C-s" = "C-s" # Alt+X, then Ctrl+S → save
Write two chords separated by a space. The first is swallowed and WinRemap waits — with no timeout, exactly as Emacs does. The next keystroke completes the binding.
If the second stroke is not one you defined, it is discarded rather than passed through, so a mistyped sequence never leaks a stray character into your document. The first chord must have a modifier: a bare prefix would swallow ordinary typing.
Macros
"C-t" = ["C-Right", "C-Left", "C-S-Right"] # up to 8 commands
An array output taps each command once, in order. The whole run is injected as a single batch, so your own typing cannot interleave with it. A macro fires once per press — holding the key does not repeat it.
If an app drops some of the strokes (the WinUI Notepad does), see
[macro] delay_ms below.
Settings sections
[macro]
[macro]
delay_ms = 8 # 0–15, default 0
record_start = "S-F10" # macro recording — see the help page
# record_stop = "S-F11" # omit it and the start key stops it too
record_play = "F10"
| Key | Meaning |
|---|---|
delay_ms |
Pause between the commands of a macro, in milliseconds (0–15). Leave it at 0
unless an app is dropping strokes; 8 is enough for the WinUI Notepad. It paces
replayed recordings too. The --macro-delay command-line flag overrides
it for experiments. |
record_startrecord_stoprecord_play |
Keys for macro recording. Recording is off until
you name record_start and record_play;
record_stop defaults to the start key, which makes it a toggle. These
keys are taken before any keymap lookup, so binding one of them in
[keymap.remap] as well is an error. |
macro_delay_ms — still
works. Setting both is an error rather than a silent winner, so you always know which
one is in effect.[ime_indicator]
[ime_indicator]
enabled = true # default: false
# trigger_keys = ["C-Space"] # if you toggle the IME with Ctrl+Space
# duration_ms = 800 # 100–5000
# size = 96 # 32–256
# opacity = 200 # 0–255
# show_app_name = true # exe name under the glyph
Flashes a translucent 「あ」 panel at the centre of the active window
when the IME turns on. Standard IME keys are detected already; add
trigger_keys for a custom toggle such as the Windows 11 IME's Ctrl+Space
option. Display only — WinRemap never switches the IME.
Key notation
Rules use the Emacs-style notation familiar from Keyhac/fakeymacs: modifier prefixes
plus a key name, e.g. C-h, A-S-F5, W-Space.
Everything is case-insensitive, and prefix order does not matter — C-S-h
and S-C-h are the same chord.
| Prefix | Modifier |
|---|---|
C- | Ctrl (either side) |
A- | Alt (either side) |
S- | Shift (either side) |
W- | Win (either side) |
| Group | Key names |
|---|---|
| Letters / digits | a–z, 0–9 |
| Function | F1–F24 |
| Editing | Back (Backspace, BS), Tab, Enter (Return), Esc (Escape), Space, Insert (Ins), Delete (Del) |
| Navigation | PageUp (PgUp), PageDown (PgDn), Home, End, Left, Up, Right, Down |
| Lock | CapsLock |
| Symbols | The character printed on the key (;, /, -, [, …). Aliases: Oem1–Oem8, Oem102, OemPlus, OemComma, OemMinus, OemPeriod |
| Modifiers as output keys | LCtrl, RCtrl, LShift, RShift, LAlt, RAlt, LWin, RWin, Apps (Menu) — output side only |
Not supported: modifier keys on the input side of a rule.
Symbol keys follow your keyboard
Write a symbol key as the character printed on it.
"C-;" = "Enter" means the key engraved ;.
Which key that is genuinely differs between keyboards: one and the same key prints
; on a US layout and : on a Japanese one. WinRemap does not
assume — it asks Windows when it loads your config. So the same file
picks the key that matches what is engraved on the keyboard in front of you.
A character that needs Shift is written with it.
On a US keyboard @ is Shift+2, so the
rule is "C-S-2", not "C-@". WinRemap will not quietly add the
Shift for you; it says so, with the spelling to use:
`@` needs Shift on this keyboard; write `S-2` instead
On a Japanese keyboard @ is a key of its own, so
"C-@" is exactly right there. Different layout, different
spelling — because the keyboards really are different, and hiding that would
cost you more than knowing it.
To carry one config between machines with different keyboards, use the
aliases: "C-OemMinus" names the same key on any layout. They are also the only
way to reach a key with nothing printed on it (Oem8), or to pick between two
keys that print the same character — a US layout has \ on two of them.
Changed keyboards? Choose Reload config from the tray menu; the question is asked again each time the config is read. The key-notation panel in the settings window () lists the symbol keys of the keyboard you have attached right now.
Worked examples
Each of these is a complete, self-contained block you can paste into your config.
CapsLock as another Ctrl
[[keymap]]
name = "capslock"
application = ["*"]
[keymap.remap]
"CapsLock" = "LCtrl"
A bare-key rule, so it holds whatever else you are pressing. CapsLock stops toggling caps entirely — it is Ctrl now, including when held for a chord.
Ctrl+H as Backspace, except where the terminal needs it
[[keymap]]
name = "backspace"
application = ["*"]
exclude = ["windowsterminal.exe", "mintty.exe"] # these send their own 0x08
[keymap.remap]
"C-h" = "Back"
The problem WinRemap was written for. In an editor Ctrl+H usually opens Find & Replace; here it deletes one character instead. Terminals are excluded because they already do the right thing.
Emacs cursor movement everywhere
[[keymap]]
name = "emacs-motion"
application = ["*"]
exclude = ["windowsterminal.exe", "emacs.exe"]
[keymap.remap]
"C-p" = "Up"
"C-n" = "Down"
"C-b" = "Left"
"C-f" = "Right"
"C-a" = "Home"
"C-e" = "End"
"C-d" = "Delete"
"C-k" = ["S-End", "C-x"] # kill to end of line, into the clipboard
"C-y" = "C-v" # yank
Note C-k: a macro, because "kill line" is two operations — select to the
end of the line, then cut. Exclude apps that already speak Emacs, or you will fight
them.
One app that needs the opposite
[[keymap]]
name = "vscode"
application = ["code.exe"]
[keymap.remap]
"C-p" = "C-p" # VS Code's quick-open: hand it back untouched
An app-specific block beats the "*" block above without needing to be
written after it. Mapping a chord to itself is the idiom for "this app is an
exception".
Emacs-style prefix commands
[[keymap]]
name = "prefix"
application = ["*"]
[keymap.remap]
"A-x u" = "C-z" # Alt+X U → undo
"A-x s" = "C-s" # Alt+X S → save
"A-x C-c" = "A-F4" # Alt+X Ctrl+C → close the window
Anything you press after Alt+X that is not listed is discarded, so a slip never types into your document.
Selecting a word with one key
[[keymap]]
name = "select-word"
application = ["*"]
[keymap.remap]
"C-t" = ["C-Right", "C-Left", "C-S-Right"]
Jump past the word, back to its start, then select to its end — which lands the caret on a whole word no matter where in it you started. Up to 8 commands per macro.
Recording a one-off repetitive edit
[macro]
delay_ms = 8
record_start = "S-F10" # Shift+F10 starts, and stops
record_play = "F10" # F10 replays
For edits not worth a permanent rule: press Shift+F10, do the work once, press it again, then press F10 down the rest of the file. The recording holds 20 commands, lives in memory only, and is gone when WinRemap exits. See macro recording for what it does and does not capture.
A starting file
Two examples ship with WinRemap and are worth reading end to end: minimal.toml (one rule, for checking that WinRemap is alive) and emacs.toml (a full fakeymacs-style set).
When the config is wrong
Every problem is reported at once, each with a line number, so one pass through the file fixes them all. A failed reload keeps the previous working config.
| What it says | What it means |
|---|---|
unknown key `Bogus` |
The name is not in the key notation table. If it is a symbol, your keyboard does not print it on any key — check the panel in the settings window for the ones it does. |
`@` needs Shift on this keyboard; write `S-2` instead |
That character is on a key's shifted face. Write it with the Shift, exactly as the message spells it (symbol keys). |
modifier key `LCtrl` cannot be a remap input |
A modifier alone cannot be the left side of a rule. It can be an output. |
a bare-key rule's target may not have modifiers |
The left side had no modifier, so this is a bare-key rule, and those leave the modifier state alone. Add a modifier to the left side to make it a chord rule. |
duplicates an earlier rule for the same key |
Two rules in one block resolve to the same chord — "C-h" and
"c-H" are the same thing. |
is already used as a sequence prefix |
The same chord is both a plain rule and the first stroke of a sequence. WinRemap cannot tell whether to fire it or wait for a second key. |
`exclude` requires application = ["*"] |
You listed apps and then excluded some. Just write the shorter list. |
is also remapped in `…`; recording keys are always taken first |
A recording key is also bound in a keymap. That rule could never fire, and nothing in the file would show it — so it is rejected instead. |
Still stuck? The tray's Show log window prints what WinRemap does with each keystroke, and the Settings window shows the config actually in effect — which is the fastest way to see that a reload did not happen.
Reading the log
Every line carries the time to the millisecond and a tag saying which stream it
belongs to. [decided] is one line per key; tick Every event
for the whole stream — [input] for the presses and releases that arrived,
[injected] for everything WinRemap sent in reply. Everything is recorded
either way, so ticking the box explains the keys you already pressed.
18:01:51.517 [input] n ↓
[decided] C-n → remapped to Down
[injected] LCtrl ↑ (modifier adjust)
[injected] Down ↓ (remap)
Where a key or a chord carries an ASCII control code, the log says so:
C-h (BS 0x08), Enter (CR 0x0D). Letters and digits never show a
code — WinRemap logs keys, not what you typed.
0x7f
for the Backspace key and BS 0x08 for Ctrl+H, and an application
may bind the two to different things — which is the problem WinRemap was written for.
Remapping C-h to Back makes your terminal send
0x7f for both. The log shows BS 0x08 on both sides because
that is the code Windows itself gives the Backspace key; the 0x7f is the
terminal's own doing, further down the line.