ERPNext is built on the Frappe Framework -- a full-stack, open-source web application framework written in Python and JavaScript. But Frappe is not just the engine under ERPNext's hood. It is a complete low-code platform that lets you build business applications from DocType definitions, without writing boilerplate CRUD code, authentication logic, or API endpoints.
Understanding Frappe is the key to understanding why ERPNext is so extensible. Every custom app CoaleTech builds -- Payroll Africa, Coale-Payments, Coale-Tax -- is a Frappe app. They inherit the same permission system, the same REST API, the same print engine, and the same workflow engine as ERPNext itself.
This article breaks down the core capabilities of Frappe Framework that make it possible to build production-grade business software in weeks instead of months.
DocTypes: Define Your Data, Get an App
The fundamental building block of Frappe is the DocType. A DocType is a data model definition -- fields, field types, validations, and layout -- stored as JSON. When you create a DocType, Frappe automatically generates the database table, the form UI, the list view, the REST API endpoints, the permission checks, and the search index. No code required for basic CRUD operations.
Field types include Link (foreign key to another DocType), Table (child table for line items), Select, Currency, Date, Attach (file uploads), Geolocation, and 30+ more. Link fields automatically provide search-ahead dropdowns. Table fields give you editable grids. Currency fields respect your company's currency settings and decimal precision.
Payroll Africa, for example, defines 26 DocTypes. Each country settings DocType (like "Kenya Payroll Settings") is a Single DocType -- meaning only one record exists, functioning as a configuration page. The PAYE Band child tables store progressive tax brackets. All of this is defined in JSON files; the framework handles the rest.
Auto-Generated REST API for Every DocType
Every DocType in Frappe is immediately accessible via REST API. GET /api/resource/Employee returns a list of employees. GET /api/resource/Employee/HR-EMP-0001 returns a single record. POST /api/resource/Employee creates a new record. All with authentication, permission checks, and validation applied automatically.
For custom logic, you use @frappe.whitelist() to expose Python functions as API endpoints. Payroll Africa uses this for its calculate_deductions endpoint -- a standalone API that returns statutory deductions for any country and gross pay amount, without creating a Salary Slip.
The API supports filters, pagination, field selection, sorting, and OR conditions out of the box. Mobile apps, dashboards, and third-party integrations can consume it immediately with no backend work.
Role-Based Permissions at Every Layer
Frappe's permission system operates at the DocType, document, and field level. You define which Roles can read, write, create, delete, submit, cancel, amend, and export records for each DocType. A "Payroll Manager" role might have full access to Salary Slips while a "Payroll User" can only read their own.
User Match permissions restrict access to records the user owns or is linked to -- an employee sees only their own leave applications, a sales person sees only their own quotations. Document-level permissions can be extended with custom Python logic via the has_permission hook for complex business rules.
Field-level Read and Write permissions hide sensitive data from unauthorised roles. Salary amounts can be visible to HR Managers but hidden from general employees, all configured through the DocType definition -- no custom code needed.
Workflows: Approval Chains Without Code
Frappe's Workflow engine lets you define multi-step approval processes on any DocType. A Leave Application might flow through: Draft → Pending Approval → Approved (by Department Head) → Confirmed (by HR). Each state transition can be restricted to specific roles, trigger email notifications, and update fields automatically.
Workflows are configured entirely through the UI -- no Python or JavaScript required. You define states, transitions, allowed roles for each transition, and optional conditions. The framework handles the state machine, the approval buttons, the audit trail, and the email alerts.
Hooks and Doc Events: Extend Anything
Frappe's hook system is how apps extend each other without modifying source code. In hooks.py, an app declares which functions should run on specific events. Payroll Africa hooks into Salary Slip.validate -- every time a salary slip is validated, Payroll Africa's calculator runs and injects statutory deductions. ERPNext never knows it happened.
Doc Events include before_insert, validate, on_submit, on_cancel, on_trash, and more. Beyond doc events, hooks cover scheduled tasks (cron jobs), boot session data, website routes, override whitelisted functions, fixtures, and app installation/uninstallation lifecycle.
This architecture means you can build apps that modify ERPNext's behaviour without forking it. When ERPNext upgrades, your hooks survive because they never touched ERPNext's code.
Print Formats, Reports, and Dashboards
Print Formats use Jinja2 templates to generate PDF documents from any DocType. Invoices, payslips, delivery notes, contracts -- all customisable with your branding, layout, and data fields. Frappe includes a drag-and-drop Print Format Builder for non-developers and raw HTML/Jinja for full control.
Script Reports let you write Python functions that return tabular data with filters. Payroll Africa's 33 reports are all Script Reports -- they query salary slips, compute totals by component and employee, and format the output for tax authority filing. The framework handles the filter UI, export to Excel/CSV, and print layout.
Workspaces are configurable landing pages with shortcuts, charts, reports, and links organised into sections. Each app can define its own workspace. Payroll Africa's workspace groups reports by region (East Africa, Southern Africa, West & Central Africa) with dynamic visibility based on which countries are enabled.
The App Architecture: Install, Uninstall, Coexist
Frappe apps are self-contained Python packages installed via bench get-app and bench install-app. Each app can declare dependencies on other apps (Payroll Africa requires ERPNext and HRMS), run setup code on install, create custom fields on existing DocTypes, and clean up on uninstall.
Multiple apps coexist on the same Frappe site. CoaleTech clients typically run ERPNext + HRMS + Payroll Africa + Coale-Payments + Coale-Tax -- five apps sharing one database, one permission system, and one user interface. No integration middleware required.
This is the power of building on a framework rather than bolting services together: every app speaks the same language, uses the same ORM, respects the same permissions, and shows up in the same Desk interface. The result is a unified business system that feels like one product, even though it is composed of independent, upgradeable apps.
Build on Frappe with CoaleTech
Frappe Framework is the reason we can build Africa-specific business apps rapidly. Instead of writing authentication, APIs, permissions, and UI from scratch, we focus on the business logic -- statutory deductions, tax compliance, payment processing -- and let Frappe handle the infrastructure.
If you are evaluating platforms for your next business application, or want to extend ERPNext with custom functionality, talk to us. We have shipped 11 Frappe apps and counting.