If you've ever wondered how ERPNext, HRMS, Helpdesk, and dozens of other production-grade business apps got built so fast — the answer is Frappe. It's a metadata-driven, low-code and full-code Python framework that handles the boring-but-critical 80% (auth, permissions, forms, REST APIs, workflows, reports) so you can focus on actual business logic.
While learning Frappe, and turned them into this single guide. It's split into three parts:
- Core Concepts — what Frappe is, how to set it up, and how apps/sites are structured
- Development Essentials — the day-to-day toolkit: controllers, hooks, ORM, scripts, reports, APIs
- Framework Internals — what actually happens under the hood, from request to response
Let's dive in.
Part 1: Core Concepts
What is Frappe?
Frappe is an open-source, metadata-driven, low-code/full-code Python framework for building modern business applications. It bundles database management, forms, auth, role-based permissions, workflows, automation, reports, and REST APIs out of the box — so you're not reinventing the wheel for every project.
It's the engine behind ERP, CRM, HRMS, Helpdesk, Manufacturing, Healthcare, Education, and many other custom business solutions.
Prerequisites
Before you install Bench (Frappe's CLI), make sure you have:
| Requirement | Version |
|---|---|
| OS | Ubuntu 22.04 LTS+ / macOS / Linux |
| Python | 3.11+ |
| Node.js | 20+ |
| Yarn | 1.22.x |
| Database | PostgreSQL 14+ / MariaDB 10.6+ |
| Redis | 6+ |
| Git | latest |
| wkhtmltopdf | for PDF generation |
| Bench CLI | official CLI tool |
Installing Bench
Bench is the official CLI tool used to set up and manage Frappe instances (technically called benches):
pip3 install frappe-bench
bench init my-bench
cd my-bench
The Bench Directory Structure
my-bench/
├── apps/ # Installed apps
├── sites/ # Sites (databases)
├── config/ # Config files
├── logs/ # Logs
├── sites/assets/ # Public assets
└── Procfile # Process configuration
Creating an App
bench new-app my_app
This scaffolds a full app structure:
my_app/
├── my_app/
│ ├── doctype/ # Custom DocTypes
│ ├── report/ # Report scripts
│ ├── page/ # Custom pages
│ ├── api/ # API endpoints
│ ├── utils/ # Utility functions
│ ├── www/ # Public web assets (CSS, JS, images)
│ ├── hooks.py # App hooks & overrides
│ ├── patches.txt # Patches for migrations
│ └── modules.txt # List of modules in the app
├── tests/ # Unit & integration tests
├── README.md
├── requirements.txt
└── setup.py
Creating a Site
bench new-site site.local
Every site gets its own isolated structure:
sites/site.local/
├── site_config.json # DB, email, redis config
├── public/ # Public files (assets, uploads)
├── private/ # Private files (backups, keys)
└── logs/ # Site-specific logs
The key idea: each site has its own database, configuration, and files — this is what enables Frappe's multi-tenancy.
Installing an App into a Site
bench --site site.local install-app my_app
bench --site site.local migrate
This installs the app in the site and creates the necessary tables, assets, roles, and permissions.
Modules & DocTypes
A module groups related functionality within an app (e.g., Accounts, HR, Selling, Buying).
A DocType is the fundamental building block of Frappe — it defines the structure and behavior of data, and is rendered automatically into forms, lists, and reports.
Types of DocTypes:
- Regular DocType — a standard doctype with its own database table
- Single — a doctype with only one record (e.g., global settings)
- Child Table — a table nested inside a parent DocType
- Submittable DocType — supports workflow states (submit/cancel)
- Virtual DocType — not stored in the DB; used for calculated/reporting data
- Custom DocType — created by developers for custom requirements
Common Bench Commands
| Command | Purpose |
|---|---|
bench start |
Start all services |
bench stop |
Stop all services |
bench restart |
Restart all services |
bench new-site <site> |
Create a new site |
bench get-app <app> |
Download an app |
bench install-app <app> |
Install app in a site |
bench migrate |
Run database migrations |
bench update |
Update apps |
bench --site <site> console |
Open the site console |
bench --site <site> backup |
Backup a site |
bench list-apps |
List installed apps |
bench list-sites |
List all sites |
Notable Frappe Features
Authentication (Login, OAuth, LDAP, 2FA, Social Login), granular role-based permissions, dynamic form & list views, a REST API out of the box, workflow & automation engine, integrated Email/SMS/WhatsApp, background jobs & scheduler, built-in reports & dashboards, secure file management, multi-tenancy, and full extensibility — all under the MIT license.
Part 2: Development Essentials
Once your app and site are set up, here's the toolkit you'll actually use day-to-day.
1. Controllers & Methods
DocTypes ship with a Python controller class where you hook into the document lifecycle:
def before_insert(self): ...
def before_save(self): ...
def before_submit(self): ...
def on_update(self): ...
def on_submit(self): ...
def on_cancel(self): ...
def after_insert(self): ...
def on_trash(self): ...
These give you precise control over what happens at every stage of a document's life.
2. Users, Roles & Permissions
Frappe's permission system is deep:
Users & Roles,
Role Permission Manager,
Permission Levels (0–3),
User Permissions,
Document Permissions,
Permission Query Conditions,
Field-Level Permissions,
Share & Assign,
conditional (if-based) rules,
User/Role Profiles.
3. Hooks
hooks.py is where an app declares itself to the framework. Key hooks include:
-
doc_events→ document event handlers -
scheduler_events→ background scheduled jobs -
override_whitelisted_methods→ override REST API methods -
override_doctype_class→ replace a DocType's controller -
doctype_js→ customize form behavior -
app_include_js/css&web_include_js/css→ load Desk/Website assets -
jinja→ custom template methods -
fixtures→ export app customizations -
permission_query_conditions→ custom list permissions -
on_login/on_logout→ authentication hooks
4. Database, ORM & Query
Frappe gives you multiple layers to work with data:
- Frappe ORM (
frappe.db,get_doc) - Query Builder (
frappe.qb) - Raw SQL when you need it
- Database transactions (commit/rollback)
- Indexes & performance tuning
- Full-text search
- Support for both MariaDB and PostgreSQL
5. Scripts
- Client Script (JS) — form events, field validation, UI customization, API calls
- Server Script (Python) — business logic, scheduled tasks, event-based logic
- System Console — execute Python/JS, debug & test, developer tools
6. Reports
- Query Report — drag-and-drop fields, filters, groups, aggregations, charts & pivot
- Script Report — custom Python logic with dynamic data
- Reports support charts, pivots, indicators, export (Excel/CSV/PDF), scheduling & sharing
7. Print Formats
Build documents using Jinja templates — supports HTML/PDF output, dynamic data, company letterheads, custom styles, and watermarks/signatures.
8. Jinja Templates
Jinja is the templating engine at the heart of Frappe's UI — used in Print Formats, Email Templates, Web Pages, and List/Form Views. Supports variables, filters, control statements, inheritance, and macros/includes.
9. REST APIs & Webhooks
Every DocType gets REST endpoints automatically — list, get, create, update, delete, with filtering, pagination, and field selection:
GET /api/resource/Customer?fields=["name","email"]&filters=["disabled",0]
Webhooks let you fire real-time notifications on document events, triggered securely and reliably.
10. Document APIs & Methods
Common patterns you'll use constantly in server scripts:
frappe.new_doc(...)
frappe.get_doc(...)
frappe.get_list(...)
frappe.get_all(...)
frappe.get_cached_doc(...)
frappe.db.exists(...)
frappe.delete_doc(...)
doc.insert()
doc.save()
doc.submit()
doc.cancel()
doc.append(...)
doc.reload()
doc.db_set(...)
11. Desk
The modern workspace for your team: role-based desk, custom workspaces, modules, shortcuts, notifications, to-do/kanban/calendar views, real-time updates, search & filters, and custom fields/views.
12. Workflows
Automate business processes with workflow states, transitions, state-based permissions, transition conditions, approve/reject actions, email/notification triggers, multi-level approvals, and role-based access.
13. Profiling, Monitoring & Logging
Built-in profiler,
request profiling,
error logs,
background job logs,
scheduler logs,
slow query analysis,
performance monitoring,
site health monitoring — essential for debugging in production.
14. Social Logins & OAuth
Out of the box: Google Login, GitHub Login, LDAP Authentication, OAuth 2.0 Providers, SAML Authentication, OpenID Connect (OIDC), and custom OAuth providers.
15. Fixtures
Fixtures allow you to export customizations from one site and ship them with your application.
Custom Fields
Property Setters
Client Scripts
Server Scripts
Print Formats
Workspaces
Reports
Roles
Notification Templates
fixtures = [
"Custom Field",
"Property Setter",
"Client Script"
]
Export fixtures:
bench export-fixtures
Why this matters:
these fourteen building blocks are what let teams ship scalable, secure business applications faster — with real-time integrations, automation, and enterprise-grade security baked in.
Part 3: Framework Internals — What Happens Under the Hood
This is the part that made Frappe click for me — understanding the actual architecture behind the abstractions.
Request Lifecycle
Every request flows through a clear, layered pipeline:
Browser
↓
Nginx
↓
Gunicorn
↓
Frappe Framework
↓
Authentication
↓
Permission Check
↓
Controller (Python)
↓
Business Logic
↓
ORM / Query Builder
↓
PostgreSQL / MariaDB
↓
Response (JSON / HTML)
- Browser — user makes a request
- Nginx — handles static files, SSL, and reverse proxying
- Gunicorn — WSGI server that receives the request
- Frappe Framework — routes the request to the application
- Authentication — user is authenticated (session/token/OAuth)
- Permission Check — permissions are validated
- Controller (Python) — request is handled by the appropriate controller
- Business Logic — application logic is executed
- ORM / Query Builder — database operations (ORM or Query Builder)
- PostgreSQL / MariaDB — data is read from or written to the database
- Response (JSON / HTML) — response is sent back to the browser
Redis (In-Memory Store)
Redis is the central storage for queues, cache, sessions, locks, and realtime events. It handles: cache, session storage, the Redis Queue (RQ), pub/sub for realtime events, job metadata, distributed locks, rate limiting, and background job status.
Background Jobs (RQ)
RQ (Redis Queue) manages asynchronous tasks so users don't wait around for long-running operations:
frappe.enqueue(...)
↓
RQ Creates Job
↓
Redis Queue
Typical background jobs: email, SMS, WhatsApp, notifications, report generation, PDF generation, import/export, webhook calls, API sync, and scheduler jobs. Jobs are stored in Redis, and workers fetch and execute them.
Workers
Workers are long-running Python processes that monitor Redis queues, fetch jobs, and execute them asynchronously. There are three tiers:
| Queue | Worker | Examples |
|---|---|---|
| Short Queue | Worker (Short) | Notifications, email, SMS, small updates |
| Default Queue | Worker (Default) | Business logic, API calls, data processing |
| Long Queue | Worker (Long) | Reports, imports, exports, PDF, AI jobs |
Important: without running workers, queued jobs remain in Redis and are never executed.
Scheduler Events
The scheduler checks scheduled events defined in hooks.py, creates background jobs, and places them into Redis queues:
hooks.py (scheduler_events)
↓
Scheduler Process (bench schedule)
↓
Checks Schedule
↓
Calls frappe.enqueue()
↓
Redis Queue (RQ)
↓
Worker
↓
Execute Task
Schedule types supported: Hourly, Daily, Weekly, Monthly, and Cron (custom).
Crucially: the scheduler never executes jobs itself — it only enqueues them. Actual execution is always the worker's job.
Realtime (Socket.IO)
Realtime communication between server and clients — live notifications, progress bars, desk updates, and chat/collaboration — all powered by Socket.IO and Redis Pub/Sub.
Authentication & Session
Handles user authentication and session management: session management, API keys, token authentication (REST API), OAuth/social login, and role-based access — supporting multiple authentication methods.
Cache
Caching improves performance and reduces database load, via Redis cache, metadata cache, session cache, and document cache — for frequently accessed data.
Monitoring & Logging
Tools to monitor performance, errors, and system health: Error Log DocType, system logs (bench logs), slow query logs, background job logs, scheduler logs, bench doctor (health check), and the built-in profiler.
Database Layer
Handles all database operations and provides an abstraction over SQL: PostgreSQL/MariaDB support, Frappe ORM, Query Builder, transactions (commit/rollback), row-level locking, indexes & performance, and migrations (bench migrate).
Frappe Queue Management Architecture — Two Flows
A. User-Triggered Flow
User Click / Action (Generate Report, Send Email, etc.)
↓
frappe.enqueue() — Add job to queue
↓
Redis Queue (RQ) — Job stored in Redis
↓
Routed to Short / Default / Long Queue
↓
Worker executes the background job
↓
Database updated / Notification sent
↓
Job Completed (result stored or sent back)
B. Scheduled Layer
Time Reached (as per schedule)
↓
Scheduler Process (bench schedule) checks hooks.py Schedule Events
↓
Calls frappe.enqueue() — creates background job
↓
Redis Queue (RQ)
↓
Worker
↓
Execute Scheduled Task
↓
Job Completed
Example scheduled events: hourly tasks, daily reports, weekly cleanups, monthly jobs, custom cron jobs.
Common Bench Commands (Internals-Focused)
| Command | Description |
|---|---|
bench start |
Start all services (web, worker, scheduler, etc.) |
bench stop |
Stop all services |
bench restart |
Restart all services |
bench status |
Check status of all services |
bench worker |
Start worker for default queue |
bench worker --queue short |
Start worker for short queue (fast tasks) |
bench worker --queue long |
Start worker for long queue (heavy tasks) |
bench schedule |
Start scheduler process |
bench migrate |
Run database migrations |
bench doctor |
Check bench health and system status |
bench show-pending-jobs |
Show pending jobs in all queues |
bench purge-jobs |
Purge completed/failed jobs from queues |
bench backup |
Create a backup of the site |
bench console |
Open Frappe console |
Official Resources
Frappe Official Website: https://frappe.io/
Frappe Framework Documentation: https://docs.frappe.io/framework
ERPNext Documentation: https://docs.erpnext.com
Frappe GitHub Repository: https://github.com/frappe/frappe
ERPNext GitHub Repository: https://github.com/frappe/erpnext
Wrapping Up
Frappe's real power isn't any single feature — it's how metadata (DocTypes), events (hooks), and infrastructure (Redis, RQ, workers, scheduler) compose together into a cohesive system. Once you understand the request lifecycle and the queue architecture, the rest of the framework — controllers, permissions, workflows, reports — starts to feel like straightforward configuration on top of a solid foundation.
If you're building your first Frappe app, my suggestion:
start with bench new-app,
get a simple DocType working end-to-end (form → hook → API),
go read about workers and the scheduler.
The internals will make a lot more sense once you've felt the "front door" of the framework first.
Happy building! 🚀
If this was useful, drop a comment with what you're building on Frappe — always curious to see what people ship with it.
Top comments (0)