param()
param() declares a named value with a control, at the top of a part() body. The part's file shows it in the Part Parameters panel; an assembly sets it per instance. Exported from a *.params.js file instead, the same call declares a project parameter that every model imports. The return value is a plain number, string, boolean or array, so the rest of the body reads it like any variable: in a sketch guess, a dimension, an extrude distance, an if, a loop bound.
A mounting plate with five parameters — two sizes, a thickness slider, a screw-size select and a checkbox that turns the chamfer on or off:

The code behind it
import { part, param, sketch, line, circle, extrude, chamfer } from 'fluidcad/core';
import { coincident, distance, fix, horizontal, vertical } from 'fluidcad/constraints';
// A parametric mounting plate: a rectangle with a clearance hole near each
// corner, extruded to a thickness. Every param() is a control in the
// Part Parameters panel; an assembly sets them per instance —
// insert(plate, { Width: 120, Hole: 6.6 }).
export const plate = part('Mounting plate', () => {
// Number fields with bounds. The label is the key an override uses.
const width = param('Width', 80, 'number', { min: 40, max: 200, step: 5, group: 'Size' });
const depth = param('Depth', 50, 'number', { min: 30, max: 150, step: 5, group: 'Size' });
// A slider — the panel shows a range control instead of a field.
const thickness = param('Thickness', 6, 'slider', { min: 3, max: 15, step: 0.5, group: 'Size' });
// A select with fixed choices: the value is the option's `value`, here
// the clearance diameter of the screw.
const hole = param('Hole', 5.5, 'select', {
options: [
{ label: 'M4', value: 4.5 },
{ label: 'M5', value: 5.5 },
{ label: 'M6', value: 6.6 },
],
description: 'Clearance hole for the mounting screw',
});
// A checkbox turns a feature on or off.
const chamfered = param('Chamfer top edges', true, 'checkbox');
sketch('xy', () => {
// The outline's guesses use the parameters directly …
const b = line([-width / 2, -depth / 2], [width / 2, -depth / 2]);
const r = line([width / 2, -depth / 2], [width / 2, depth / 2]);
const t = line([width / 2, depth / 2], [-width / 2, depth / 2]);
const l = line([-width / 2, depth / 2], [-width / 2, -depth / 2]);
coincident(b.end(), r.start());
coincident(r.end(), t.start());
coincident(t.end(), l.start());
coincident(l.end(), b.start());
horizontal(b);
horizontal(t);
vertical(r);
vertical(l);
fix(b.start(), [-width / 2, -depth / 2]);
// … and so do the dimensions: change Width in the panel and the
// solver re-sizes the outline.
distance(b.start(), b.end(), width);
distance(r.start(), r.end(), depth);
// One hole inset 8 mm from each corner.
const inset = 8;
for (const sx of [-1, 1]) {
for (const sy of [-1, 1]) {
circle([sx * (width / 2 - inset), sy * (depth / 2 - inset)], hole / 2);
}
}
});
const e = extrude(thickness);
if (chamfered) {
chamfer(1, e.endEdges());
}
});
How to create parameters
There are three ways to declare a parameter, and they all write the same statement into the part body.
From the Part Parameters panel
- Open the Part Parameters panel from the left rail.
- The Part dropdown under the header names the part the new parameter goes into: the active part by default (or, after you step out of it, the part you stepped out of), or any other part of the file.
- Click + and choose New parameter. The dialog asks for Label, Type and, per type, Min / Max / Step, the select options and how they are Shown as, plus a Group and a Description. Expose as property, off by default, also declares a property of the same name valued by the new parameter, so an assembly can read it. (New property declares a property on its own instead.)
- Apply. The
param()call is written at the top of that part's body, below the parameters already there, and its control appears in the panel. Edit or delete a parameter later from its row's menu.
Renaming a parameter's label renames the variable it binds after it (Wall thickness binds wallThickness) and every read of that variable, along with the insert(…, { Label }) overrides that address it by label in the assemblies that insert the part. Deleting a parameter puts its default value in place of every read of the variable and drops those overrides, so the model still builds. The dialog says what it is about to rewrite, in which files, before you confirm. A default that is an expression over other names (base * 2) can only replace reads where those names are in scope; anywhere else the delete is refused, naming the reads to rewrite by hand first.
In code
Call param() at the top of the part body, before the geometry that reads it, and keep the returned value:
export const plate = part('Mounting plate', () => {
const width = param('Width', 80, 'number', { min: 40, max: 200, step: 5 });
const chamfered = param('Chamfer top edges', true);
// … geometry that uses width and chamfered
});
param(label, defaultValue) // control inferred from the default
param(label, defaultValue, type, options?) // explicit control
The label is the key: what the panel shows and what an assembly override names. Without a type, the control follows the default value: a boolean is a checkbox, a number a number field, a string a text field. The controls and their options are listed under Types and options.
From an expression input
Every numeric field in a feature dialog, and every value the sketcher asks for (a dimension, a typed tool readout, the coordinate pill) is an expression input. Type a name and a value where you would type a number:

Here the distance between two hole centres is typed as pitch = 64. The commit writes const pitch = param("pitch", 64); at the top of the part body, below the parameters already there, adds the param import if it is missing, and uses pitch in the statement: distance(a.center(), b.center(), pitch). The panel gains a pitch control.
pitch = 64declarespitchwith the default 64. A fresh name on its own,pitch, declares it with the field's current number as the default; the dropdown offers this as a new row.- The P toggle beside the field decides what is declared. On, the declaration is a
param()as above. Off, it is a plainconst pitch = 64placed just before the statement (at the top of the sketch body for a sketch dimension) — a named value with no control. The toggle is on by default, and a flip is remembered by every expression input for the rest of the session. - Names follow JavaScript: letters, digits,
_and$, no leading digit, no keyword. A name that already exists is refused ('pitch' is already defined) rather than declared twice. - A parameter declared this way has no options. Add
min,max, agroupor adescriptionfrom the panel row's menu, or in the source.
Using parameters
In the Part Parameters panel
Every param() of the part is one control: a number field, a slider, a text field, a select, a checkbox or a colour swatch. Parameters with the same group fold together; a description is the control's help text. Change a value and the model re-renders with it. The new value is written into the source once you finish with it — Enter or leaving the field, letting go of a slider, a pick, a click — as one undo step: it becomes the default in the param() statement, or, while a configuration is on screen, that configuration's value. A value outside the parameter's min and max, or not one of its options, is refused with a message. The pencil on a row edits the rest of the declaration: its label, its control and its range.
In configurations
A part that comes in a few known variants — an M6, M8 and M10 bracket — names each set of values as a configuration: a row of a configurations({...}) table at the top of the body, listing only the parameters it changes. The Configuration row at the top of the Part Parameters panel creates rows and switches between them, and a value you change while a row is on screen is written into that row; an assembly inserts a row by name, and export writes one file per row. See Configurations.
In code
The value is a plain number, string, boolean or array. Use it wherever a literal would go, and let it drive structure too:
const holes = param('Holes', 4, 'select', { options: [{ label: '2', value: 2 }, { label: '4', value: 4 }] });
sketch('xy', () => {
// …
distance(b.start(), b.end(), width); // a parametric dimension
for (let i = 0; i < holes; i++) { /* … */ } // a parametric count
});
if (chamfered) { // a parametric feature
chamfer(1, e.endEdges());
}
A parameter used in a sketch dimension re-solves the sketch on every change: distance(a, b, width) is the parametric form of a dimension.
In an expression input
The same input that declares a parameter also references one. What you type is written into the statement as source:
| You type | The statement gets | Meaning |
|---|---|---|
25 | 25 | A plain number. |
thickness | thickness | The parameter's variable. Type the first letters and pick it from the dropdown. |
width / 2 - 8 | width / 2 - 8 | Arithmetic over variables: + - * / % and parentheses. |
Math.min(width, depth) / 4 | Math.min(width, depth) / 4 | Any JavaScript expression, written verbatim. |
pitch = 64 | pitch | Declares a new parameter and uses it — see above. |

- The dropdown opens while you type a name. It lists the variables the statement can see: exact matches first, then prefix matches, each with its initializer as a hint. ↑ ↓ move, Tab or Enter fill in the highlighted name, Enter again commits.
- The ghost preview evaluates plain arithmetic over known variables as you type. An expression it cannot evaluate, a
Math.call for instance, keeps the last value until the re-render lands; the statement is still written verbatim. - Editing a statement shows its source expression, not the resolved number. Committing it unchanged leaves the source alone, so re-opening a dialog never re-declares anything.
From an assembly
Inserted into an assembly, the part's parameters are its interface: the Insert dialog shows a form per instance, Edit parameters… re-opens it, and in code the values go in insert(plate, { Width: 120 }). See Part parameters.
Scope
Where a param() may be called, and who can see it:
- Inside a
part()body (or anassembly()body), or exported from a*.params.jsfile. Called at the top level of a part or assembly file it is an error —param('Width') must be declared inside a part() body — move it to the top of the part's callback, or export it from a *.params.js file to share it across the project— because a model file has no parameters of its own. At the top level of a*.params.jsfile, as the initializer of anexport const, it is a project parameter: shared by every file that imports it, listed in the Global Parameters panel rather than under a part, and never overridden per instance. Inside a body, declare parameters at the top, before the geometry that reads them: a declaration deeper down still belongs to the part, but the code above it cannot use it. - The part owns it. The panel lists a part's parameters under that part, and a parameter declared through the UI lands in the part chosen in the Part dropdown. Two parts in one file have two independent lists; the same label may appear in both, but must be unique within one part.
- The expression dropdown is part-scoped. A dialog's field offers the declarations the statement can see: the part body's own (its
param()s first), the file's top-level declarations above the part, and, inside a sketch, the sketch body's variables. The part's own properties are offered too, with a Pr chip, and the project parameters after everything of the file, with a G chip — picking one writes the import. Another part's parameters are never offered. Feature results (const e = extrude(…)) are hidden; only values and expressions are. - The value depends on who is building. When the file itself renders, the value is the default, or whatever the panel last wrote back. When an assembly inserts the part, the value comes from that
insert()'s overrides, and the default fills in for every label the insert does not name. The consuming file's panel never shows an inserted part's parameters — they are the part's interface, not the assembly's controls. - Read it inside the callback. A
part()definition is lazy and may build several variants, so the body must read parameters throughparam()at run time — not from a variable computed outside the callback, which every variant would share.
Types and options
type | Default value | Options |
|---|---|---|
'number' | number | min, max, step |
'slider' | number | min, max, step |
'text' | string | — |
'select' | one option's value (or an array with multi: true) | options: [{ label, value }], multi, multiControlType: 'select' | 'checkboxes' | 'chips' |
'checkbox' | boolean | — |
'color' | CSS colour string | — |
Every type also takes group (parameters with the same group fold together in the panel) and description (shown as the control's help text).
const teeth = param('Teeth', 24, 'number', { min: 8, max: 120, step: 1, group: 'Gear' });
const tint = param('Body colour', '#4a90d9', 'color');
const finish = param('Finish', ['deburr'], 'select', {
options: [{ label: 'Deburr', value: 'deburr' }, { label: 'Anodise', value: 'anodise' }],
multi: true,
multiControlType: 'chips',
});
Rules
- A
param()is only valid inside apart()orassembly()body, or as anexport constat the top level of a*.params.jsfile (project parameters). - Declare parameters at the top of the body, before the geometry that uses them.
- Labels are unique within a part.
- Setting parameters from an assembly is covered in Part parameters.