Development

How to run Modular from a clone.

Requirements#

First run#

sh
cp .env.example .env
# put a real secret in MODULAR_CREDENTIAL_SECRET:
node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"
pnpm install

pnpm --filter @modular/plugin-sdk build   # the web Vite config imports from its build output
pnpm --filter "./plugins/*" build         # the server loads the built-in plugins at start

pnpm dev:fullstack

The web app is on http://localhost:5174, the server on http://127.0.0.1:21423.

The server reads .env.local, then .env, from the repository root; a variable already set in the environment wins. MODULAR_DATA_DIR=./data is relative to the server's working directory, so the data lands in apps/server/data/ (modular.db, server.log, preferences/, runtimes/). Use an absolute path to put it elsewhere.

No model provider keys are needed to start; everything else in .env.example (OpenTelemetry, Langfuse, PostHog) is off by default.

Commands#

CommandRuns
pnpm dev:fullstackServer and web app
pnpm dev:server, pnpm dev:webOne of them
pnpm dev:desktopThe Electron app (builds relayd, the mac bridge and native helpers first)
pnpm typecheckTypeScript, every package
pnpm lintESLint
pnpm testUnit tests (builds the SDK and plugins first)
pnpm test:serverServer tests only
pnpm exec cucumber-js --config e2e/cucumber.mjsEnd-to-end scenarios (e2e/)
pnpm e2e:checkChecks the e2e suite's own rules
pnpm generate:webRegenerates the web API client from the server's OpenAPI
pnpm gen:cliRegenerates the CLI commands from the server's OpenAPI
pnpm brand:renderRenders the logo and icons (resources/brand)
pnpm observability:upLocal OpenTelemetry collector and Prometheus (Docker)

pnpm generate:web boots the server's route table, so it needs MODULAR_DATA_DIR set (any empty folder).

Running inside a Modular terminal#

A terminal opened by an installed Modular exports that app's variables (its data folder, its port, NODE_ENV=production). Unset them before running a dev server or tests, or they point at your real data:

sh
env -u NODE_ENV $(env | grep -E '^(MODULAR_|CRADLE_)' | cut -d= -f1 | sed 's/^/-u /') pnpm dev:fullstack

Native module clash#

better-sqlite3 is used by the server under Node and by the desktop app under Electron, and pnpm links one copy. Whichever rebuilt it last wins, and the other fails with was compiled against a different Node.js version. To restore the Node build:

sh
cd "$(node -e "console.log(require('node:path').dirname(require.resolve('better-sqlite3/package.json',{paths:['./apps/server']})))")"
npx prebuild-install -r node -t "$(node -p 'process.versions.node')"

apps/server/scripts/rebuild-electron-runtime.mjs rebuilds a private copy for Electron when packaging, so packaging does not break the Node build.

Rules for contributors#

AGENTS.md holds the project's rules: ownership of data, the View / Container split in the web app, styling, testing. Architecture is the map of the code.