Frontend Customization Guide
This guide explains how to work with Framework M's bundled Desk UI and customize it for your needs.
Overview
Framework M ships with a pre-built Desk UI (similar to Frappe's Desk), providing a batteries-included experience. You can:
- Use the bundled UI - Zero npm setup required
- Customize the bundled UI - Run Vite dev server for live development
- Create custom frontends - Scaffold new UIs from templates
Quick Start: Bundled Desk UI
No npm Required
The bundled Desk UI is included in the Python package. Just install and run:
# Install Framework M
pip install framework-m
# Start the application in production mode
m prod
That's it! Open http://127.0.0.1:8000 in your browser. The Desk UI is pre-built and served automatically from the inner package assets.
How It Works
- Pre-built static files are bundled in the PyPI
framework-m-standardpackage - Served automatically by the backend when no custom frontend is active
- No build step needed - zero-configuration setup for immediate use
Development Mode: Live Customization
When you need to customize the UI or build your own plugins, use m dev. This command is orchestration-heavy and requires Framework M Studio to be installed in your environment.
Starting with Frontend Dev Server
m dev
By default, m dev:
- Auto-detects if a
frontend/directory exists. - Starts the Backend (uvicorn) with auto-reload.
- Starts the Frontend (Vite) concurrently with HMR (Hot Module Replacement).
- Starts Studio on port 9999 for visual editing.
Development Workflow
# Start the full dev stack
m dev
# Backend typically runs on http://127.0.0.1:8000
# Frontend runs on http://localhost:5173
# Studio runs on http://localhost:9999
# Edit files in frontend/src/ or use Studio to scaffold
# Changes appear instantly!
Creating Custom Frontends
Initialize Frontend Template (Requires Studio)
Scaffolding new frontends is a developer-only feature provided by the m studio toolkit. You cannot scaffold in a production environment as the templates and logic are not installed there.
# Initialize in ./frontend using studio CLI
m studio new frontend
# Or specify custom location
m studio new frontend ../my-custom-ui
This scaffolds a complete React + TypeScript + Vite application with:
- @framework-m/desk npm package (providers pre-configured)
- Refine.dev setup for rapid admin UI development
- React Router for client-side routing
- Tailwind CSS + shadcn/ui components
- Vite for fast development and optimized builds
Template Structure
frontend/
├── package.json # Dependencies and scripts
├── .npmrc # GitLab registry configuration
├── vite.config.ts # Vite build configuration
├── tsconfig.json # TypeScript configuration
├── src/
│ ├── index.tsx # Application entry point
│ ├── App.tsx # Refine.dev setup
│ ├── pages/ # Page components
│ │ ├── Dashboard.tsx
│ │ └── Login.tsx
│ └── components/ # Reusable components
├── public/ # Static assets
└── README.md # Setup instructions
Install Dependencies
After scaffolding, install dependencies:
cd frontend
pnpm install
Run Development Server
# Option 1: Run frontend only
cd frontend
pnpm dev
# Option 2: Run both backend + frontend (recommended)
cd ..
m prod --with-frontend
Using @framework-m/desk Package
What is @framework-m/desk?
The @framework-m/desk npm package provides pre-built providers for Refine.dev:
frameworkMDataProvider- CRUD operations via REST APIauthProvider- Cookie-based authenticationliveProvider- WebSocket real-time updates- Constants - API URLs configured from environment
This package is automatically included in templates created with m new frontend .
Basic Usage
import { Refine } from "@refinedev/core";
import { BrowserRouter } from "react-router-dom";
import {
frameworkMDataProvider,
authProvider,
liveProvider,
} from "@framework-m/desk";
export default function App() {
return (
<BrowserRouter>
<Refine
dataProvider={frameworkMDataProvider}
authProvider={authProvider}
liveProvider={liveProvider}
resources={[
{
name: "User",
list: "/users",
show: "/users/:id",
edit: "/users/:id/edit",
create: "/users/new",
},
]}
>
{/* Your application routes */}
</Refine>
</BrowserRouter>
);
}
API Configuration
The package uses globalThis.__FRAMEWORK_CONFIG__ for API URLs:
// Set in index.html or environment
declare global {
interface Window {
__FRAMEWORK_CONFIG__?: {
API_URL?: string;
META_URL?: string;
WS_URL?: string;
};
}
}
// Default values (from vite.config.ts proxy)
globalThis.__FRAMEWORK_CONFIG__ = {
API_URL: "http://127.0.0.1:8888/api/v1",
META_URL: "http://127.0.0.1:8888/api/v1/meta",
WS_URL: "ws://127.0.0.1:8888/api/v1/live",
};
Authentication Flow
The authProvider handles cookie-based authentication:
// Login
await authProvider.login({
email: "admin@example.com",
password: "password",
});
// Check authentication status
const { authenticated } = await authProvider.check();
// Get current user identity
const identity = await authProvider.getIdentity();
// Logout
await authProvider.logout();
Real-time Updates
The liveProvider enables WebSocket subscriptions:
import { liveProvider, closeAllConnections } from "@framework-m/desk";
// Subscribe to User changes
liveProvider.subscribe({
channel: "User",
types: ["created", "updated", "deleted"],
callback: event => {
console.log("User changed:", event);
},
});
// Cleanup on unmount
useEffect(() => {
return () => closeAllConnections();
}, []);
GitLab Registry Configuration
Why GitLab Registry?
The @framework-m/desk package is published to your project's GitLab Package Registry (private by default). This ensures:
- Version control - Package versions tied to git tags
- Private packages - Not exposed to public npm registry
- CI/CD integration - Automatic publishing on releases
Configure Access
The template includes a .npmrc file:
@framework-m:registry=https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/
Local Development
For local development outside CI/CD, create a Personal Access Token:
- GitLab Settings → Access Tokens
- Create token with
read_apiscope - Configure npm:
# In your frontend directory
echo "//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${YOUR_TOKEN}" >> .npmrc
CI/CD Authentication
In GitLab CI/CD, authentication is automatic:
# .gitlab-ci.yml
install-frontend:
script:
- cd frontend
- echo "@framework-m:registry=https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" > .npmrc
- echo "//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}" >> .npmrc
- pnpm install
The CI_JOB_TOKEN is automatically available in GitLab CI/CD pipelines.
Publishing Updates
To publish a new version of @framework-m/desk:
# Update version in package.json
cd libs/framework-m-desk
npm version patch # or minor, major
# Create git tag
git tag framework-m-desk@v0.1.1
git push origin framework-m-desk@v0.1.1
# GitLab CI will build and publish automatically
CLI Reference
m prod
Start Framework M in production mode.
m prod [OPTIONS]
Options:
--port PORT- Backend port (default: 8000)--host HOST- Host to bind to (default: 0.0.0.0)--with-frontend- Serve custom built assets fromfrontend/distvia backend.--frontend-dir PATH- Frontend directory path (default: ./frontend)
Examples:
# Production mode - serve bundled assets
m prod
# Production mode - serve custom built assets from frontend/dist
m prod --with-frontend
# Custom port
m prod --port 3000
m dev (Development Only)
Standard full-stack development command. Starts backend, frontend (Vite), and Studio.
m dev [OPTIONS]
Options:
--port PORT- Backend port (default: 8000)--studio-port PORT- Studio port (default: 9999)--no-frontend- Disable the Vite frontend process.--enable-worker- Start the Taskiq background worker concurrently.
m studio new frontend (Requires Studio)
Initialize a new frontend from the standard template.
m studio new frontend [TARGET]
Arguments:
TARGET- Target directory for frontend (default: ./frontend)
Examples:
# Initialize in ./frontend
m studio new frontend
# Initialize in custom directory
m studio new frontend ../my-ui
What It Does:
- Copies template from
apps/studio/frontend - Creates directory structure with all configuration files
- Runs
pnpm installto install dependencies - Provides instructions for next steps
Deployment
Building for Production
cd frontend
pnpm build
This creates optimized static files in frontend/dist/.
Serving Production Build
The production build can be served in two ways:
Option 1: Bundle with PyPI package (recommended)
Include the built frontend in your PyPI distribution:
# Build frontend
cd frontend
pnpm build
# Copy to package static directory
cp -r dist/* ../libs/framework-m/src/framework_m/static/
# Build Python package
cd ../libs/framework-m
uv build
# Deploy
pip install dist/framework_m-*.whl
m prod
Option 2: Separate static file server
Serve frontend/dist/ with nginx, Caddy, or any static file server. Configure CORS to allow API requests from your frontend domain.
Distributed UI & Macroservices
For large applications, you can decompose your UI into independent macroservices. See the Distributed UI Guide for more information on how to configure remote MFE discovery and transparent proxying.
Environment Variables
Configure API endpoints for production:
<!-- frontend/index.html -->
<script>
window.__FRAMEWORK_CONFIG__ = {
API_URL: "https://api.yourdomain.com/api/v1",
META_URL: "https://api.yourdomain.com/api/v1/meta",
WS_URL: "wss://api.yourdomain.com/api/v1/live",
};
</script>
Or use environment variables with Vite:
# .env.production
VITE_API_URL=https://api.yourdomain.com/api/v1
VITE_META_URL=https://api.yourdomain.com/api/v1/meta
VITE_WS_URL=wss://api.yourdomain.com/api/v1/live
Troubleshooting
Studio Features Not Found
Symptom: m studio new frontend or m dev fails with "Command not found".
Solution: Ensure framework-m-studio is installed in your development environment. This package is typically not included in production-lean images.
uv pip install framework-m-studio
npm Package Not Found
Symptom: npm ERR! 404 Not Found - GET https://gitlab.com/...
Solutions:
- Check
.npmrcconfiguration:cat frontend/.npmrc - Verify CI_PROJECT_ID is set correctly
- Create Personal Access Token for local development
- Check package registry permissions in GitLab
Port Already in Use
Symptom: Error: Address already in use
Solutions:
# Use different port
m prod --port 8001
# Kill process using port 8888
lsof -ti:8888 | xargs kill -9
WebSocket Connection Failed
Symptom: Live updates not working
Solutions:
- Check WS_URL in configuration
- Verify WebSocket endpoint is accessible
- Check firewall/proxy settings allow WebSocket connections
- Use secure WebSocket (wss://) in production with HTTPS
Build Failures
Symptom: pnpm build fails with errors
Solutions:
- Clear node_modules and reinstall:
rm -rf node_modules pnpm-lock.yaml
pnpm install - Check Node.js version (requires Node 20+)
- Verify TypeScript errors:
pnpm tsc --noEmit
Best Practices
Development Workflow
- Start with bundled UI - Verify backend works first
- Scaffold frontend - Only when customization is needed
- Use
m prod --with-frontend- Simplifies dual-server management - Hot reload is automatic - Backend and frontend reload on changes
- Commit often - Frontend changes tracked in git
Project Structure
my-project/
├── app.py # Backend application entry
├── frontend/ # Custom frontend (gitignored by default)
│ ├── src/
│ │ ├── App.tsx
│ │ └── pages/
│ └── package.json
├── models/ # Your DocTypes
├── controllers/ # Business logic
└── pyproject.toml # Python dependencies
Version Control
Recommended .gitignore:
# Frontend
frontend/node_modules/
frontend/dist/
frontend/.npmrc # Contains auth tokens
# Python
__pycache__/
*.pyc
.venv/
dist/
*.egg-info/
Commit frontend source:
- ✅ Commit
frontend/src/,frontend/package.json,frontend/vite.config.ts - ❌ Don't commit
node_modules/,dist/,.npmrcwith tokens
Performance Optimization
- Use production builds -
pnpm buildcreates optimized bundles - Enable gzip/brotli - Compress static assets
- Use CDN - Serve static files from CDN in production
- Lazy load routes - Split code by route for faster initial load
- Cache API responses - Use Refine's built-in query caching
Next Steps
- DocType Guide - Learn how to create data models
- Protocol Reference - Explore protocol interfaces
- Refine Documentation - Learn Refine.dev framework
- Vite Documentation - Understand Vite build tool
Support
- GitHub Issues: Report bugs or request features
- Community Forum: Ask questions and share knowledge
- Documentation: Browse full documentation