Framework Development Setup
This guide is for contributors working on the Framework M source (the monorepo). It assumes you are comfortable with uv, pnpm, and a Python/TypeScript monorepo.
If you only want to build an application with the published packages, use Getting Started instead.
1. Prerequisites
| Tool | Version |
|---|---|
| Python | 3.12+ |
| uv | latest |
| Node.js | 24+ |
| pnpm | 12+ |
| git | any recent version |
| Docker | optional, for the Postgres/NATS/Redis stack |
2. Clone and install
Run everything from the repository root.
git clone https://gitlab.com/framework-m/framework-m.git
cd framework-m
# Python workspace + every optional extra + the dev group
uv sync --all-packages --all-extras --all-groups
# JavaScript workspace
pnpm install
# Build the internal JS packages (required before the dev server starts)
pnpm run ci:js:build-internal
Why each step is required:
uv sync --all-packages --all-extras --all-groupsinstalls all Python workspace members, thedevgroup, and the optional extras ([db],[server], the ASGI engines, etc.). Plainuv syncis not enough: importingframework_m_standard.bootstrapreaches SQLAlchemy,create_app()requirescryptography, andm devneeds an ASGI engine. All three live in extras.pnpm installlinks the pnpm workspace packages.pnpm run ci:js:build-internalbuilds@framework-m/ui,@framework-m/plugin-sdk,@framework-m/vite-plugin, and@framework-m/deskinto theirdist/directories.frontend/vite.config.tsimports@framework-m/vite-plugin, whose package exports point atdist/.dist/is gitignored, so a fresh clone fails to start Vite until this build runs.
Re-run the internal build whenever you change one of those packages' build output or exports.
3. Configure the local environment
Option A — SQLite
Create .env at the repo root:
DATABASE_URL=sqlite+aiosqlite:///./dev.db
# Generate with: openssl rand -hex 32
FRAMEWORK_M_JWT_SECRET=<paste-the-generated-value>
m dev sets M_DEV_MODE=true, so the JWT secret is not strictly required during development, but set it so local behaviour matches production. m prod does require it.
On startup, the dev server automatically syncs the schema from the registered DocTypes. To do that explicitly instead:
uv run m migrate sync
The monorepo already ships alembic.ini and alembic/, so m migrate init is not needed here.
Option B — Docker stack (Postgres/NATS/Redis)
cp .env.example .env
Confirm the database URL uses the async driver:
DATABASE_URL=postgresql+asyncpg://framework_m:${POSTGRES_PASSWORD}@localhost:5432/framework_m_dev
Then start the services:
docker compose up -d
NATS and Redis are optional; leave NATS_URL/REDIS_URL unset if you do not need them. The dev server still creates the schema from DocTypes on startup.
For Postgres, prefer the dev server's startup schema sync.
m migrate syncconverts+asyncpgto+psycopg2for its synchronous reflection step, andpsycopg2is not part of the extras.
4. Run the development stack
uv run m dev
With the monorepo's frontend/ present, this starts:
| Process | URL |
|---|---|
| Backend API | http://127.0.0.1:8888 |
| Vite UI | http://127.0.0.1:5173 |
Open http://127.0.0.1:5173/ for the UI. Vite proxies /api to the backend on :8888. In dev mode the backend does not serve the bundled Desk.
The backend uses an async server, which is Granian by default (part of --all-extras). Additionally Uvicorn is also supported.
If you want to force use an engine then run:
FRAMEWORK_M_SERVER_ENGINE=uvicorn uv run m dev
Notes:
- Without
DATABASE_URL, the backend runs stateless and logs[SyncSchema] Database disabled, skipping schema sync. m devuses honcho: if any one process exits, the whole stack is stopped. Usem dev --no-frontendto isolate a backend problem.m dev --studioalso starts Studio on port9999.- The frontend
predevhook merges package locales intofrontend/public/locales; review/revert unrelated locale changes before committing.
5. Create a login (optional)
To use the Desk UI, create a local user (in a second terminal while the dev server runs, or before starting it):
uv run m user create \
--email admin@local.com \
--password 'ChangeMe123!' \
--name "Local Admin" \
--role Admin
Sign in at http://127.0.0.1:5173/ with those credentials. --role is repeatable.
6. Run the checks
Lint and format
uv run ruff check .
uv run ruff format . # apply formatting
uv run ruff format --check . # CI-style check only
Type checking
Use the per-package wrapper (the same commands CI runs):
uv run m typecheck --cwd libs/framework-m-core --strict
uv run m typecheck --cwd libs/framework-m-standard --strict
uv run m typecheck --cwd libs/framework-m --strict
uv run m typecheck --cwd apps/studio --strict
uv run m typecheck --cwd libs/framework-mx-mongo --strict
Tests and architecture
uv run pytest -n auto
# Equivalent to the CI invocation:
# uv run m test -c pyproject.toml -n auto
uv run lint-imports
For full CI parity, also run:
uv run m lint libs/ apps/
uv run m format --check libs/ apps/
uv run m security libs/ apps/ -ll
7. Frontend and Studio
The shell's Vite config aliases @framework-m/desk, @framework-m/ui, and @framework-m/plugin-sdk to their source files, so changes there hot-reload during m dev. @framework-m/vite-plugin is loaded from its built dist/.
If the plugin itself is updated, rebuild it after changing:
pnpm --filter @framework-m/vite-plugin build
# or rebuild every internal package:
pnpm run ci:js:build-internal
To work on the Studio UI with hot reload:
# terminal 1 — Studio API/UI
uv run m studio --port 9999
# terminal 2 — Vite dev server
cd apps/studio/studio_ui
pnpm dev
Open http://127.0.0.1:9999/studio/ui/.
Avoid running pnpm build in apps/studio/studio_ui locally: it rewrites tracked static assets with hashed filenames. CI builds release assets; use pnpm dev for local work.
8. Troubleshooting
| Symptom | Fix |
|---|---|
No module named 'sqlalchemy' | Run uv sync --all-packages --all-extras --all-groups (plain uv sync omits [db]) |
Payload encryption requires the 'cryptography' package | Run the full sync above; cryptography comes from [server] |
No module named uvicorn / No module named granian | Run the full sync above; install [server-uvicorn]/[server-granian] for a targeted install |
Failed to resolve entry for package "@framework-m/vite-plugin" | Run pnpm run ci:js:build-internal |
[SyncSchema] Database disabled, skipping schema sync | Set DATABASE_URL in .env |
m dev exits after one process fails | Honcho stops the whole stack by design; scroll up to the failing process, or run m dev --no-frontend |
mypy: error: Missing target module, package, files, or command | Use uv run m typecheck --cwd <package> --strict |
| Postgres async-driver error | Use postgresql+asyncpg://…, not postgresql://… |
Locale files modified while running m dev | The frontend predev hook merges package locales; review/revert before committing |