---
title: "Custom M-Codes in LinuxCNC: The M100 to M199 Setup Guide"
description: "LinuxCNC reserves M100 to M199 for your own commands: executable scripts the control runs mid-program. The setup steps, the P and Q words, the gotchas."
url: https://gcodepractice.com/journal/custom-m-codes-in-linuxcnc-setup/
canonical: https://gcodepractice.com/journal/custom-m-codes-in-linuxcnc-setup/
author: "Lawrence Arya"
authorUrl: https://www.linkedin.com/in/vibecoding/
published: 2026-06-07
updated: 2026-06-07
category: "Guides"
tags: ["linuxcnc", "m-codes", "m100", "customization"]
lang: en
---

# Custom M-Codes in LinuxCNC: The M100 to M199 Setup Guide

> **TL;DR** LinuxCNC gives you a hundred user-defined M-codes out of the box: an executable file named M100 through M199, placed in the directory your configuration points at, runs whenever a program calls that M number, with the P and Q words passed in as the two command-line arguments. The program waits for the script to finish before continuing, which makes custom M-codes perfect for relays, air blasts, markers, and logging, and wrong for anything slow.

On a Fanuc or any closed control, the M-codes above the standard core belong to the machine builder, and you live with whatever they chose. [LinuxCNC](https://en.wikipedia.org/wiki/LinuxCNC) flips that: the range M100 through M199 is reserved for you. Any executable file with one of those names becomes a real M-code, callable from any program, and the [M-code reference](https://linuxcnc.org/docs/html/gcode/m-code.html) documents the mechanism in a paragraph because a paragraph is genuinely all it takes.

## How the mechanism works

When the interpreter reaches a block like `M110 P5 Q2.5`, it looks for an executable file named `M110` in the directories your configuration points at for user commands. If it finds one, it runs it, passing the P and Q word values as the two command-line arguments, and the program pauses until the script exits. Exit success, and the program continues with the next block. That is the whole contract: a file name, an executable bit, two optional numbers in, a blocking call.

Everything interesting follows from the blocking part. The machine genuinely waits, which means a custom M-code can guarantee that the chuck clamped, the air blast finished, or the log line was written before the next move starts. It also means a script that hangs, hangs your program with it.

## Setup, step by step

| Step | Action | The gotcha |
| --- | --- | --- |
| 1 | Write the script in anything executable, shell or Python | Keep it short; it blocks the program while it runs |
| 2 | Name the file exactly M100 to M199 | The name is the M number: no extension, capital M |
| 3 | Make it executable | A missing executable bit is the classic silent failure |
| 4 | Put it in the directory your config searches for user commands | Wrong directory means the control reports an unknown M-code |
| 5 | Call it as M1xx P... Q... | P and Q arrive as the first and second command-line arguments |
| 6 | Test from MDI before any program uses it | Never debut a new M-code mid-program on real metal |

The testing row deserves its own habit. A new custom M-code gets three rehearsals: alone in a terminal, where you can see everything it prints; from the MDI line in a sim or with the machine idle; and only then inside a program. The [learning ladder for LinuxCNC](/journal/best-way-to-learn-linuxcnc-ngc-gui/) applies here unchanged, because a custom M-code is just more G-code vocabulary, except you wrote the dictionary entry.

## A worked example: the part logger

A script that stamps a log line every time a part finishes is the classic first custom M-code, and it pairs naturally with a [part counter in G-code](/journal/how-to-program-a-part-counter-in-g-code/):

```
#!/bin/bash
# /home/cnc/linuxcnc/nc_files/M119 : log a finished part
echo "$(date '+%F %T') part complete P=$1 Q=$2" >> /home/cnc/parts.log
exit 0
```

Call `M119 P1001` at the end of the program and every cycle appends a timestamped line with the job number you passed in P. Note the explicit `exit 0`: the interpreter treats the script's exit status as the verdict, so a clean exit is part of the job, and a nonzero exit is your deliberate way to stop a program when the script detects something wrong.

Good uses share a profile: short, physical or clerical, and worth blocking for. Pulsing a relay, firing an air blast, triggering a camera, writing a log, asserting an interlock. Bad uses share the opposite profile: anything long, anything interactive, anything that should really be a background job. The [G-code overview](https://linuxcnc.org/docs/html/gcode/overview.html) is the place to confirm how words like P and Q parse if your calling blocks get fancy.

## Document your dialect

The moment M119 means something on your machine, your machine has a builder layer, and you are the builder. Treat it the way you wish every builder treated you: a one-page table next to the machine listing each custom code, its arguments, and what it touches. Anyone who has hunted undocumented M-codes on a used machine knows exactly why; it is the same manual-owned-edge problem that closed controls have, covered from the other side in posts like the [structured program tricks of O-word subroutines](/journal/o-word-subroutines-linuxcnc-examples/), except here the documentation costs you ten minutes instead of an email to a vendor.

For behavior deeper than running a script, LinuxCNC also supports remapping codes to G-code routines and components, but that is a bigger tool for a bigger job. M100 to M199 covers the everyday cases first, and most machines never need more.

## Standard core first, custom layer second

Custom M-codes extend a vocabulary, and extensions only help if the base vocabulary is automatic. The standard M-code core, spindle, coolant, tool change, stops and end, is small, stable, and exactly the kind of material that recall practice installs permanently: the 60-second drill rounds on the [G-code practice page](/g-code-practice/) cycle it daily until M08 versus M09 stops costing a thought. Then the codes you invent sit on top of a foundation instead of a guess, the same way a [generated program](/journal/writing-a-script-to-generate-g-code/) sits on top of hand-written fluency.

## Sources

- [LinuxCNC: M-code reference](https://linuxcnc.org/docs/html/gcode/m-code.html)
- [LinuxCNC: G-code overview](https://linuxcnc.org/docs/html/gcode/overview.html)
- [Wikipedia: LinuxCNC](https://en.wikipedia.org/wiki/LinuxCNC)

## Frequently asked questions

### How do I set up custom M-codes in LinuxCNC?

Write a script, name the file exactly M100 through M199 with no extension, make it executable, and place it in the directory your configuration searches for user commands. A block like M110 P5 Q2.5 runs the file with 5 and 2.5 as its arguments, and execution waits until the script exits.

### What are the P and Q words for in LinuxCNC user M-codes?

They are the two values you can hand a custom M-code from G-code. Whatever P and Q carry in the calling block arrives in the script as its two command-line parameters.

### Why does my LinuxCNC custom M-code not run?

In order of likelihood: the file name is not exactly the M number, the executable bit is missing, the file is in a directory the configuration does not search, or the script exits with an error. Test alone in a terminal, then from MDI, before it appears in a program.

### What is the best way to memorize the standard M-codes before customizing them?

Recall practice. The standard core is small enough to drill into automatic memory in a couple of weeks, and the free G-Code Sprint app runs exactly that drill in 60-second rounds.

---

Source: https://gcodepractice.com/journal/custom-m-codes-in-linuxcnc-setup/
Author: Lawrence Arya — https://www.linkedin.com/in/vibecoding/
