Creating QMK, VIA, and Vial Keymaps
QMK, VIA, and Vial Overview
Section titled “QMK, VIA, and Vial Overview”QMK is open-source keyboard firmware. You write C code defining your keymaps, compile it, and flash it. Changes require editing code, recompiling, and reflashing.
VIA adds dynamic keymap storage on top of QMK. Flash once, then remap keys through the web interface without recompiling. VIA is closed-source and requires JSON definitions for keyboard recognition. It doesn’t support rotary encoders, MIDI, or full RGB matrix control.
Vial is an open-source fork of VIA that addresses these limitations. It includes tap dance, combos, rotary encoders, MIDI, and complete RGB matrix support. Keyboard definitions are compiled into firmware instead of sideloaded.
Quick Comparison
Section titled “Quick Comparison”| Feature | QMK | VIA | Vial |
|---|---|---|---|
| License | GPL-2.0 (open) | Closed source | GPL-2.0 (open) |
| Live remapping | No | Yes | Yes |
| Tap dance | Yes | No | Yes |
| Combos | Yes | Limited | Yes |
| Rotary encoders | Yes | No | Yes |
| MIDI | Yes | No | Yes |
| RGB Matrix | Yes | Basic | Full support |
| Keyboard detection | N/A | JSON sideload required | Built into firmware |
| Web interface | No | usevia.app | vial.rocks |
| Desktop app | No | No | Yes (Win/Mac/Linux) |
Setting Up QMK
Section titled “Setting Up QMK”Requirements
Section titled “Requirements”- Linux/macOS/WSL
- Git
- Python 3.8+
- QMK-compatible keyboard
Clone QMK
Section titled “Clone QMK”git clone https://github.com/qmk/qmk_firmware.gitcd qmk_firmwareInstall QMK CLI
Section titled “Install QMK CLI”Two installation approaches:
Option A: Virtual Environment
python -m venv venvsource venv/bin/activate # Windows: venv\Scripts\activate
# NO --user flag in a venvpython -m pip install qmkOption B: Global Install
# Deactivate any venv firstdeactivate
# Use --user for global installpython -m pip install --user qmkDownload Submodules
Section titled “Download Submodules”make git-submoduleQMK depends on external libraries (LUFA, ChibiOS, etc.). Without this step, compilation fails:
tmk_core/protocol/lufa/lufa.mk:13: lib/lufa/LUFA/makefile: No such file or directorymake: *** No rule to make target 'lib/lufa/LUFA/makefile'. StopVerify It Works
Section titled “Verify It Works”qmk compile -kb planck/rev6 -km defaultIf this compiles without errors, the setup is complete.
Keymap Anatomy
Section titled “Keymap Anatomy”Keymaps live under keyboards/<vendor>/<model>/keymaps/<keymap_name>/. Standard structure:
keyboards/boardsource/5x12/keymaps/├── default/ # Factory keymap│ └── keymap.c├── via/ # VIA-enabled default│ ├── keymap.c│ └── rules.mk└── my-layout/ # Your custom keymap ├── keymap.c └── rules.mk # Optional: feature flagsMinimal keymap.c
Section titled “Minimal keymap.c”#include QMK_KEYBOARD_H
enum layers { _BASE = 0, _LOWER = 1, _RAISE = 2,};
#define LOWER MO(_LOWER)#define RAISE MO(_RAISE)
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { [_BASE] = LAYOUT_ortho_5x12( KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_BSPC, KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_DEL, KC_ESC, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_ENT, KC_LCTL, KC_LGUI, KC_LALT, KC_APP, LOWER, KC_SPC, KC_SPC, RAISE, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT ), // ... more layers};Breaking it down:
- Layer enum: Name your layers. Numbers must be sequential starting from 0.
- Defines: Shortcuts for layer access (
MO= momentary, hold to activate). - Keymap array: The actual key assignments.
LAYOUT_ortho_5x12is board-specific—check your keyboard’sinfo.jsonor existing keymaps for the right macro. - Order matters: Keys map to physical positions left-to-right, top-to-bottom.
Common Keycodes
Section titled “Common Keycodes”Full reference: QMK Keycodes
Letters, Numbers, Modifiers
Section titled “Letters, Numbers, Modifiers”KC_A through KC_Z // Self-explanatoryKC_1 through KC_0 // Number row
KC_LSFT, KC_RSFT // Left/Right ShiftKC_LCTL, KC_RCTL // Left/Right ControlKC_LALT, KC_RALT // Left/Right AltKC_LGUI, KC_RGUI // Super/Win/Cmd
KC_SPC, KC_BSPC, KC_ENT, KC_ESC, KC_TAB, KC_DELKC_F1 through KC_F12KC_LEFT, KC_RIGHT, KC_UP, KC_DOWNSymbols (Where You’ll Get Burned)
Section titled “Symbols (Where You’ll Get Burned)”These are unshifted keys. To get shifted symbols (!@#$%), use LSFT():
KC_MINUS // - (unshifted) and _ (shifted)KC_EQL // = (unshifted) and + (shifted)KC_LBRC // [ and {KC_RBRC // ] and }KC_BSLS // \ and |KC_SCLN // ; and :KC_QUOT // ' and "KC_GRV // ` and ~KC_COMM // , and <KC_DOT // . and >KC_SLSH // / and ?
// For shifted symbols:LSFT(KC_1) // !LSFT(KC_2) // @LSFT(KC_EQL) // +LSFT(KC_MINUS) // _Numpad Keycodes
Section titled “Numpad Keycodes”Avoid numpad keycodes on standard typing layers:
KC_KP_PLUS // Numpad plus—WRONG for a symbol layerKC_KP_MINUS // Numpad minus—WRONG for a symbol layerThese send different scancodes than the main keyboard equivalents. Use standard keys for symbol layers:
KC_EQL // Correct for = keyKC_MINUS // Correct for - keyLSFT(KC_EQL) // Correct for + symbolLayer Control
Section titled “Layer Control”MO(layer) // Hold to activate layerLT(layer, kc) // Tap for key, hold for layerTG(layer) // Toggle on/offDF(layer) // Set as default base layerCreating Your Keymap
Section titled “Creating Your Keymap”Start By Copying
Section titled “Start By Copying”Don’t write from scratch. Copy a working keymap:
cd keyboards/boardsource/5x12/keymapscp -r default my-colemakcd my-colemakEdit keymap.c
Section titled “Edit keymap.c”Example Colemak-DH layout with symbol layers:
enum layers { _COLEMAK = 0, _QWERTY = 1, _LOWER = 2, _RAISE = 3, _ADJUST = 4,};
#define LOWER MO(_LOWER)#define RAISE MO(_RAISE)
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { [_COLEMAK] = LAYOUT_ortho_5x12( KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_BSPC, KC_TAB, KC_Q, KC_W, KC_F, KC_P, KC_B, KC_J, KC_L, KC_U, KC_Y, KC_SCLN, KC_DEL, KC_ESC, KC_A, KC_R, KC_S, KC_T, KC_G, KC_M, KC_N, KC_E, KC_I, KC_O, KC_QUOT, KC_LSFT, KC_Z, KC_X, KC_C, KC_D, KC_V, KC_K, KC_H, KC_COMM, KC_DOT, KC_SLSH, KC_ENT, KC_LCTL, KC_LGUI, KC_LALT, KC_APP, LOWER, KC_SPC, KC_SPC, RAISE, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT ),
[_LOWER] = LAYOUT_ortho_5x12( LSFT(KC_GRV), LSFT(KC_1), LSFT(KC_2), LSFT(KC_3), LSFT(KC_4), LSFT(KC_5), LSFT(KC_6), LSFT(KC_7), LSFT(KC_8), LSFT(KC_9), LSFT(KC_0), KC_BSPC, KC_ESC, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_DEL, KC_DEL, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_MINUS, KC_EQL, KC_LBRC, KC_RBRC, KC_BSLS, KC_RSFT, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, _______, _______, _______, _______, KC_ENT, _______, RGB_TOG, RGB_MOD, RGB_RMOD,_______, KC_SPC, KC_SPC, _______, _______, _______, _______, _______ ),
[_RAISE] = LAYOUT_ortho_5x12( LSFT(KC_GRV), LSFT(KC_1), LSFT(KC_2), LSFT(KC_3), LSFT(KC_4), LSFT(KC_5), LSFT(KC_6), LSFT(KC_7), LSFT(KC_8), LSFT(KC_9), LSFT(KC_0), KC_BSPC, KC_ESC, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_DEL, KC_DEL, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, LSFT(KC_MINUS), LSFT(KC_EQL), LSFT(KC_LBRC), LSFT(KC_RBRC), LSFT(KC_BSLS), KC_RSFT, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, _______, KC_MPRV, KC_MNXT, _______, KC_ENT, _______, _______, _______, _______, _______, KC_SPC, KC_SPC, _______, KC_MUTE, KC_VOLD, KC_VOLU, KC_MPLY ),
[_ADJUST] = LAYOUT_ortho_5x12( _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, QK_BOOT, KC_PWR, KC_SLEP, KC_WAKE, _______, _______, _______, _______, DF(_QWERTY), DF(_COLEMAK), _______, _______ ),};
// Tri-layer: Hold LOWER+RAISE to activate ADJUSTlayer_state_t layer_state_set_user(layer_state_t state) { return update_tri_layer_state(state, _LOWER, _RAISE, _ADJUST);}Notes:
_______is transparent (passes through to lower layer)XXXXXXXdisables the key completelyDF(_QWERTY)switches the default base layerQK_BOOTputs the keyboard in bootloader mode for reflashing
Test Compilation
Section titled “Test Compilation”Before flashing, make sure it compiles:
qmk compile -kb boardsource/5x12 -km my-colemakCommon errors:
- “Undefined keycode”: Typo. Check against the keycode list.
- “Expected N arguments, got M”: Wrong number of keys in
LAYOUT_*()macro. - “Missing comma”: Every keycode needs a comma after it (except the last one in a row).
Enabling VIA
Section titled “Enabling VIA”VIA requires two tiny changes.
1. Create rules.mk
Section titled “1. Create rules.mk”In your keymap directory, create rules.mk:
VIA_ENABLE = yesIf your keyboard supports other features (RGB, audio, etc.):
VIA_ENABLE = yesRGBLIGHT_ENABLE = yesAUDIO_ENABLE = yes2. Explicit Layer Numbering
Section titled “2. Explicit Layer Numbering”VIA needs layers explicitly numbered. Change your enum:
enum layers { _COLEMAK = 0, // Must be explicit _QWERTY = 1, _LOWER = 2, _RAISE = 3, _ADJUST = 4,};That’s it. Compile and flash:
qmk compile -kb boardsource/5x12 -km my-colemakAfter flashing, visit usevia.app. Your keyboard should be detected automatically. If not, check that VIA is enabled in rules.mk and you recompiled after adding it.
Enabling Vial
Section titled “Enabling Vial”Vial requires the vial-qmk fork instead of mainline QMK.
Clone vial-qmk
Section titled “Clone vial-qmk”Important: Don’t nest this inside your existing qmk_firmware directory. Clone it separately:
cd ~git clone https://github.com/vial-kb/vial-qmk.gitcd vial-qmkmake git-submoduleCreate Vial Keymap
Section titled “Create Vial Keymap”The keymap must be named vial:
cd keyboards/boardsource/5x12/keymapscp -r default vialcd vial1. Create rules.mk
Section titled “1. Create rules.mk”VIA_ENABLE = yesVIAL_ENABLE = yesYes, you need both. Vial builds on VIA’s protocol.
2. Generate Keyboard UID
Section titled “2. Generate Keyboard UID”Every Vial keyboard needs a unique 8-byte identifier:
# From the vial-qmk root directorypython3 util/vial_generate_keyboard_uid.pyThis outputs something like:
{0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX}3. Create config.h
Section titled “3. Create config.h”In your keymaps/vial/ directory:
#pragma once
#define VIAL_KEYBOARD_UID {0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX}
// Unlock key combination (prevents malicious host from changing settings)// Define at least 2 keys: top-left and bottom-right corners are common#define VIAL_UNLOCK_COMBO_ROWS { 0, 4 }#define VIAL_UNLOCK_COMBO_COLS { 0, 11 }Replace the 0xXX bytes with what the UID generator gave you.
The unlock combo is a security feature—you hold those keys while plugging in the keyboard to unlock security settings. Pick two keys far apart (like top-left and bottom-right).
4. Create vial.json
Section titled “4. Create vial.json”This defines your keyboard’s physical layout for the Vial UI. It’s not the same as QMK’s info.json.
You can either:
- Use the VIA Layout Editor to generate one
- Convert your existing
info.json(check Vial porting docs) - Copy from a similar keyboard and modify
Minimal example for a 5x12 ortho:
{ "name": "Boardsource 5x12", "lighting": "none", "matrix": { "rows": 5, "cols": 12 }, "layouts": { "keymap": [ ["0,0", "0,1", "0,2", "0,3", "0,4", "0,5", "0,6", "0,7", "0,8", "0,9", "0,10", "0,11"], ["1,0", "1,1", "1,2", "1,3", "1,4", "1,5", "1,6", "1,7", "1,8", "1,9", "1,10", "1,11"], ["2,0", "2,1", "2,2", "2,3", "2,4", "2,5", "2,6", "2,7", "2,8", "2,9", "2,10", "2,11"], ["3,0", "3,1", "3,2", "3,3", "3,4", "3,5", "3,6", "3,7", "3,8", "3,9", "3,10", "3,11"], ["4,0", "4,1", "4,2", "4,3", "4,4", "4,5", "4,6", "4,7", "4,8", "4,9", "4,10", "4,11"] ] }}The "row,col" strings map to your matrix positions.
5. Update keymap.c
Section titled “5. Update keymap.c”Use the same keymap as before with explicit layer numbering:
enum layers { _COLEMAK = 0, _QWERTY = 1, _LOWER = 2, _RAISE = 3, _ADJUST = 4,};Compile Vial Firmware
Section titled “Compile Vial Firmware”From the vial-qmk root directory:
make boardsource/5x12:vialDon’t use qmk compile with vial-qmk—use make directly. The build system is slightly different.
Flash and Test
Section titled “Flash and Test”make boardsource/5x12:vial:flashAfter flashing, visit vial.rocks or download the desktop app. Your keyboard should be detected immediately—no JSON sideloading needed.
Vial Directory Structure
Section titled “Vial Directory Structure”Final structure should look like:
keyboards/boardsource/5x12/keymaps/vial/├── config.h # Keyboard UID and unlock combo├── keymap.c # Your actual keymap├── rules.mk # VIA_ENABLE and VIAL_ENABLE└── vial.json # Physical layout definitionFlashing Your Keyboard
Section titled “Flashing Your Keyboard”Put Keyboard in Bootloader Mode
Section titled “Put Keyboard in Bootloader Mode”Most boards: Press the physical RESET button on the PCB, or press the QK_BOOT key in your keymap (usually on an _ADJUST layer).
Flash It
Section titled “Flash It”For ATmega32U4/STM32 boards (most keyboards):
qmk flash -kb boardsource/5x12 -km my-colemakThis compiles, waits for bootloader detection, and flashes automatically. If it hangs on “Waiting for device…”, your keyboard isn’t in bootloader mode.
Linux permissions issue?
sudo qmk flash -kb boardsource/5x12 -km my-colemakOr add udev rules (see QMK docs).
For RP2040 boards (Boardsource Equals 60):
RP2040 boards use UF2 bootloader and appear as USB mass storage devices. No flashing tool needed:
- Put keyboard in bootloader mode (RESET button or
QK_BOOTkey) - A drive named
RPI-RP2appears - Compile the firmware:
Terminal window qmk compile -kb boardsource/equals/60 -km my-keymap - Copy the
.uf2file to theRPI-RP2drive:Terminal window cp .build/boardsource_equals_60_my-keymap.uf2 /media/$USER/RPI-RP2/ - The keyboard reboots automatically after the file copies
The .uf2 file is in .build/ after compilation.
Debugging When Things Break
Section titled “Debugging When Things Break”Console Output
Section titled “Console Output”Enable debug printing in rules.mk:
CONSOLE_ENABLE = yesAdd prints to keymap.c:
#include "print.h"
bool process_record_user(uint16_t keycode, keyrecord_t *record) { if (record->event.pressed) { uprintf("Pressed: 0x%04X\n", keycode); } return true;}View output:
qmk consoleCommon Issues
Section titled “Common Issues”| Symptom | Cause | Fix |
|---|---|---|
| Key does nothing | Wrong keycode or transparent key with no lower layer | Check keycode, use XXXXXXX to explicitly disable |
| Wrong symbol appears | Used numpad key (KC_KP_*) instead of main keyboard key | Use KC_MINUS, KC_EQL, etc. |
| Layer won’t activate | Layer index mismatch or wrong MO() value | Verify enum numbers match layer definitions |
| VIA doesn’t detect | VIA_ENABLE missing or not recompiled | Add to rules.mk, recompile, reflash |
| Vial doesn’t detect | Missing vial.json or wrong UID | Check VIAL_KEYBOARD_UID in config.h, verify vial.json exists |
| Compile fails after git pull | Submodules out of sync | Run make git-submodule again |
| vial-qmk compile error | Using qmk compile instead of make | Use make keyboard:keymap with vial-qmk |
Real Examples (My Actual Boards)
Section titled “Real Examples (My Actual Boards)”Boardsource 5x12
Section titled “Boardsource 5x12”# Standard QMK (qmk_firmware)qmk compile -kb boardsource/5x12 -km 5x12-mod-dh-erfiqmk flash -kb boardsource/5x12 -km 5x12-mod-dh-erfi
# VIA-enabled (qmk_firmware)qmk compile -kb boardsource/5x12 -km via-colemak-mod-dh-erfiqmk flash -kb boardsource/5x12 -km via-colemak-mod-dh-erfi
# Vial-enabled (vial-qmk)cd ~/vial-qmkmake boardsource/5x12:vialmake boardsource/5x12:vial:flashBoardsource Equals 60
Section titled “Boardsource Equals 60”The Equals 60 uses RP2040, which supports UF2 flashing. Compile and copy the .uf2 file:
# Standard QMK (qmk_firmware)qmk compile -kb boardsource/equals/60 -km colemak-mod-dh-erficp .build/boardsource_equals_60_colemak-mod-dh-erfi.uf2 /media/$USER/RPI-RP2/
# VIA-enabled (qmk_firmware)qmk compile -kb boardsource/equals/60 -km via-colemak-mod-dh-erficp .build/boardsource_equals_60_via-colemak-mod-dh-erfi.uf2 /media/$USER/RPI-RP2/
# Vial-enabled (vial-qmk)cd ~/vial-qmkmake boardsource/equals/60:vialcp .build/boardsource_equals_60_vial.uf2 /media/$USER/RPI-RP2/Or use qmk flash if you prefer automated flashing.
All three use the same base keymap—the difference is in rules.mk and whether you’re using qmk_firmware or vial-qmk.
Advanced Stuff You Might Want
Section titled “Advanced Stuff You Might Want”Custom Keycodes (Macros)
Section titled “Custom Keycodes (Macros)”enum custom_keycodes { SHRUG = SAFE_RANGE,};
bool process_record_user(uint16_t keycode, keyrecord_t *record) { switch (keycode) { case SHRUG: if (record->event.pressed) { SEND_STRING("¯\\_(ツ)_/¯"); } return false; } return true;}Use SHRUG in your keymap like any other keycode.
Tap Dance
Section titled “Tap Dance”Different actions based on tap count. Requires TAP_DANCE_ENABLE = yes in rules.mk:
enum { TD_ESC_CAPS,};
tap_dance_action_t tap_dance_actions[] = { [TD_ESC_CAPS] = ACTION_TAP_DANCE_DOUBLE(KC_ESC, KC_CAPS),};Tap once for Esc, twice for Caps Lock. Use TD(TD_ESC_CAPS) in your keymap.
RGB Control
Section titled “RGB Control”# In rules.mkRGBLIGHT_ENABLE = yesThen use in keymap:
RGB_TOG // Toggle RGB on/offRGB_MOD // Next modeRGB_RMOD // Previous modeRGB_HUI // Hue +RGB_SAI // Saturation +RGB_VAI // Brightness +Quick Reference
Section titled “Quick Reference”# QMK Setupgit clone https://github.com/qmk/qmk_firmware.gitcd qmk_firmwarepython -m venv venv && source venv/bin/activatepip install qmkmake git-submodule
# Vial Setup (separate from QMK)cd ~git clone https://github.com/vial-kb/vial-qmk.gitcd vial-qmkmake git-submodule
# List keyboardsqmk list-keyboards | grep -i boardsource
# Compile (QMK/VIA)qmk compile -kb <keyboard> -km <keymap>qmk flash -kb <keyboard> -km <keymap>
# Compile (Vial)cd ~/vial-qmkmake <keyboard>:vialmake <keyboard>:vial:flash
# Clean build artifactsqmk clean # For QMKmake clean # For Vial
# View keyboard infoqmk info -kb <keyboard>Resources
Section titled “Resources”QMK:
VIA:
Vial (Open Source):
- Vial Homepage
- Vial Web App
- Vial Desktop Downloads
- Vial Documentation
- vial-qmk GitHub
- vial-gui GitHub
Troubleshooting Checklist
Section titled “Troubleshooting Checklist”Before asking for help:
General:
- Ran
make git-submodule - Keymap compiles without errors
- Layer enum numbers match layer array indices (0, 1, 2, 3…)
- No typos in keycodes
- Correct number of keys in
LAYOUT_*()macro - Keyboard in bootloader mode when flashing
- Tried turning it off and on again
VIA-specific:
-
VIA_ENABLE = yesinrules.mk - Using
qmk_firmware(not vial-qmk) - Browser has USB permission for usevia.app
- Keyboard JSON definition loaded (if required)
Vial-specific:
- Both
VIA_ENABLE = yesandVIAL_ENABLE = yesinrules.mk - Using
vial-qmkfork (not standard qmk_firmware) -
VIAL_KEYBOARD_UIDset inconfig.h -
vial.jsonexists in keymap directory - Keymap named exactly
vial(notviaor anything else) - Using
makecommands, notqmk compile - Unlock combo defined (or
VIAL_INSECUREset)
For additional help, see QMK Discord.