Quick Start
This guide takes a new user from zero setup to custom UI in production.
Prerequisites
- Python 3.12+ (
python --version) - uv package manager (install)
One-Click Development Environment (Recommended)
The fastest way to get started is using a pre-configured cloud development environment. This handles all dependencies, database setup, and the studio environment for you.
Project Setup (from scratch)
mkdir my-project && cd my-project
uv init
# Core runtime
uv add framework-m
# Developer tooling
uv add --dev framework-m-studio
1) Base App
Create your first app package:
uv run m new app crm
cd crm
uv sync
Create your first DocType:
uv run m new doctype Contact
Example Contact DocType (src/crm/doctypes/contact/doctype.py):
from __future__ import annotations
from typing import ClassVar
from framework_m import DocType, Field
class Contact(DocType):
__doctype_name__: ClassVar[str] = "Contact"
first_name: str = Field(description="First name")
last_name: str = Field(description="Last name")
email: str = Field(default="", description="Email address")
class Meta:
requires_auth: ClassVar[bool] = False # Allow access without login (dev)
apply_rls: ClassVar[bool] = False # All users can access all records (dev)
naming_rule: ClassVar[str] = "autoincrement"
api_resource: ClassVar[bool] = True
show_in_desk: ClassVar[bool] = True
permissions: ClassVar[dict[str, list[str]]] = {
"create": ["All"],
"read": ["All"],
"write": ["All"],
"delete": ["All"],
}
Note: if your project has not implemented authentication and a custom permission model yet,
keep these Meta defaults so Desk create/read/update/delete works during development.
If you want to implement authentication and role-based access control (RBAC), refer to:
Install database drivers (important):
# Development (SQLite)
uv add --dev aiosqlite
# Production (PostgreSQL)
uv add asyncpg
Set database URL in your app .env (important):
Create crm/.env (the same directory where you run uv run m ...):
# Development (SQLite)
DATABASE_URL=sqlite+aiosqlite:///./dev.db
# Production example (PostgreSQL)
# DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/crm
If DATABASE_URL is missing at runtime, API create/list routes can respond but won’t persist records.
Run migrations:
For rapid development, use sync to automatically update your database schema based on your DocType definitions:
uv run m migrate sync
Alternatively, use all to run both versioned patches and schema synchronization:
uv run m migrate all
Or follow the traditional Alembic workflow:
uv run m migrate init
uv run m migrate create "Add Contact doctype" --autogenerate
uv run m migrate run
2) Base Desk UI
Start with bundled Desk UI:
uv run m prod
Open http://127.0.0.1:8888/desk/.
Use this when you want ready UI with no custom frontend build.
3) Frontend
Initialize your frontend scaffold:
# Execute from crm root
uv run m new frontend
This creates ./frontend under your crm app and installs dependencies.
Build custom UI when ready:
cd ./frontend
pnpm build
# Switch back to crm root
cd ..
4) Extended UI Apps
Install additional app packages (example):
uv add business-m wms-app
Discovery model:
framework_m.appsentry points → backend DocTypes/routesframework_m.frontendentry points → frontend plugin discovery in dev/build flows
5) Extended UI Plugins
Add local/custom plugin entries in your app frontend modules for composition.
Typical flow:
- add plugin entry files
- run
uv run m devfor live composition - run frontend build for production artifacts
Reference docs:
6) m dev
Use this for day-to-day development:
uv run m dev
Current behavior:
- Backend API server on
127.0.0.1:8888 - Frontend Vite dev server on
127.0.0.1:5173 - Hot reload for frontend + backend workflows
7) m prod
Use this for production-style runtime:
# Bundled Desk via backend
uv run m prod
# Custom UI deployment mode (prebuilt dist served by backend)
uv run m prod --with-frontend
Important for --with-frontend:
- Backend serves prebuilt static from
frontend/dist(viaFRAMEWORK_M_FRONTEND_DIST) m prod --with-frontenddoes not run frontend build
For high-scale production, nginx/CDN static serving is optional. Use this reusable nginx template:
It includes placeholders:
${APP_NAME}${STATIC_ROOT}${BACKEND_UPSTREAM}
Quick API Check
curl http://127.0.0.1:8888/health
curl http://127.0.0.1:8888/api/meta/doctypes