CNC macro programming essentials for shops that run repeat work
Macros exist to stop you from editing the same G-code 40 times a week. This page covers what macro variables actually store, how IF/GOTO and WHILE loops behave on a Fanuc-style control, how to pass values into a subprogram, and the cases where plain G-code is still the better answer.

In this article
- 1
- 2
- 3
- 4
- 5
- 6
- 7
What CNC macro programming essentials actually cover
A macro is a program that can read the machine state, do arithmetic, and decide what to do next. Standard G-code cannot. It runs top to bottom and stops. If you want the same bolt-circle cycle at three different radii, you either write three programs or you write one macro and pass the radius in as a number.
On a Fanuc-style control the arithmetic lives in variables. #1 through #33 are local and get wiped when the macro returns. #100 through #199 and #500 through #999 are common variables that survive a power cycle on most machines. #1000 and up are usually tied to system data such as tool offsets, current position, or alarm state. Confirm the range on your own control before you write to it.
The practical gain is not speed of the spindle. It is speed of the setup. A parametric drilling cycle that accepts hole count, radius, start angle, and peck depth turns a 20-minute edit into a 20-second change. That matters most on families of parts, not on one-off work.
One boundary up front: macros do not fix a bad process. If the fixture moves, a macro just moves the error faster. Fix the setup first.
Variables, arguments, and how values get in
A macro call looks like this: G65 P9010 X50.0 Y25.0 R20.0 A0. B8. I-2.5. Inside program O9010, the letters map to local variables in a fixed order. X is #24, Y is #25, Z is #26, A is #1, B is #2, C is #3, I is #4, J is #5, K is #6, R is #18. Get that mapping wrong and the cycle runs at the wrong radius with no alarm.
You can also call a macro with G65 or with M-code aliases set in parameters, and you can call it as a subprogram with M98. G65 passes arguments, M98 P9010 L3 does not pass a value but will repeat the subprogram three times. Use G65 when the geometry changes, M98 when it does not.
Local variables reset to null on each call unless you use the G66 modal form or copy them out to common variables. A common mistake is storing a tool offset in #1 and expecting it next cycle. It is gone. Write it to #101 or #501 if it needs to persist.
Readability costs nothing and saves a lot. Comment the variable map at the top of the program: (X=#24 center X, Y=#25 center Y, R=#18 radius). Six months later, that comment is the difference between a two-minute change and an hour of decoding.
IF/GOTO, WHILE, and conditional logic on the floor
The two workhorses are IF [condition] GOTO n and WHILE [condition] DO m ... END m. IF is a one-shot branch. WHILE loops back until the condition fails. Both compare using operators you write as brackets: EQ, NE, GT, GE, LT, LE, plus AND, OR, XOR. They evaluate as true or false, nothing else.
A typical use is depth stepping. Instead of hard-coding five Z passes, you write WHILE [#1 GT #2] DO 1, cut, then #1 = #1 - #3, END 1. Change the step size in one place and the loop count adjusts itself. That is the whole point of CNC macro programming essentials: one input, many passes.
Guarding is not optional. Add a check at the top of the macro: IF [#18 LE 0] GOTO 9999, then at N9999 write an alarm or a message. A macro that runs with a null radius will happily rapid the tool into the fixture. The control will not stop it for you.
Watch for infinite loops. If #1 never satisfies the exit condition, the WHILE never ends and the machine sits there. Always step the counter inside the loop body, and test the loop with the spindle in the air before you cut metal.
- 1IF is a branch, WHILE is a loopIF runs once and jumps. WHILE repeats until false.
- 2Step the counter inside the loopForget this line and the program hangs.
- 3Guard every inputA null argument can send the tool into the vise.
- 4Keep loops shallowNested DO loops read poorly and debug badly.
A bolt-circle drilling macro, step by step
The classic example is a bolt circle. Nine inputs: center X, center Y, start angle, hole count, radius, hole depth, peck depth, rapid plane, and feed. Everything else is computed. The macro converts the start angle and the index angle to radians, then loops once per hole using SIN and COS to find X and Y.
Written out, the loop reads: WHILE [#5 GT 0] DO 1, then #30 = #2 + #18 * COS[#3], #31 = #1 + #18 * SIN[#3], then a G81 or G83 at that position, then #3 = #3 + 360 / #4 and #5 = #5 - 1, END 1. Degrees to radians is the step people forget. Multiply by 0.017453.
The gain shows up on setup. A 12-hole flange becomes a 16-hole flange by changing one number. Same program, different R value. On the machines we run, that is a ten-second edit instead of a full re-post.
The limit is equally clear. If the hole pattern is irregular, if the holes sit on a spline, or if the part is a one-off, a macro costs more time than it saves. Write it once, use it a hundred times, or do not write it at all.
- 1Radians, not degreesMultiply by 0.017453 before SIN or COS.
- 2Count down, not upDecrement the hole counter so the loop exits cleanly.
Where macros help and where they get in the way
Macros pay back on part families. A family shares geometry and varies in a few dimensions: length, hole count, boss diameter, thread depth. If you make that family more than a handful of times a year, the parametric program wins. The CAM post stops being the bottleneck.
Macros also pay on in-process checks. You can probe a datum, write the result to a common variable, and shift the work offset before the first cut. On a five-axis job with a long setup, that catches a fixture error before it becomes scrap. We run 16 simultaneous 5-axis centers where that check is standard practice.
They do not pay on one-off prototypes. A macro takes longer to write and prove out than a posted program. For a single part, posted G-code is faster and easier to verify. The trade-off is real: a macro is an investment in repeat work.
They also do not pay when the CAM system already handles it. If your post outputs a clean parametric cycle and the operator never edits it, adding a macro layer just adds a place for the two systems to disagree.
Proving out a macro before it cuts metal
Run the macro in single block with the tool at a safe Z. Watch the position display, not the code. Confirm each computed X and Y matches the drawing before you let it feed. A wrong COS sign is invisible on screen and obvious on the part.
Set a hard upper bound on any loop: IF [#5 GT 100] GOTO 9999. A runaway loop is rare but it wastes a shift. The guard costs one line.
Keep the macro in a protected program number and back it up off the machine. Controls lose memory. A macro that only exists on one control is a single point of failure.
Finally, document the arguments on the setup sheet. The operator needs to know that R is the radius in millimeters, not the rapid plane. Ambiguity here is where most macro scrap comes from.
Macro vs plain G-code: which one fits the job
Match the programming method to the work, not to habit.
| Job condition | Use a macro | Use posted G-code |
|---|---|---|
| Part family with 3+ varying dimensions | Yes, one program, many sizes | No, re-post each variant |
| One-off prototype | No, write time exceeds run time | Yes, fast to verify |
| Bolt circles or hole arrays | Yes, radius and count as inputs | Only if count never changes |
| In-process probing and offset shift | Yes, reads state and reacts | Not possible in plain G-code |
| CAM post already parametric | Rarely, adds a second source of truth | Yes, single source of truth |
| Operator edits dimensions at the machine | Yes, one number changes the cycle | No, edit means re-post |
| Five-axis tapered wall or swarf cut | Sometimes, for tool vector math | Usually, CAM handles the vectors |
| Anything with a null argument risk | Only with a guard at the top | Not applicable |
The short version
If the part repeats and only a few numbers change, write the macro. If it runs once, post the G-code and move on. There is no prize for parametric code on a one-off.
Macro programming questions we get asked
Which variables survive a power cycle?
On most Fanuc-style controls, #100 through #199 and #500 through #999 are common variables that hold their value with the power off. Local variables #1 through #33 reset to null when the macro returns.
The exact range and behavior vary by control model and option. Check the manual for your machine before you store a tool offset or a work coordinate there.
What is the difference between G65 and M98?
G65 calls a macro and passes arguments through address letters such as X, Y, R, and A. M98 calls a subprogram. It repeats the block but does not hand it a value.
Use G65 when the geometry or the count changes between calls. Use M98 when the same fixed cycle just needs to run again.
Can I use macros on a lathe as well as a mill?
Yes. The variable and control-flow rules are the same across mill and lathe controls. What changes is the address-to-variable map, so verify it against the lathe manual.
Common lathe uses are family turning cycles, groove width steps, and thread depth passes driven by a single input.
How do I stop a macro from running with a bad input?
Put a guard at the top of the program. Test the critical arguments and branch to an alarm label if any of them is zero or negative.
A radius of zero or a null feed will not raise an error on its own. The control will simply execute it.
Does macro programming replace CAM software?
No. CAM still handles complex 3D surfacing, five-axis tool vectors, and rest machining far better than hand-written macros.
Macros are best for the parametric, repetitive cycles that CAM tends to output as fixed code. The two work side by side.
What tolerance can a macro-driven cycle hold?
The macro itself does not set tolerance. It computes positions. The machine and the process set the achievable result.
On our equipment, we hold ±0.005 mm and finishes from Ra 0.2–0.8 μm on the right setup, with 100% inspection before shipment.
Send us the part family and we will quote it
Upload a drawing and we return a quotation with a free DFM analysis within 12 hours. Production can start within 24 hours, and parts ship in 3–5 days.
12-hour quote100% inspectionNo minimum order quantityNDA on request