Reference

OpenSCAD modules and functions: children(), let() and use vs include

An OpenSCAD module is a reusable piece of geometry that you call like a built-in shape. A function is a reusable calculation that returns a value. Modules draw, functions compute, and together they turn a long script into a small, readable part library.

6 min read Updated October 2026 Intermediate OpenSCAD reference · 7 of 8
In short
  1. module name(args) { … } makes geometry; function name(args) = expr; returns a value.
  2. Give every argument a sensible default and call with named arguments.
  3. children() lets a module wrap, move or repeat whatever you put after it.
  4. Use include for BOSL2 and for files whose variables you need; use imports only modules and functions.

Modules vs functions at a glance

ModuleFunction
Defined withmodule m(a=1) { … }function f(a=1) = a * 2;
ProducesGeometryA value: number, vector, list, string or another function
Called asA statement: m(a=2);Part of an expression: x = f(2);
BodyAny statements, loops and ifOne expression; use let(), ?:, list comprehensions or recursion
Can take children()YesNo

Define a module with default arguments

module washer(od=10, id=3.2, t=1.6) {
  difference() {
    cylinder(h=t, d=od, $fn=48);
    translate([0, 0, -1]) cylinder(h=t + 2, d=id, $fn=32);
  }
}

washer();                                          // all defaults
translate([20, 0, 0]) washer(od=20, id=8, t=3);    // named arguments
translate([45, 0, 0]) washer(16, 5);               // positional: od, id

Arguments can be passed by position or by name, and any you leave out take their default. Named arguments are clearer and survive reordering, so prefer them for anything past the first one or two.

A misspelled argument only warns. washer(thickness=3) runs with the default t and prints a warning that thickness is not a parameter. If a change seems to do nothing, check the console.

children(): modules that wrap other shapes

A module can act like a transformation. Whatever you write after the call is available inside it as children():

module array_x(n=5, spacing=10) {
  for (i = [0:n - 1])
    translate([i * spacing, 0, 0]) children();
}

array_x(6, 12) cylinder(h=8, d=6, $fn=32);
FormDraws
children()All children
children(i)Child number i, counting from 0
children([0:2])A range of children
children([0, 3])A list of chosen children
$childrenNot a call: the number of children passed

Pass several children in braces and place each one separately:

module row(spacing=15) {
  for (i = [0:1:$children - 1])          // the explicit step stops [0:-1] when empty
    translate([i * spacing, 0, 0]) children(i);
}

row() {
  cube(8);
  translate([4, 4, 4]) sphere(d=8, $fn=32);
  translate([4, 4, 0]) cylinder(h=8, d=8, $fn=32);
}

Using array_x from above, how many shapes does array_x(3, 10) { cube(2); sphere(1); } draw?

  1. 2
  2. 3
  3. 6
  4. It is an error: children() needs an index

children() with no index draws every child, so each of the 3 loop passes draws both the cube and the sphere: 6 shapes.

Special variables like $fn flow into modules

Variables that start with $ are passed down to everything a call reaches, including shapes inside your modules. Setting $fn on the call changes the resolution of the whole module without adding a parameter:

module post() { cylinder(h=10, d=8); }

post($fn=6);                         // hexagonal
translate([15, 0, 0]) post($fn=64);  // smooth: $fn flows into the module

Functions return values

A function body is a single expression. Use them for unit conversions, derived dimensions and point lists for polygon():

function in_to_mm(i) = i * 25.4;
function circle_pts(r, n=36) =
  [for (i = [0:n - 1]) [r * cos(i * 360 / n), r * sin(i * 360 / n)]];

linear_extrude(height=3) polygon(circle_pts(20, 8));          // an octagon plate
translate([50, 0, 0]) cube([in_to_mm(1), in_to_mm(0.5), 3]);  // 25.4 × 12.7 mm

A bare polygon() is 2D. The PrintPal editor only exports 3D solids, so wrap 2D shapes in linear_extrude() as above; see 2D shapes and extrusion.

let(): local names inside a function

function diagonal(w, h) = let (w2 = w * w, h2 = h * h) sqrt(w2 + h2);
echo(diagonal(30, 40));   // ECHO: 50
cube([30, 40, 2]);

List comprehensions: loops that build lists

Functions cannot contain a for statement, but a list comprehension does the same job and returns a list. circle_pts() above is one. You can filter with if, as in [for (x = v) if (x > 0) x], and the C-style form carries a running value from one step to the next:

heights = [5, 10, 3, 8];

// Running total with a C-style list comprehension: the z position of each layer
function z_offsets(h) = [for (i = 0, z = 0; i < len(h); z = z + h[i], i = i + 1) z];

z = z_offsets(heights);       // [0, 5, 15, 18]
echo(z);
for (i = [0:len(heights) - 1])
  translate([0, 0, z[i]]) cylinder(h=heights[i], d=30 - 5 * i, $fn=48);

Function literals (OpenSCAD 2021.01 and later)

Since OpenSCAD 2021.01 a function can be stored in a variable and passed to another function, which makes small helpers like map_list() possible:

double = function (x) x * 2;
function map_list(v, f) = [for (x = v) f(x)];

sizes = map_list([2, 4, 6], double);     // [4, 8, 12]
echo(sizes);
for (i = [0:len(sizes) - 1])
  translate([i * 15, 0, 0]) cube(sizes[i]);

Recursion and its limits

Recursion is the other way to loop in a function. OpenSCAD has two limits to know about:

KindLimit
Plain recursion, e.g. n + sum_to(n - 1)A few thousand levels, depending on the build
Tail recursion: the recursive call is the whole result of one branch1,000,000 steps, because OpenSCAD turns it into a loop
Recursive modulesNo loop conversion, so keep the depth modest

Going past a limit stops the run with Recursion detected calling function (or calling module). If you see it, switch to the tail-recursive form or a list comprehension.

// Plain recursion: fine for small inputs
function sum_to(n) = n == 0 ? 0 : n + sum_to(n - 1);

// Tail recursion: the recursive call is the last step,
// so OpenSCAD turns it into a loop
function sum_to_tail(n, acc=0) = n == 0 ? acc : sum_to_tail(n - 1, acc + n);

echo(sum_to(100));            // ECHO: 5050
echo(sum_to_tail(100000));    // ECHO: 5.00005e+9
cube(5);

Recursive modules are handy for self-similar shapes:

module tower(levels, d=30, h=6) {
  cylinder(h=h, d=d, $fn=48);
  if (levels > 1)
    translate([0, 0, h]) tower(levels - 1, d * 0.8, h);
}

tower(5);   // five tiers, each 80% of the one below

Group related settings in a list

When a module needs many values, a named list keeps presets together. The trade-off is that p[3] is less readable than hole_d, so comment the order:

// [length, width, thickness, hole_d, hole_inset]
M3_BRACKET = [30, 20, 4, 3.4, 5];

module bracket(p) {
  difference() {
    cube([p[0], p[1], p[2]]);
    translate([p[4], p[1] / 2, -1])
      cylinder(h=p[2] + 2, d=p[3], $fn=24);
  }
}

bracket(M3_BRACKET);

use vs include: loading library files

include <file.scad>use <file.scad>
Modules and functionsImportedImported
The file's variables and constantsImported, and your file can override themNot visible to your file
The file's top-level shapesDrawnSkipped
Where it can goAnywhereTop level only
Good forBOSL2, constants files, your own shared settingsMCAD, files with demo geometry at the bottom

With use, a library's modules work but its variables do not. MCAD's nuts_and_bolts.scad defines a constant MM; through use it is unknown, while boltHole() still works:

use <MCAD/nuts_and_bolts.scad>

echo(MM);   // undef, with a warning: use does not import variables
difference() {
  cube([20, 20, 6]);
  translate([10, 10, -1]) boltHole(3, length=10);   // modules do come through
}

Load BOSL2 with include. use <BOSL2/std.scad> fails, because BOSL2's modules depend on constants such as BOT and TOP that use leaves behind. See OpenSCAD libraries for what is bundled in the PrintPal editor.

With include, a variable set in your own file wins over the library's value silently, which is useful for settings and confusing if a name clashes by accident.

Name features for the CAD Agent with @feature

In the PrintPal editor, a // @feature: name comment on the line above a module call names that piece of the model. The editor turns each tag into a chip: click it to highlight the feature in 3D and drop @feature[name] into the chat, so you can write "make @feature[corner_holes] 4 mm" instead of describing where the holes are. Tags placed above a module definition highlight nothing, because a definition draws nothing on its own.

plate_length = 60;
plate_width = 30;
plate_thickness = 4;
hole_d = 3.4;
hole_inset = 6;

module plate() { cube([plate_length, plate_width, plate_thickness]); }

module corner_holes() {
  for (x = [hole_inset, plate_length - hole_inset],
       y = [hole_inset, plate_width - hole_inset])
    translate([x, y, -1]) cylinder(h=plate_thickness + 2, d=hole_d, $fn=24);
}

difference() {
  // @feature: base_plate
  plate();
  // @feature: corner_holes
  corner_holes();
}

The CAD Agent guide covers the chips and edit workflow, and the parametric design guide shows how to lay out a whole file this way.

Ask the CAD Agent for reusable modules

Write a reusable module polar_array(n, radius) that places its children() n times around a circle. Use it to make a 70 mm round desk organiser, 30 mm tall, with six 12 mm pen holes 20 mm deep on a 50 mm circle. Keep all sizes as parameters at the top.

Refactor my current design: one module per feature with default arguments, parameters at the top, a // @feature tag above each module call, and a single top-level call that builds the part.

AI tool · free to tryCAD AgentGet clean, modular OpenSCAD with named features you can click and edit.Open tool

Frequently asked questions

What is the difference between a module and a function in OpenSCAD?

A module creates geometry and is called as a statement, like cube(). A function returns a value such as a number, vector or list, and is used inside expressions. Functions cannot draw shapes, and modules cannot return values.

What does children() do in OpenSCAD?

children() draws the shapes written after a module call, so your module can move, repeat or cut them like a built-in transformation. children(i) draws only child number i, counting from 0, and $children holds how many children were passed.

What is the difference between use and include in OpenSCAD?

include pastes the whole file in, so its variables, modules, functions and top-level shapes all come along. use imports only modules and functions, skips the file's top-level shapes and keeps its variables private. BOSL2 must be loaded with include.

Can an OpenSCAD function contain a loop?

Not a for statement, because a function body is a single expression. Use a list comprehension such as [for (i = [0:n-1]) i * 2], or recursion. Tail-recursive functions are turned into loops and can run up to a million steps.

Can I store a function in a variable in OpenSCAD?

Yes, since OpenSCAD 2021.01. Write f = function (x) x * 2; then call it as f(3) or pass f to another function as an argument.

Sources & further reading