About Experience Engineering Projects Infrastructure Blog Contact
ERPNext & Frappe 🧩 9-Part Course · Part 1: Foundations

ERPNext Beginner to Intermediate: The Architecture Guide

Published: Aug 30, 2026

🎯 Who This Is For & the Learning Journey

This guide is written for anyone who needs to go from zero knowledge of ERPNext to being productive with it professionally: ERPNext beginners, Python/Django and full-stack developers, system administrators, database developers, ERP implementers, business analysts, technical support engineers, and anyone building a custom ERPNext module.

ERPNext Beginner
↓
Installation
↓
System Setup
↓
Users & Roles
↓
Modules
↓
DocTypes
↓
Database
↓
Business Workflows
↓
Customization
↓
Custom Apps
↓
API Integration
↓
Intermediate ERPNext Developer

Every diagram in this guide labels the same underlying chain: User β†’ Frontend β†’ Backend β†’ DocType β†’ Database β†’ Integration. That chain is the single idea the whole course is built around β€” once it clicks, every ERPNext screen, table, and API call reads as an instance of it.

⚠️ Version & Accuracy Note

Read this before the rest of the guide. This content is written conceptually against the ERPNext v14/v15 and Frappe Framework v14/v15 generation β€” the architecture, DocType/table conventions, bench commands, and hook mechanisms described have been stable across recent versions, but exact field names, UI labels, menu paths, and command flags do change between versions. Two categories of claim appear throughout this guide:
  • Verified Frappe/ERPNext convention β€” core, long-stable behavior (e.g. the tab<DocType> table naming rule, the bench command family, doc-event hooks).
  • Conceptual / illustrative architecture β€” diagrams and table lists built to teach the relationships, not copied from a specific installation's schema browser.
Always verify exact table names, fields, and API behavior against the official documentation for your installed version before relying on them operationally, and never modify ERPNext core source to work around a version difference.

🧭 What Is ERPNext? Frappe vs ERPNext

ERP (Enterprise Resource Planning) software exists to solve one recurring business problem: sales, purchasing, inventory, accounting, and HR are all run as separate spreadsheets or disconnected tools, so nobody can see one true, current picture of the business. An ERP unifies them into one database with one shared set of master data (customers, items, accounts) so every transaction updates the same source of truth.

ERPNext is an open-source ERP application. It is not a standalone codebase β€” it is built on top of Frappe Framework, a full-stack, metadata-driven web application framework (Python backend, JavaScript frontend, MariaDB database) that ERPNext uses for everything: authentication, permissions, forms, reports, workflows, and the REST API. This is the single most important distinction for a developer to internalize before touching ERPNext code.

Frappe Frameworkgeneric metadata-driven app platform
↓
ERPNexta Frappe app: the ERP business logic
↓
Business ModulesAccounting, Selling, Stock, HR…
↓
DocTypesthe unit of data + logic + UI
↓
DatabaseMariaDB tables generated per DocType
TermWhat it means
FrappeThe underlying framework: auth, permissions, ORM, UI rendering, REST API, background jobs
ERPNextA Frappe app that implements ERP business logic on top of the framework
AppAn installable Python package (Frappe or ERPNext itself, or a custom app) that bundles DocTypes, code, and assets
ModuleA logical grouping of DocTypes within an app (e.g. "Selling", "Stock")
DocTypeA metadata definition of a business object β€” its fields, permissions, and behavior; the framework generates a database table and a UI form from it
DocumentOne saved record/row of a DocType (e.g. one specific Sales Invoice)
FieldOne attribute on a DocType (Data, Link, Currency, Table, Select, …)
Child TableA DocType marked "Is Child Table", used to store repeatable rows inside a parent document (e.g. invoice line items)
WorkspaceA configurable landing page/dashboard grouping shortcuts, charts, and links for a module or role
ReportA saved query/script that reads DocType data and renders it as a table, chart, or dashboard
PageA custom, code-driven screen that isn't a standard DocType list/form (used for dashboards, POS, etc.)
Web FormA public-facing, simplified form that lets portal/external users create or edit specific DocTypes

πŸ—οΈ Frappe/ERPNext Architecture

Browser

↓HTTP / WebSocket

ERPNext UI

  • Desk, forms, list views
↓renders via

Frappe Framework

  • auth, ORM, permissions
↓ ↓ ↓dispatches to

DocTypes

  • metadata + schema

APIs

  • REST / RPC endpoints

Controllers

  • Python classes, hooks
↓persist to

MariaDB

  • ERPNext Database

Behind that request/response path, a full ERPNext deployment runs several supporting processes together:

ComponentRole
PythonBackend language β€” DocType controllers, server scripts, API logic
JavaScriptFrontend logic β€” client scripts, form behavior, the Desk UI itself
MariaDBPrimary relational database; one table per DocType
RedisCaching, background job queue, and real-time pub/sub backing
Background workersProcess queued/long-running jobs (emails, reports, bulk updates) outside the request cycle
SchedulerTriggers scheduler_events hooks on a cron-like cadence (daily, hourly, etc.)
Web serverGunicorn (app server) typically fronted by Nginx in production
Socket.IOReal-time updates β€” live notifications, document locks, desk refresh
REST API/api/resource/<DocType> β€” the framework-generated API surface for every DocType
File storageAttachments and generated files, stored under the site's public/files / private/files directories

βš™οΈ Installation & Dependencies

ERPNext can be installed for local development (bench on bare metal or WSL), via Docker, or directly on a production VPS. All paths converge on the same tool: Frappe Bench, the CLI that manages sites, apps, and processes.

Linux

Python

Node.js

MariaDB

Redis

Git

Yarn

wkhtmltopdf

↓required by

Frappe Bench

↓installs

Frappe

↓installs as an app

ERPNext

The core bench workflow, in order:

# install bench itself (via pip/pipx), then:
bench init frappe-bench                 # scaffold a new bench (downloads the Frappe app)
cd frappe-bench
bench new-site site1.local              # create a site: its own database + config
bench get-app erpnext                   # fetch the ERPNext app source
bench --site site1.local install-app erpnext   # install ERPNext into that site
bench start                             # run web server + workers + scheduler for development

Each command has a distinct job: bench init sets up the shared bench (apps + environment); bench new-site creates one isolated tenant (its own MariaDB database, own config, own set of installed apps); get-app vs install-app is the same split as downloading a package vs actually enabling it for a specific site β€” one bench can host many sites, each with a different combination of installed apps.

PathContents
apps/Source of every installed app β€” apps/frappe/, apps/erpnext/, and any custom apps
sites/One folder per site; sites/common_site_config.json holds bench-wide settings, sites/site1.local/ holds that site's config and files
config/Generated Nginx/Supervisor config for production setups
logs/Web, worker, and scheduler logs
env/The Python virtual environment bench runs inside
ProcfileProcess list bench start uses in development (web, workers, scheduler, socketio)

πŸš€ Production Deployment Architecture

Internet
↓
Domain (DNS)
↓
NginxTLS termination, static files, reverse proxy
↓
GunicornFrappe/ERPNext app server
↓
Background Workersqueued jobs, supervised
↓
Rediscache, queue, pub/sub
↓
MariaDBthe ERPNext database

In production, Supervisor keeps Gunicorn, the background workers, and the scheduler running (and restarts them on crash or reboot), while Nginx handles TLS (typically via Let's Encrypt / Certbot for free auto-renewing HTTPS certificates), serves static assets directly, and proxies dynamic requests to Gunicorn. A production checklist also includes a configured firewall (only 80/443/22 open), a scheduled backup job, and a DNS record pointed at the server before requesting a certificate.

πŸ”‘ First Login & Initial Setup

The first login into a freshly installed site runs the Setup Wizard, which walks through the minimum master data ERPNext needs before any transaction can be created:

StepWhy it's required first
CompanyAlmost every transaction and account belongs to a Company β€” it's the top of the accounting hierarchy
Currency & Fiscal YearEvery ledger entry needs a currency and an open accounting period to post into
Chart of AccountsGenerated automatically from a country/industry template, then customized β€” every GL Entry needs an Account
WarehouseStock can't move without at least one warehouse to move into/out of
Customers / SuppliersOptional at setup, but needed before the first Sales/Purchase document
Users & RolesDetermines who can do what from day one β€” see Users, Roles & Permissions

πŸ–₯️ ERPNext UI Concepts

Almost the entire application is one interface, called the Desk, built from a small set of reusable view types:

#UI elementWhat it's for
1SidebarModule and workspace navigation
2WorkspaceA configurable dashboard/landing page per module or role
3Awesome Bar (search)Global fuzzy search across DocTypes, documents, and even commands ("new sales invoice")
4User menuProfile, settings, "switch to desk/portal", logout
5NotificationsAssignments, mentions, and document events targeted at the current user
6List ViewFilterable, sortable table of documents for one DocType
7FiltersStandard and saved filters on any list or report
8Form ViewThe single-document editing screen β€” fields, child tables, and actions
9Actions (Submit/Cancel/Print/Duplicate…)Document-level operations available from the form
β€”Report ViewSpreadsheet-like grouped/aggregated view over a DocType's data
β€”Dashboard / Kanban / Calendar / Tree viewAlternate visualizations of the same underlying documents, chosen per DocType

πŸ‘₯ Users, Roles & Permission Flow

ERPNext ships a set of standard roles that map to job functions, all ultimately subordinate to the System Manager role, which has unrestricted administrative access:

System Manager
β†’
Accounts Manager
β†’
Sales Manager
β†’
Purchase Manager
β†’
Stock Manager
β†’
HR Manager
β†’
Manufacturing Manager
β†’
Projects Manager
β†’
Employee

Access is resolved through a layered chain, not a single flag β€” this is the same "many small typed layers, not one field" principle that shows up in ERP accounting status modeling:

User
↓
Role
↓
Role PermissionDocType-level: read/write/create/submit/cancel/delete
↓
User Permissionrestricts to specific records, e.g. one Company
↓
Document Permissionshare, workflow state, owner rules
↓
Allowed Action

A Role Profile bundles a set of roles for fast assignment to new users; Document Share grants ad-hoc access to one specific document outside the normal role rules; and workflow-state permissions (see Workflow Engine) can further restrict who may transition a document, independent of its base role permissions.

🧩 Core Modules at a Glance

ERPNext ships as a set of business modules sharing one database and one permission system. Every module follows the same internal shape β€” Purpose β†’ Master Data β†’ Transactions β†’ Child Tables β†’ Workflow β†’ Database β†’ Accounting/Stock Impact β†’ Other-Module Connections β†’ Reports β†’ API β€” so once you understand one module deeply, reading a new one is mostly about learning its specific documents, not a new mental model.

ModulePurposeKey master dataCore transaction flowConnects to
AccountingBooks of record β€” every module ultimately posts hereCompany, Account, Cost Center, Fiscal Year, Tax TemplateSales/Purchase Invoice β†’ Payment Entry / Journal Entry β†’ GL EntryAll modules
SellingQuote-to-cash for customersCustomer, Territory, Sales Person, Price ListLead β†’ Opportunity β†’ Quotation β†’ Sales Order β†’ Delivery Note β†’ Sales InvoiceStock, Accounting, CRM
BuyingProcure-to-pay from suppliersSupplier, Item, Buying Price ListMaterial Request β†’ RFQ β†’ Supplier Quotation β†’ Purchase Order β†’ Purchase Receipt β†’ Purchase InvoiceStock, Accounting
StockInventory quantity & valuationItem, Item Group, Warehouse, Batch, Serial NoStock Entry / Delivery Note / Purchase Receipt β†’ Stock Ledger Entry β†’ BinSelling, Buying, Manufacturing, Accounting
ManufacturingConvert raw materials into finished goodsBOM, Workstation, Operation, RoutingBOM β†’ Work Order β†’ Material Transfer β†’ Job Card β†’ Finished Goods β†’ Stock EntryStock, Accounting
CRMPre-sales relationship managementLead, Opportunity, Contact, AddressLead β†’ Opportunity β†’ Quotation β†’ CustomerSelling
ProjectsTrack billable and internal workProject, Task, Activity TypeProject β†’ Task β†’ Timesheet β†’ Sales InvoiceHR, Accounting
AssetsFixed asset lifecycle & depreciationAsset Category, AssetPurchase Invoice β†’ Asset β†’ Depreciation β†’ Asset Movement/DisposalAccounting
HREmployee lifecycle & attendanceEmployee, Department, DesignationEmployee β†’ Attendance β†’ Leave ApplicationPayroll
PayrollSalary calculation & disbursementSalary Structure, Employee GradeSalary Structure β†’ Salary Slip β†’ Payroll EntryHR, Accounting
QualityInspection & conformanceQuality Procedure, Quality GoalQuality Inspection ↔ Purchase Receipt / Delivery Note / ManufacturingStock, Manufacturing, Buying
SupportPost-sales customer serviceIssue, Service Level AgreementCustomer β†’ Issue β†’ Assignment β†’ Communication β†’ ResolutionCRM, Selling
πŸ’‘ Additional apps: ERPNext's ecosystem also includes separate Frappe apps for POS, Website/Portal, Healthcare, Agriculture, and others depending on what's installed on a given site β€” treat any module not listed above as installation-specific and verify it against the actual site before documenting it as standard.

πŸ”— Flagship Document Flows

Selling: Lead to Cash

Lead
β†’
Opportunity
β†’
Quotation
β†’
Sales Order
β†’
Delivery Note
β†’
Sales Invoice
β†’
Payment Entry

Child tables carry the line items at every stage: Quotation Item, Sales Order Item, Delivery Note Item, Sales Invoice Item β€” each new document typically pulls its rows from the previous one so item, price, and quantity stay traceable end to end.

Buying: Procure to Pay

Material Request
β†’
Request for Quotation
β†’
Supplier Quotation
β†’
Purchase Order
β†’
Purchase Receipt
β†’
Purchase Invoice
β†’
Payment Entry

Manufacturing: BOM to Finished Goods

Item
β†’
BOM
β†’
Work Order
β†’
Material Transfer
β†’
Job Card
β†’
Finished Goods
β†’
Stock Entry

A BOM (Bill of Materials) can itself reference another Item that has its own BOM β€” a multi-level BOM β€” so manufacturing a finished item can cascade into manufacturing its sub-assemblies first.

HR & Payroll: Employee to Accounting

Employee
β†’
Attendance
β†’
Leave
β†’
Salary Structure
β†’
Salary Slip
β†’
Payroll Entry
β†’
Accounting

Assets: Purchase to Disposal

Asset Category
β†’
Asset
β†’
Purchase Invoice
β†’
Depreciation
β†’
Asset Movement
β†’
Asset Disposal
πŸ’‘ The GL Entry table is the convergence point. Sales Invoices, Purchase Invoices, Payment Entries, Journal Entries, Payroll Entries, and Asset Depreciation all eventually write to the same GL Entry table β€” it's arguably the single most important transaction table in ERPNext, because it's where every module's financial impact becomes comparable in one ledger. For the full accounting theory behind why invoices, payments, and GL postings are deliberately kept as separate records, see Invoice vs Payment: The Complete ERP Accounting Architecture.

πŸ•ΈοΈ Module Interconnection Master Diagram

CRM

↓

Selling

↓

Sales Order

↓ ↓splits into

Stock

  • Delivery Note

Accounting

  • GL Entry
↓ ↓both feed

Payment

Source moduleFeeds into
BuyingStock, Accounting
ManufacturingStock, Accounting
HRPayroll
PayrollAccounting
ProjectsTimesheet β†’ Billing β†’ Accounting

Notice the pattern: almost every module is a source that eventually drains into Stock and/or Accounting. Those two are the shared "settlement layer" every other module's transactions converge on β€” the ERPNext equivalent of the subledger/GL relationship described in the accounting curriculum's ERP Architecture level.

πŸ—„οΈ Database Architecture: DocType β†’ Table

DocTypemetadata: fields, permissions, options
↓
Database Tableauto-generated by the framework
↓
Document Recordone row = one saved document

This is a verified, stable Frappe convention: every non-child, non-virtual DocType gets a MariaDB table named tab + the DocType name, spaces included β€” e.g. tabCustomer, tabSupplier, tabItem, tabSales Order, tabSales Invoice. Changing a DocType's fields in the UI ("Customize Form" or the DocType editor) triggers a schema migration that adds/alters the corresponding column β€” this is why DocTypes are described as "metadata-driven": the schema is a byproduct of the metadata, not hand-written SQL.

ConceptDefinition
DocTypeThe schema + behavior definition (fields, permissions, controller class)
DocumentOne instance/row of a DocType
Child tableA DocType flagged "Is Child Table"; its rows always belong to exactly one parent document
Link fieldA foreign-key-style reference to another DocType's name (primary key)
Dynamic LinkA Link field whose target DocType is itself chosen by another field (used for polymorphic references, e.g. an Address linking to a Customer or a Supplier)
Table fieldThe field type on a parent DocType that embeds a child table

πŸ“š Master Tables vs Transaction Tables

TypeExamplesBehavior
MasterCustomer, Supplier, Item, Warehouse, Account, EmployeeCreated once, referenced repeatedly by many transactions; rarely deleted, usually just deactivated
TransactionSales Order, Purchase Order, Sales Invoice, Delivery Note, Payment Entry, Stock EntryCreated per business event, generally follow a submit/cancel lifecycle, and are the rows that actually move quantities and money

Master data exists so transactions don't repeat information β€” a Sales Order doesn't store a customer's address text, it stores a Link to the Customer, and the Customer record is the single place that address gets corrected. This is the same "don't duplicate the fact, reference it" principle behind normalized database design generally.

Child Tables Explained

A child table row always carries four framework-managed fields that tie it back to its parent, in addition to whatever fields the child DocType itself defines:

FieldPurpose
parentThe name of the owning parent document
parenttypeThe parent's DocType (needed because a child table can, in principle, be reused by more than one parent DocType)
parentfieldWhich Table field on the parent this row belongs to
idxThe row's display order within the table
tabSales Order
       β”‚
       β”‚ parent (1) ──── (N) rows
       β–Ό
tabSales Order Item
  parent      = "SAL-ORD-2026-00042"
  parenttype  = "Sales Order"
  parentfield = "items"
  idx         = 1
  item_code   = "WIDGET-001"
  qty         = 10

Illustrative Table Map (Verify Per Version)

The following are well-known, long-stable core DocTypes grouped by module, to illustrate the naming convention β€” not an exhaustive schema dump. Confirm exact names and any version differences against your installed site (e.g. via bench --site SITE console or the DocType list in Developer Mode) before relying on them in code.

AreaRepresentative tables
CoretabUser, tabRole, tabDocType, tabDocField, tabWorkflow, tabFile, tabCommunication, tabToDo
CRM / SellingtabLead, tabOpportunity, tabCustomer, tabQuotation, tabSales Order, tabSales Order Item, tabDelivery Note, tabSales Invoice, tabSales Invoice Item
BuyingtabSupplier, tabMaterial Request, tabPurchase Order, tabPurchase Order Item, tabPurchase Receipt, tabPurchase Invoice, tabPurchase Invoice Item
StocktabItem, tabItem Group, tabWarehouse, tabBin, tabStock Entry, tabStock Ledger Entry, tabSerial No, tabBatch
AccountingtabAccount, tabGL Entry, tabPayment Entry, tabJournal Entry, tabCost Center, tabCompany
ManufacturingtabBOM, tabBOM Item, tabWork Order, tabJob Card
HRtabEmployee, tabDepartment, tabAttendance, tabLeave Application, tabSalary Slip, tabPayroll Entry

πŸ”€ ER Diagrams: Customer, Item, Accounting

Customer Relationships

Customer
↓ referenced by
Contact Β· Address Β· Opportunity Β· Quotation Β· Sales Order Β· Delivery Note Β· Sales Invoice

Item Relationships

Item
↓ referenced by
Item Group Β· Price List Β· Warehouse (via Bin) Β· BOM Β· Sales/Purchase Order Item Β· Delivery/Receipt Item Β· Stock Ledger Entry

Accounting Relationships

Company
↓ owns
Account Β· Cost Center Β· Journal Entry Β· Payment Entry Β· GL Entry

πŸ”Œ Internal vs External Connections

Internal (module ↔ module, inside one database)

Selling ↔ StockSelling ↔ Accounting
Buying ↔ StockBuying ↔ Accounting
Manufacturing ↔ StockManufacturing ↔ Accounting
HR ↔ PayrollProjects ↔ Accounting

External (ERPNext ↔ the outside world)

ERPNext
↓
REST API Β· Webhooks Β· OAuth Β· API Keys
↓
E-commerce Β· Payment Gateways Β· SMS/Email Β· Other ERPs/Systems

🌐 REST API Basics

Frappe auto-generates a REST-style endpoint for every DocType at /api/resource/<DocType>, supporting the standard HTTP verbs, authenticated with an API key/secret pair generated per user:

# list/read
GET /api/resource/Customer
GET /api/resource/Customer/CUST-00001

# create
POST /api/resource/Customer

# update
PUT /api/resource/Customer/CUST-00001

# delete
DELETE /api/resource/Customer/CUST-00001
# Python (requests)
import requests
headers = {"Authorization": "token API_KEY:API_SECRET"}
r = requests.get("https://site.example.com/api/resource/Customer", headers=headers)

// JavaScript (fetch)
fetch("https://site.example.com/api/resource/Customer", {
  headers: { "Authorization": "token API_KEY:API_SECRET" }
}).then(r => r.json());
⚠️ Secure API usage: always call over HTTPS, scope API keys to the minimum role needed rather than a System Manager account, and rotate/revoke keys through the User record if a key is ever exposed β€” the same audit-trail discipline covered for financial records in the accounting curriculum's System Architecture level applies to API credentials too.

πŸ› οΈ Customization Decision Tree

ERPNext offers a deliberate ladder of customization tools, from no-code to full custom application β€” always reach for the lowest rung that solves the problem:

Need a simple new field?
Yes β†’ Custom Field (+ Property Setter to tweak an existing field)
↓ No
Need UI behavior only?
Yes β†’ Client Script (JavaScript, runs in the browser)
↓ No
Need server-side validation/logic?
Yes β†’ Server Script (Python, runs on save/submit)
↓ No
Need a multi-step approval process?
Yes β†’ Workflow (states, transitions, role-gated actions)
↓ No
Need new DocTypes, deep integration, or reusable logic β†’ build a Custom App

πŸ“¦ Building a Custom Frappe App

bench new-app my_custom_app
bench --site site1.local install-app my_custom_app
my_custom_app/
β”œβ”€β”€ hooks.py            # app config: doc_events, scheduler_events, includes, fixtures
β”œβ”€β”€ modules.txt         # modules this app defines
β”œβ”€β”€ patches.txt         # data migration scripts run on update
β”œβ”€β”€ public/             # JS/CSS/images served to the browser
β”œβ”€β”€ templates/           # Jinja templates for web pages/emails
└── my_custom_app/
    └── my_module/
        β”œβ”€β”€ doctype/     # one folder per custom DocType (json + python + js)
        β”œβ”€β”€ report/      # custom reports
        └── page/        # custom Desk pages

Doc-event hooks are how a custom app plugs into the lifecycle of any document β€” including ERPNext's own β€” without editing ERPNext source:

User saves a document
↓
validate()
↓
before_save
↓
Database Save
↓
on_update
↓
after_save

Other key hook categories: scheduler_events (cron-style background jobs), app_include_js/app_include_css (global frontend assets), fixtures (data β€” like custom fields or roles β€” exported and reapplied on install), permission_query_conditions and has_permission (row-level access rules beyond the standard permission engine), and override_doctype_class (replacing a standard DocType's controller logic without editing its file).

πŸ§ͺ Custom Module Case Study: Laboratory Management

A realistic custom app example β€” extending ERPNext for a diagnostic lab β€” illustrates how a domain-specific module plugs into ERPNext's core rather than replacing it:

Patient
↓
Sample
↓
Test Request
↓
Test
↓
Lab Result
↓
Lab Report

New DocTypes (Patient, Sample, Test Request, etc.) link back into ERPNext core via ordinary Link fields β€” Sample.customer β†’ Customer, Sample.company β†’ Company, Test.item β†’ Item, Test Request.employee β†’ Employee β€” so a lab report can bill through the standard Sales Invoice flow instead of the custom app reinventing billing.

Integration happens on three levels, and a well-designed custom app touches all three deliberately rather than only the first:

LevelMechanism
1. UI IntegrationWorkspace, module icon, dashboard cards for the new DocTypes
2. Data IntegrationLink/Dynamic Link fields and child tables tying custom records to Customer, Item, Company, Employee
3. Business Logic IntegrationHooks, doc events, controller methods, and APIs that trigger ERPNext-side actions (e.g. auto-creating a Sales Invoice when a Lab Report is approved)

πŸ”„ Workflow Engine

Draft
β†’
Submitted
β†’
Approved
β†’
Completed
β†’
Cancelled

A Frappe Workflow defines a set of Workflow States, the Actions/Transitions allowed between them, and which Role may perform each transition β€” letting a document (any DocType, standard or custom) require a multi-step approval chain without writing code:

Purchase Request
β†’
Employee submits
β†’
Department Manager
β†’
Finance
β†’
Purchase Manager
β†’
Approved

πŸ›‘οΈ Security, Backup & Troubleshooting

Authentication
↓
Authorization
↓
Role Permission
↓
User Permission
↓
Document Access

Layer that with HTTPS everywhere, a real password policy, server-side validation (never trust client scripts alone), and the audit trail every document already carries (owner, modified_by, version history) β€” plus scheduled, tested backups:

ERPNext
↓
Database Backup + Files Backup
↓
Backup Storage (offsite)
↓
Recovery (tested, not assumed)
SymptomLikely causeFirst command to run
Site doesn't openWeb process/Nginx down, or wrong host entrybench start / check Nginx logs
MariaDB connection errorDB not running or wrong credentials in site configbench mariadb
Redis errorRedis cache/queue process not runningcheck redis-server status/logs
Scheduled jobs not firingScheduler disabled or worker not runningbench doctor
Migration failureConflicting patch or schema changebench migrate (read the traceback first)
Build/asset failureNode/Yarn version mismatchbench build
Nginx 502Gunicorn/app server not runningcheck Supervisor status

πŸ’» Developer Command Cheat Sheet

bench start                          # run dev processes (web, workers, scheduler)
bench restart                        # restart supervisor-managed processes (production)
bench update                         # pull latest app code + run migrations
bench migrate                        # apply pending schema/data migrations
bench build                          # rebuild frontend JS/CSS assets
bench clear-cache                    # clear Frappe's Redis cache
bench clear-website-cache            # clear rendered website page cache
bench console                        # interactive Python shell with the site bootstrapped
bench mariadb                        # open a MariaDB shell for the current site
bench doctor                         # diagnose common environment problems
bench --site SITE list-apps          # list apps installed on a site
bench --site SITE install-app APP    # install an app into a site
bench --site SITE uninstall-app APP  # remove an app from a site
bench --site SITE backup             # take a database (+ optional files) backup

βœ… Best Practices

  • Never modify ERPNext (or Frappe) core source β€” every core change gets silently discarded or conflicts on the next bench update.
  • Extend via a Custom App + hooks + Custom DocTypes + Custom Fields + Workflows + APIs instead.
  • Use fixtures and patches to move customizations between dev, staging, and production predictably.
  • Keep development, staging, and production as separate sites/environments, and test every upgrade in staging first.
  • Back up the database before running bench migrate or bench update in production.
  • Prefer the REST API for external integrations over direct database access.
  • Design permissions through Roles and User Permissions rather than hard-coding access checks in custom scripts.
  • Keep everything in version control, including fixtures exported from the UI.

🏒 Real-World Scenarios

Retail: ABC Electronics Ltd.

Customer
β†’
Quotation
β†’
Sales Order
β†’
Delivery
β†’
Sales Invoice
β†’
Payment

Running in parallel on the purchasing side β€” Supplier β†’ Purchase Order β†’ Purchase Receipt β†’ Purchase Invoice β†’ Payment β€” with Stock and Accounting updating automatically from both sides without either team touching the other's documents directly.

Manufacturing Company

Sales Order
β†’
BOM
β†’
Work Order
β†’
Raw Material Consumption
β†’
Job Card
β†’
Finished Product
β†’
Delivery & Invoice

Custom Module: Laboratory Extension

The same Lab Management case study above, closing with billing: once a Lab Report is approved, the custom app's business-logic hook creates a Sales Invoice against the linked Customer, feeding straight back into the Accounting module covered earlier.

πŸ—ΊοΈ Learning Roadmap & What's Next

Level 1 β€” ERP Basics
↓
Level 2 β€” ERPNext UI
↓
Level 3 β€” Modules
↓
Level 4 β€” Business Workflows
↓
Level 5 β€” DocTypes
↓
Level 6 β€” Database
↓
Level 7 β€” Permissions
↓
Level 8 β€” Customization
↓
Level 9 β€” Custom App
↓
Level 10 β€” API & Integration
↓
Level 11 β€” Production Deployment
↓
ERPNext Intermediate Developer

This article is Part 1 of a 9-part series, covering the foundations (architecture, installation, modules, and database design) in enough depth to navigate the rest of ERPNext confidently. The planned follow-ups go deeper on each area:

If your interest in ERPNext is specifically the accounting side β€” how GL Entry, Payment Entry, and invoice settlement actually work under the hood β€” that ground is covered in full depth, framework-agnostically, in Invoice vs Payment: The Complete ERP Accounting Architecture and the Accounting to ERP curriculum.

↑