Installation
FluidCAD ships as a desktop app for Windows, Linux and macOS, and as an npm package for running it from a Node.js project in your browser. Pick the download for your system below, or skip to the npm installation if you prefer to work from a terminal.
Models are .part.js files (parts) and .assembly.js files (assemblies), written by the viewport as you work.
The links below point at the current release, FluidCAD 0.0.43; every build is also on the GitHub releases page. The app updates itself from there afterwards: it checks quietly in the background and installs the next version when you quit.
Windows
- Installer (x64):
FluidCAD-0.0.43-win-x64.exe
Run the installer and follow the prompts. The installer is not code-signed, so Windows SmartScreen shows Windows protected your PC on first launch; choose More info → Run anyway.
Linux
- AppImage (x64):
FluidCAD-0.0.43-linux-x86_64.AppImage - Debian package (x64):
FluidCAD-0.0.43-linux-amd64.deb
For the AppImage, mark it executable (chmod +x FluidCAD-*.AppImage) and run it. For the .deb, install it with sudo apt install ./FluidCAD-*.deb. The AppImage updates itself; the .deb is updated by installing the next release's package.
macOS
- Disk image (Apple Silicon):
FluidCAD-0.0.43-mac-arm64.dmg - Zip archive (Apple Silicon):
FluidCAD-0.0.43-mac-arm64.zip
Open the disk image and drag FluidCAD into Applications. The build is signed and notarized, so it opens without a Gatekeeper prompt.
npm installation
If you would rather run FluidCAD from a Node.js project and model in your browser, install the fluidcad package into a folder, start its server, and model in the tab it opens.
Prerequisites
- Node.js version 24 or higher
- npm (comes with Node.js)
Create a new project
mkdir my-hinge && cd my-hinge
npm i fluidcad
npx fluidcad init
npx fluidcad serve
init creates a project with the following structure:
my-hinge/
├── package.json
├── jsconfig.json
├── fluidcad.json ← project configuration (engine pin, unit)
├── init.js ← required project entry point
└── box.part.js ← your first model
serve starts the FluidCAD server and opens the viewport in your browser. Leave it running while you model; press Ctrl+C to stop it.
| Flag | Description | Default |
|---|---|---|
-w, --workspace <path> | Path to your project | Current directory |
-p, --port <port> | Server port — if it's already taken, the next free one is used | 3100 |
--no-open | Don't launch a browser — for CI and remote sessions | opens by default |
To run from elsewhere, point -w at your project: npx fluidcad serve -w ./my-app.
fluidcad.json records which FluidCAD version the project was authored against and, optionally, its default length unit. See Project configuration.
FluidCAD model files use the .part.js extension (assemblies use .assembly.js). The viewer only picks up files with this naming convention.
FluidCAD is installed per-project and run with npx fluidcad …; a global install (npm install -g fluidcad) is not supported. The engine and your editor's type hints both resolve from the project's node_modules, so the package must live there. If you see Cannot find module 'fluidcad' imported from …, run npm i fluidcad inside the project.
The init.js file
Every FluidCAD project must have an init.js file at its root. It initializes the engine and must be present before any .part.js file can run:
import { init } from 'fluidcad';
export default await init();
Do not modify this file — it is generated by npx fluidcad init and is required for the viewer to work. Project configuration covers what it does and what else lives in the project folder.
The starter file
Open box.part.js. This is the file that was generated for you, and it is what the viewport shows when the server starts:
import { arc, line, extrude, shell, sketch, part } from "fluidcad/core";
import { radius, distance, equal, vertical, horizontal, tangent, coincident } from 'fluidcad/constraints';
export const box = part("Box", () => {
sketch("xy", () => {
const l1 = line([-30, -20], [30, -20]);
const a1 = arc([30, -20], [40, -10], [30, -10]);
const l2 = line([40, -10], [40, 10]);
const a2 = arc([40, 10], [30, 20], [30, 10]);
const l3 = line([30, 20], [-30, 20]);
const a3 = arc([-30, 20], [-40, 10], [-30, 10]);
const l4 = line([-40, 10], [-40, -10]);
const a4 = arc([-40, -10], [-30, -20], [-30, -10]);
coincident(l1.end(), a1.start());
coincident(a1.end(), l2.start());
coincident(l2.end(), a2.start());
coincident(a2.end(), l3.start());
coincident(l3.end(), a3.start());
coincident(a3.end(), l4.start());
coincident(l4.end(), a4.start());
coincident(a4.end(), l1.start());
tangent(l1, a1);
tangent(a1, l2);
tangent(l2, a2);
tangent(a2, l3);
tangent(l3, a3);
tangent(a3, l4);
tangent(l4, a4);
tangent(a4, l1);
horizontal(l1);
horizontal(l3);
vertical(l2);
vertical(l4);
equal(a1, a2);
equal(a1, a3);
equal(a1, a4);
distance(l4, l2, 80);
radius(a1, 10);
});
const e = extrude(25);
shell(-2, e.endFaces());
});
Here's what each part does:
part("Box", () => { ... })declares a named part and exports it asbox. Everything inside the callback builds that part, and the export is what an assembly would laterinsert().sketch("xy", () => { ... })starts a sketch on the XY plane (the horizontal plane). The geometry inside is drawn with explicit coordinates, and the constraint statements pin it down.- The four
line(...)and fourarc(...)calls draw a rounded rectangle: eacharc(start, end, center)sits between two sides. Thecoincidentstatements join the ends into a closed loop andtangentmakes every corner blend smoothly. horizontal,vertical,equal,distance,radiussquare the sides up, make all four corners the same size, set the width between the left and right sides to 80, and dimension the corner radius to 10.extrude(25)takes the sketch and pulls it up 25 units into a 3D solid. We store the result ineso we can reference its faces later.shell(-2, e.endFaces())hollows out the solid with a wall thickness of 2, removing the top face (endFaces()). The negative value means the shell goes inward.
The result is a small open-topped tray. In the viewport, every one of those statements is a row in the History panel, and the sketch's constraints fold behind an N constraints row under the sketch.
Beyond serve
The same CLI writes STEP, STL, and PNG files without opening the UI, and runs the MCP server that lets AI agents drive your workspace:
npx fluidcad export step # every shape → <entry>.step
npx fluidcad export png --view front --open
See the CLI Reference for every command and flag, or run npx fluidcad --help.
Editor setup
FluidCAD runs on its own — no editor required. If you'd rather keep modelling in the editor you already use, it also ships official extensions for VS Code and Neovim. Every option drives the same engine and the same live 3D viewport; pick whichever fits your workflow.
The built-in editor (recommended)
The desktop app and npx fluidcad serve both open the whole product: the 3D
viewport, every interactive modelling tool, and a code editor with completion
driven by the engine's own type declarations — so extrude( shows its signature
and edge().onPlane( completes, always matching the version you're running.
The code editor stays hidden until you ask for it. Open it from the ☰ menu or
with Ctrl+B; it takes width from the left, so the model
narrows rather than being covered. Files are tabs in the top bar, added with
+ — there's no file tree, because the editor already knows about every file
in the workspace.
VS Code
- Install the FluidCAD extension from the VS Code Marketplace.
- Open your project folder in VS Code.
- Open the Command Palette (
Ctrl+Shift+Pon Windows/Linux,Cmd+Shift+Pon macOS) and run Show FluidCAD Scene.
A 3D viewport opens in a side panel. It updates live as you edit any .part.js file.
Neovim
Add the plugin with lazy.nvim:
{
"Fluid-CAD/FluidCAD",
config = function()
require("fluidcad").setup()
end,
ft = { "javascript" },
}
Open a .part.js file — the server starts automatically. Run :FluidCadOpenBrowser to open the 3D viewport in your browser.
Any other editor
npx fluidcad serve works alongside any editor too: leave the browser tab on
the viewport and edit .part.js in whatever you prefer — the model rebuilds on
save. Opening the same file in two editors at once is the one thing to avoid.
Next
Getting started builds a butt hinge from an empty part to an animated assembly, one tool per step.