Managing ZMK files

Hi, i am interested in knowing what the proper practice is for managing zmk files. Do you use separate files for your left and right halves? Do you use a separate file for your custom behaviors? Or do you just define them in your keymap files?
15 Replies
TyperAI
TyperAI4w ago
Great questions! Managing ZMK config files comes down to how you want to keep things organized, but there are some common practices: Left vs Right halves - You don’t need separate keymap files for left and right halves.
Both halves share the same keymap definition (for Corne or Lily58, etc.), and ZMK takes care of assigning the correct layout to each side.
- What you do need are the appropriate board/shield definitions (e.g., corne_left and corne_right). Those are already set up correctly if you’re using our config repos. You only manage one config, and ZMK builds separate firmware files for left and right. Custom behaviors - You can define custom behaviors in two ways: 1. Directly in your keymap.keymap file – this is fine if you only have a couple of small tweaks and don’t want extra files. 2. Separate .keymap or .keymap.inc files – this is cleaner if you have more complex or multiple custom behaviors. Many people keep a behaviors/ folder or use behaviors.dtsi for things like combos, tap-dance definitions, or macros, then reference those in the main keymap. Recommended Approach - Keep one config repo (per board) with: - keymap.keymap → main layers
- behaviors.dtsi (optional) → custom combos/macros/tap-dance
- zmk.yml and .conf → build + low-level settings
- Let GitHub Actions (or local builds) generate the separate UF2s for left and right halves automatically.
That way your repo stays modular and tidy, but you aren’t duplicating keymaps for each half.
👉 I recommend checking the ZMK docs on user setup and keymaps for good examples. Would you like me to share a sample directory structure of a clean ZMK config repo so you can see how most people organize their files?
Installing ZMK | ZMK Firmware
Unlike other keyboard firmwares, ZMK Firmware has been built from the ground up to allow users to manage
Keymaps & Behaviors | ZMK Firmware
ZMK uses a declarative approach to keymaps, using devicetree syntax to configure them in a .keymap file.
John
JohnOP4w ago
you recommended a separate keymap file or a keymap.inc file or a behaviors.dtsi. What would you really recommend out of the three?
TyperAI
TyperAI4w ago
Good follow-up 👍
Here’s the practical breakdown:
- Keep behaviors in the keymap.keymap file if you only have a few (like one or two combos/macros). This keeps things simple, no need to over-structure.
- Use a separate .keymap.inc file if your keymap itself is getting long and cluttered. Some people split out layers into layers.keymap.inc, then include them in keymap.keymap. This is nice if you experiment a lot with layouts.
- Use a behaviors.dtsi (or similar .dtsi file) if you have lots of custom stuff—complex combos, multi-step macros, or a set of reusable behaviors you may change independently of the main keymap. This is the most modular approach and is easier to maintain long term, especially if the same behaviors might be shared across multiple boards.
Recommendation: If you’re just starting out → keep it all in keymap.keymap until it feels messy.
Once it does → move custom behaviors to behaviors.dtsi and keep keymap.keymap focused on layers.
That way you don’t overcomplicate your setup in the beginning but still have a clean migration path when your config grows.
Would you like me to show you an example snippet of how a behavior declared in behaviors.dtsi gets referenced in your keymap.keymap? That might make the tradeoff clearer.
John
JohnOP4w ago
what is dtsi and what else should i know about behaviors.dtsi
TyperAI
TyperAI4w ago
Great question — this can feel a little “magic” when you’re new to ZMK!
What is a .dtsi file? - .dtsi stands for Device Tree Source Include.
- ZMK uses Zephyr under the hood, and Zephyr uses device tree files (.dts/.dtsi) to describe hardware, overlays, and features.
- You can think of .dtsi as a config include file that gets pulled into your build. It’s not its own standalone thing; it extends or overrides definitions in your keyboard’s main device tree.
What is behaviors.dtsi used for? - It’s a convention, not a requirement. Many ZMK users name the file behaviors.dtsi (or combos.dtsi, macros.dtsi) to keep organization clean.
- Inside behaviors.dtsi, you define reusable behaviors like: - Combos — key chords (q + w → ESC)
- Tap dance — different action depending on 1/2/hold taps
- Macros — multiple key presses mapped to one action
- Then your keymap.keymap just calls those behaviors, instead of keeping all the logic mixed in with layer definitions.
Why split it out? - Keeps your keymap.keymap tidy (layers only).
- Makes it easier to reuse behaviors across layouts.
- You can turn behaviors on/off or adjust them in a single file.
Things to know: - .dtsi follows the device tree syntax (nodes, labels, properties). It’s not C or keymap syntax.
- You always include it from your board’s overlay or config repo. Example in keymap.keymap:
/ {
behaviors {
#include "behaviors.dtsi"
};
};

/ {
behaviors {
#include "behaviors.dtsi"
};
};

- It’s optional. For small configs, you don’t need it. For growing configs, it’s the most maintainable path.
👉 TL;DR: Use behaviors.dtsi as your “toolbox” file for combos/macros/tap-dance. The keymap.keymap then just shows what gets placed on each layer.
Would you like me to put together a tiny working example behaviors.dtsi + keymap.keymap snippet so you can see the pattern in action?
John
JohnOP4w ago
how would the code know where to find the file? I'm just going to be placing it in the same folder as the keymap, but will it know that?
TyperAI
TyperAI4w ago
Excellent detail to clarify 👌
ZMK won’t automatically “know” about your behaviors.dtsi file unless you explicitly include it.
How it works - When you add a #include "behaviors.dtsi" in your keymap.keymap (or in another .dtsi), the build process looks in the same folder as the including file first.
- So if your behaviors.dtsi is in the same folder as keymap.keymap (usually config/ in your repo), then just:
#include "behaviors.dtsi"

#include "behaviors.dtsi"

will work without extra steps.
- If you put it in a subfolder (e.g. config/behaviors/behaviors.dtsi), then you need to reference the relative path:
#include "behaviors/behaviors.dtsi"

#include "behaviors/behaviors.dtsi"

Typical layout (single-board repo)
config/
│── keymap.keymap
│── behaviors.dtsi
│── shields.dtsi (if needed)
│── <board>.conf
│── zmk.yml
config/
│── keymap.keymap
│── behaviors.dtsi
│── shields.dtsi (if needed)
│── <board>.conf
│── zmk.yml
Important notes - .dtsi includes are handled by the Zephyr preprocessor, not C — so the syntax must use #include "file.dtsi" (with quotes, not < >).
- If you ever get file not found errors, double-check: - The relative path you’re using - That your keymap.keymap and behaviors.dtsi are inside config/ (since only that directory is copied into the build environment).
--- 👉 So yes — if you drop behaviors.dtsi into the same folder as your keymap.keymap, a simple #include "behaviors.dtsi" is all you need.
Would you like me to show a minimal example of a behaviors.dtsi (say, a combo or tap-dance) and how it’s pulled into keymap.keymap? That way you can be confident it’s wired up correctly.
John
JohnOP4w ago
zmk already has an include called the behaviors.dtsi, so if i will be making a custom one, will i need to use a different file name?
TyperAI
TyperAI4w ago
Ah, you’ve caught an important detail 👍
You’re right — ZMK itself already has a behaviors.dtsi in its core, where the built‑in behaviors (like &kp, &mo, etc.) are defined. If you create another file with the same name, it may cause confusion or path ambiguity during the build.
Best Practice - Yes, give your custom file a different name.
- Common patterns people use: - custom_behaviors.dtsi - my_behaviors.dtsi - Split by type if you have a lot: - combos.dtsi - macros.dtsi - tap_dance.dtsi Then in your keymap.keymap (or another .dtsi) you include it like:
#include "custom_behaviors.dtsi"
#include "custom_behaviors.dtsi"
Why not reuse the name? - If you call it behaviors.dtsi, the build system might include ZMK’s version instead of yours, depending on search order.
- To avoid clobbering or confusing yourself later, it’s cleaner to give your custom one a unique name.
--- 👉 TL;DR: Don’t override ZMK’s behaviors.dtsi. Put your own definitions in e.g. custom_behaviors.dtsi, and include that explicitly in your keymap.keymap.
Would you like me to show you a small side‑by‑side example of a custom_behaviors.dtsi + how it plugs into keymap.keymap? That way you can be 100% sure how the separation looks.
John
JohnOP4w ago
why does zmk use underscores a lot. I prefer minus signs cause they are easier to type. Is this not allowed?
TyperAI
TyperAI4w ago
Great observation 👀
This comes down to device tree naming rules (since ZMK is built on Zephyr, which uses devicetree files).
Why underscores (_)? - In devicetree node labels and property names, - (minus/hyphen) is not allowed in identifiers.
- Hyphens are reserved for node names (like /soc/gpio@40002000) or DTS paths, not for labels or references.
- So when we write things like custom_behaviors.dtsi or tap_dance, we’re actually defining labels or macros, which must follow C identifier rules:
- only a-z, A-Z, 0-9, and _
- must start with a letter
Example ✅ Allowed:
&mt { // "modifier-tap"
flavor = "hold-preferred";
}
&mt { // "modifier-tap"
flavor = "hold-preferred";
}
(Node names and comments can have -)
⚠️ Not Allowed in labels/behaviors:
&modifier-tap { // ❌ will fail to build
flavor = "hold-preferred";
}
&modifier-tap { // ❌ will fail to build
flavor = "hold-preferred";
}
File Names For file names, technically you can use - (e.g. my-behaviors.dtsi). But most people stick with _ because: - It visually matches the identifiers inside.
- It avoids confusion if you later rename/move things.
- It’s the convention in ZMK repos, so examples are consistent.
--- 👉 Answer:
- Inside .dtsi and keymap files → you must use underscores _ for labels/behaviors.
- For file names → you could use -, but _ is more conventional and consistent with ZMK/Zephyr.
Would you like me to show you exactly where you can and can’t use hyphens inside a ZMK config (e.g. node name vs label vs filename)? That usually clears up the confusion.
John
JohnOP4w ago
i plug in the left half. I drag the new uf2 file. However, the keyboard disconnects and the changes arent made i plug in the left half. I drag the new uf2 file. However, the keyboard disconnects and the changes arent made The operation can’t be completed because you don’t have permission to access some of the items.
TyperAI
TyperAI4w ago
Got it — thanks for the details. This sounds like a flashing issue with the nice!nano bootloader rather than ZMK itself. Here’s what could be happening: Why it disconnects - The NICENANO drive always disappears immediately after you drop a .uf2 file — that’s normal! The bootloader reboots into firmware mode, so it looks like it “disconnects.” - If your changes don’t appear, the UF2 either wasn’t written successfully or you flashed the wrong file (left vs right). “Permission” error This is common on macOS when copying files to UF2 drives: - There is a known Finder bug on macOS 13.0 that causes permission errors. Upgrading to macOS 13.1+ fixes it.
- On any macOS version, Finder can sometimes fail. Workarounds: - Use Terminal instead of Finder:
cp /path/to/your/file.uf2 /Volumes/NICENANO/

cp /path/to/your/file.uf2 /Volumes/NICENANO/

- Or try a different USB cable/port. Things to check 1. Make sure you put the nice!nano in bootloader mode (double-tap reset → should show NICENANO drive). 2. Verify the correct half’s firmware (left for the left side, right for the right side). 3. Check your macOS version → update if still on 13.0. 4. If the error persists, try flashing from a Linux/Windows machine to rule out macOS Finder issues. 👉 Full guide: Entering the Bootloader --- Would you like me to walk you through how to confirm the left-half firmware actually flashed (checking via keyboard response / ZMK Studio)? That’s the easiest way to tell if the file truly took.
Entering the Bootloader | Typeractive.xyz Documentation
Learn how to enter the bootloader on your nice!nano.
John
JohnOP4w ago
board still not flashing properly The operation can’t be completed because you don’t have permission to access some of the items.
TyperAI
TyperAI4w ago
Thanks for confirming — since you’re still getting that permission error, this really points to the macOS + Finder issue. Here’s how to work around it: 1. Use Terminal instead of Finder Instead of dragging the UF2 in Finder, copy via Terminal: 1. Plug in your left half and double-tap reset so it shows up as NICENANO.
2. Open Terminal.
3. Run something like:
cp ~/Downloads/6_col_corne_left-nice_nano_v2-zmk.uf2 /Volumes/NICENANO/

cp ~/Downloads/6_col_corne_left-nice_nano_v2-zmk.uf2 /Volumes/NICENANO/

(adjust the path to match where your UF2 file is saved).
4. The NICENANO drive should disappear right after — that means it flashed successfully. 2. Verify macOS version - If you’re on macOS 13.0, update to 13.1 or newer — Finder has a known bug in 13.0 that breaks UF2 flashing with “permission denied.”
- Later versions (13.1+) don’t have this bug. 3. Other things worth checking - Try a different USB cable/USB port (some cables are power-only).
- Make sure you’re flashing the left firmware UF2 to the left half (ZMK repo provides both halves separately).
--- 👉 In short:
- Stick to Terminal cp — this bypasses Finder’s permissions bug.
- If you’re still on macOS 13.0, update macOS first.
Do you want me to give you an exact Terminal command template where you just paste your UF2 filename? That way you can copy-paste without worrying about syntax.

Did you find this page helpful?