Skip to content

Creating QMK, VIA, and Vial Keymaps

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.

FeatureQMKVIAVial
LicenseGPL-2.0 (open)Closed sourceGPL-2.0 (open)
Live remappingNoYesYes
Tap danceYesNoYes
CombosYesLimitedYes
Rotary encodersYesNoYes
MIDIYesNoYes
RGB MatrixYesBasicFull support
Keyboard detectionN/AJSON sideload requiredBuilt into firmware
Web interfaceNousevia.appvial.rocks
Desktop appNoNoYes (Win/Mac/Linux)

  • Linux/macOS/WSL
  • Git
  • Python 3.8+
  • QMK-compatible keyboard
Terminal window
git clone https://github.com/qmk/qmk_firmware.git
cd qmk_firmware

Two installation approaches:

Option A: Virtual Environment

Terminal window
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# NO --user flag in a venv
python -m pip install qmk

Option B: Global Install

Terminal window
# Deactivate any venv first
deactivate
# Use --user for global install
python -m pip install --user qmk
Terminal window
make git-submodule

QMK 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 directory
make: *** No rule to make target 'lib/lufa/LUFA/makefile'. Stop
Terminal window
qmk compile -kb planck/rev6 -km default

If this compiles without errors, the setup is complete.


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 flags
GPL-2.0-or-later
#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:

  1. Layer enum: Name your layers. Numbers must be sequential starting from 0.
  2. Defines: Shortcuts for layer access (MO = momentary, hold to activate).
  3. Keymap array: The actual key assignments. LAYOUT_ortho_5x12 is board-specific—check your keyboard’s info.json or existing keymaps for the right macro.
  4. Order matters: Keys map to physical positions left-to-right, top-to-bottom.

Full reference: QMK Keycodes

KC_A through KC_Z // Self-explanatory
KC_1 through KC_0 // Number row
KC_LSFT, KC_RSFT // Left/Right Shift
KC_LCTL, KC_RCTL // Left/Right Control
KC_LALT, KC_RALT // Left/Right Alt
KC_LGUI, KC_RGUI // Super/Win/Cmd
KC_SPC, KC_BSPC, KC_ENT, KC_ESC, KC_TAB, KC_DEL
KC_F1 through KC_F12
KC_LEFT, KC_RIGHT, KC_UP, KC_DOWN

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) // _

Avoid numpad keycodes on standard typing layers:

KC_KP_PLUS // Numpad plus—WRONG for a symbol layer
KC_KP_MINUS // Numpad minus—WRONG for a symbol layer

These send different scancodes than the main keyboard equivalents. Use standard keys for symbol layers:

KC_EQL // Correct for = key
KC_MINUS // Correct for - key
LSFT(KC_EQL) // Correct for + symbol
MO(layer) // Hold to activate layer
LT(layer, kc) // Tap for key, hold for layer
TG(layer) // Toggle on/off
DF(layer) // Set as default base layer

Don’t write from scratch. Copy a working keymap:

Terminal window
cd keyboards/boardsource/5x12/keymaps
cp -r default my-colemak
cd my-colemak

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 ADJUST
layer_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)
  • XXXXXXX disables the key completely
  • DF(_QWERTY) switches the default base layer
  • QK_BOOT puts the keyboard in bootloader mode for reflashing

Before flashing, make sure it compiles:

Terminal window
qmk compile -kb boardsource/5x12 -km my-colemak

Common 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).

VIA requires two tiny changes.

In your keymap directory, create rules.mk:

VIA_ENABLE = yes

If your keyboard supports other features (RGB, audio, etc.):

VIA_ENABLE = yes
RGBLIGHT_ENABLE = yes
AUDIO_ENABLE = yes

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:

Terminal window
qmk compile -kb boardsource/5x12 -km my-colemak

After 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.


Vial requires the vial-qmk fork instead of mainline QMK.

Important: Don’t nest this inside your existing qmk_firmware directory. Clone it separately:

Terminal window
cd ~
git clone https://github.com/vial-kb/vial-qmk.git
cd vial-qmk
make git-submodule

The keymap must be named vial:

Terminal window
cd keyboards/boardsource/5x12/keymaps
cp -r default vial
cd vial
VIA_ENABLE = yes
VIAL_ENABLE = yes

Yes, you need both. Vial builds on VIA’s protocol.

Every Vial keyboard needs a unique 8-byte identifier:

Terminal window
# From the vial-qmk root directory
python3 util/vial_generate_keyboard_uid.py

This outputs something like:

{0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX}

In your keymaps/vial/ directory:

GPL-2.0-or-later
#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).

This defines your keyboard’s physical layout for the Vial UI. It’s not the same as QMK’s info.json.

You can either:

  1. Use the VIA Layout Editor to generate one
  2. Convert your existing info.json (check Vial porting docs)
  3. 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.

Use the same keymap as before with explicit layer numbering:

enum layers {
_COLEMAK = 0,
_QWERTY = 1,
_LOWER = 2,
_RAISE = 3,
_ADJUST = 4,
};

From the vial-qmk root directory:

Terminal window
make boardsource/5x12:vial

Don’t use qmk compile with vial-qmk—use make directly. The build system is slightly different.

Terminal window
make boardsource/5x12:vial:flash

After flashing, visit vial.rocks or download the desktop app. Your keyboard should be detected immediately—no JSON sideloading needed.

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 definition

Most boards: Press the physical RESET button on the PCB, or press the QK_BOOT key in your keymap (usually on an _ADJUST layer).

For ATmega32U4/STM32 boards (most keyboards):

Terminal window
qmk flash -kb boardsource/5x12 -km my-colemak

This 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?

Terminal window
sudo qmk flash -kb boardsource/5x12 -km my-colemak

Or 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:

  1. Put keyboard in bootloader mode (RESET button or QK_BOOT key)
  2. A drive named RPI-RP2 appears
  3. Compile the firmware:
    Terminal window
    qmk compile -kb boardsource/equals/60 -km my-keymap
  4. Copy the .uf2 file to the RPI-RP2 drive:
    Terminal window
    cp .build/boardsource_equals_60_my-keymap.uf2 /media/$USER/RPI-RP2/
  5. The keyboard reboots automatically after the file copies

The .uf2 file is in .build/ after compilation.


Enable debug printing in rules.mk:

CONSOLE_ENABLE = yes

Add 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:

Terminal window
qmk console
SymptomCauseFix
Key does nothingWrong keycode or transparent key with no lower layerCheck keycode, use XXXXXXX to explicitly disable
Wrong symbol appearsUsed numpad key (KC_KP_*) instead of main keyboard keyUse KC_MINUS, KC_EQL, etc.
Layer won’t activateLayer index mismatch or wrong MO() valueVerify enum numbers match layer definitions
VIA doesn’t detectVIA_ENABLE missing or not recompiledAdd to rules.mk, recompile, reflash
Vial doesn’t detectMissing vial.json or wrong UIDCheck VIAL_KEYBOARD_UID in config.h, verify vial.json exists
Compile fails after git pullSubmodules out of syncRun make git-submodule again
vial-qmk compile errorUsing qmk compile instead of makeUse make keyboard:keymap with vial-qmk

Terminal window
# Standard QMK (qmk_firmware)
qmk compile -kb boardsource/5x12 -km 5x12-mod-dh-erfi
qmk flash -kb boardsource/5x12 -km 5x12-mod-dh-erfi
# VIA-enabled (qmk_firmware)
qmk compile -kb boardsource/5x12 -km via-colemak-mod-dh-erfi
qmk flash -kb boardsource/5x12 -km via-colemak-mod-dh-erfi
# Vial-enabled (vial-qmk)
cd ~/vial-qmk
make boardsource/5x12:vial
make boardsource/5x12:vial:flash

The Equals 60 uses RP2040, which supports UF2 flashing. Compile and copy the .uf2 file:

Terminal window
# Standard QMK (qmk_firmware)
qmk compile -kb boardsource/equals/60 -km colemak-mod-dh-erfi
cp .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-erfi
cp .build/boardsource_equals_60_via-colemak-mod-dh-erfi.uf2 /media/$USER/RPI-RP2/
# Vial-enabled (vial-qmk)
cd ~/vial-qmk
make boardsource/equals/60:vial
cp .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.


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.

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.

# In rules.mk
RGBLIGHT_ENABLE = yes

Then use in keymap:

RGB_TOG // Toggle RGB on/off
RGB_MOD // Next mode
RGB_RMOD // Previous mode
RGB_HUI // Hue +
RGB_SAI // Saturation +
RGB_VAI // Brightness +

Terminal window
# QMK Setup
git clone https://github.com/qmk/qmk_firmware.git
cd qmk_firmware
python -m venv venv && source venv/bin/activate
pip install qmk
make git-submodule
# Vial Setup (separate from QMK)
cd ~
git clone https://github.com/vial-kb/vial-qmk.git
cd vial-qmk
make git-submodule
# List keyboards
qmk list-keyboards | grep -i boardsource
# Compile (QMK/VIA)
qmk compile -kb <keyboard> -km <keymap>
qmk flash -kb <keyboard> -km <keymap>
# Compile (Vial)
cd ~/vial-qmk
make <keyboard>:vial
make <keyboard>:vial:flash
# Clean build artifacts
qmk clean # For QMK
make clean # For Vial
# View keyboard info
qmk info -kb <keyboard>

QMK:

VIA:

Vial (Open Source):


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 = yes in rules.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 = yes and VIAL_ENABLE = yes in rules.mk
  • Using vial-qmk fork (not standard qmk_firmware)
  • VIAL_KEYBOARD_UID set in config.h
  • vial.json exists in keymap directory
  • Keymap named exactly vial (not via or anything else)
  • Using make commands, not qmk compile
  • Unlock combo defined (or VIAL_INSECURE set)

For additional help, see QMK Discord.