Skip to main content
ERPNext follows strict code style guidelines to maintain consistency and quality across the codebase. This guide covers the tools and rules used for code formatting and linting.

Python Requirements

ERPNext requires Python 3.14 or higher.
requires-python = ">=3.14"

Automated Code Quality Tools

ERPNext uses several automated tools to enforce code quality standards. These tools run automatically through pre-commit hooks and CI/CD pipelines.

Pre-commit Hooks

All contributors must use pre-commit hooks. These hooks run automatically before each commit to catch issues early.
1

Install Pre-commit

Pre-commit hooks are configured in .pre-commit-config.yaml and will run automatically when you commit.
# Pre-commit is installed as part of the development dependencies
# Hooks will run automatically on git commit
2

Understand What Runs

The following checks run on every commit:
  • Trailing whitespace removal
  • YAML, JSON, and TOML validation
  • Merge conflict detection
  • Python AST validation
  • Debug statement detection
  • Code formatting (Prettier, Ruff)
  • Linting (ESLint, Ruff)
3

Fix Issues

If pre-commit hooks fail, fix the reported issues and commit again. Some issues are auto-fixed.
Commits will be blocked if pre-commit hooks fail. You cannot bypass these checks.

Python Code Style

Ruff Configuration

ERPNext uses Ruff for Python linting and formatting. Ruff is configured in pyproject.toml:
[tool.ruff]
line-length = 110
target-version = "py310"

Line Length

Maximum line length is 110 characters.

Enabled Linting Rules

ERPNext enables the following Ruff rule sets:
[tool.ruff.lint]
select = [
    "F",    # Pyflakes
    "E",    # pycodestyle errors
    "W",    # pycodestyle warnings
    "I",    # isort (import sorting)
    "UP",   # pyupgrade
    "B",    # flake8-bugbear
    "RUF",  # Ruff-specific rules
]
  • F (Pyflakes): Detects various Python errors like undefined names, unused imports, etc.
  • E/W (pycodestyle): Enforces PEP 8 style conventions
  • I (isort): Ensures imports are properly sorted and organized
  • UP (pyupgrade): Suggests modern Python syntax improvements
  • B (bugbear): Catches likely bugs and design problems
  • RUF: Ruff-specific rules for code quality

Ignored Rules

Certain rules are intentionally disabled for ERPNext:
ignore = [
    "B017",  # assertRaises(Exception) - should be more specific
    "B018",  # useless expression, not assigned to anything
    "B023",  # function doesn't bind loop variable
    "B904",  # raise inside except without from
    "E101",  # indentation contains mixed spaces and tabs
    "E402",  # module level import not at top of file
    "E501",  # line too long (handled by formatter)
    "E741",  # ambiguous variable name
    "F401",  # "unused" imports
    "F403",  # can't detect undefined names from * import
    "F405",  # can't detect undefined names from * import
    "F722",  # syntax error in forward type annotation
    "W191",  # indentation contains tabs
    "RUF001", # string contains ambiguous unicode character
]
These rules are ignored due to ERPNext’s specific coding patterns and legacy code considerations.

Type Hints

ERPNext uses Frappe’s type hint system:
typing-modules = ["frappe.types.DF"]
This allows proper type checking for Frappe DocType fields.

Code Formatting

Ruff’s formatter is configured with these settings:
[tool.ruff.format]
quote-style = "double"
indent-style = "tab"
docstring-code-format = true
1

Quote Style

Use double quotes for strings:
# ✅ Correct
message = "Hello, World!"

# ❌ Incorrect
message = 'Hello, World!'
2

Indentation

Use tabs for indentation, not spaces:
# ✅ Correct (tabs)
def calculate_total(items):
	total = 0
	for item in items:
		total += item.amount
	return total
3

Docstring Code Formatting

Code examples in docstrings are automatically formatted:
def process_order(order):
	"""Process a sales order.
	
	Example:
		>>> process_order(order)
		{'status': 'success'}
	"""
	pass

Import Sorting

Ruff automatically sorts imports using the isort rules:
- id: ruff
  name: "Run ruff import sorter"
  args: ["--select=I", "--fix"]
Imports are organized in this order:
  1. Standard library imports
  2. Third-party imports
  3. Frappe imports
  4. Local imports
# ✅ Correct import order
import json
from datetime import datetime

import requests
from rapidfuzz import fuzz

import frappe
from frappe import _
from frappe.utils import flt, cint

from erpnext.stock.utils import get_stock_balance

JavaScript/Vue Code Style

Prettier

ERPNext uses Prettier for JavaScript, Vue, and SCSS files:
- repo: https://github.com/pre-commit/mirrors-prettier
  rev: v2.7.1
  hooks:
    - id: prettier
      types_or: [javascript, vue, scss]
Prettier automatically formats your JavaScript and Vue files with consistent style.

Excluded Patterns

The following files are excluded from Prettier formatting:
  • erpnext/public/dist/* (build artifacts)
  • cypress/* (test files)
  • node_modules/*
  • *boilerplate*
  • erpnext/templates/includes/* (Jinja templates)

ESLint

JavaScript files are linted with ESLint:
- repo: https://github.com/pre-commit/mirrors-eslint
  rev: v8.44.0
  hooks:
    - id: eslint
      types_or: [javascript]
      args: ['--quiet']
ESLint errors will block your commit. Fix all reported issues before committing.

Pre-commit Hook Configuration

Basic Checks

These checks run on every commit:
Removes trailing whitespace from ERPNext files (excludes JSON, TXT, CSV, and MD files):
- id: trailing-whitespace
  files: "erpnext.*"
  exclude: ".*json$|.*txt$|.*csv|.*md"
Validates file syntax:
  • check-yaml: YAML files must be valid
  • check-json: JSON files must be valid
  • check-toml: TOML files must be valid
  • check-ast: Python files must have valid syntax
Prevents direct commits to the develop branch:
- id: no-commit-to-branch
  args: ['--branch', 'develop']
You cannot commit directly to the develop branch. Create a feature branch instead.
Detects merge conflict markers:
- id: check-merge-conflict
Prevents committing Python debug statements like pdb.set_trace():
- id: debug-statements

CI/CD Linting

All pull requests are automatically checked by GitHub Actions:

Linters Workflow

name: Linters

jobs:
  linters:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Set up Python 3.14
        uses: actions/setup-python@v6
      - name: Install and Run Pre-commit
        uses: pre-commit/[email protected]
The same pre-commit hooks that run locally also run in CI. If they pass locally, they’ll pass in CI.

Semgrep Analysis

ERPNext also runs Semgrep for advanced security and correctness checks:
semgrep:
  runs-on: ubuntu-latest
  steps:
    - name: Download Semgrep rules
      run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git
    - name: Run Semgrep rules
      run: semgrep ci --config ./frappe-semgrep-rules/rules
Semgrep checks for common security vulnerabilities and code correctness issues. Address all Semgrep findings before merging.

Best Practices

Server-Side Validation

All business logic and validations must be implemented on the server-side, not in JavaScript.
This ensures:
  • Security: Client-side code can be bypassed
  • API consistency: REST API and UI use the same logic
  • Reliability: Logic is tested and version-controlled

Code Organization

Follow these guidelines for organizing your code:
  1. Keep functions focused: Each function should do one thing well
  2. Use meaningful names: Variable and function names should be descriptive
  3. Add docstrings: Document functions with clear docstrings
  4. Write tests: Include unit tests for new functionality
  5. Handle errors: Use proper error handling and user-friendly messages

Example: Well-Formatted Python Code

import frappe
from frappe import _
from frappe.utils import flt, getdate

from erpnext.stock.utils import get_stock_balance


def validate_stock_availability(doc, method=None):
	"""Validate that sufficient stock is available for all items.
	
	Args:
		doc: Sales Order document
		method: Hook method name (optional)
		
	Raises:
		frappe.ValidationError: If insufficient stock is available
	"""
	for item in doc.items:
		if item.item_code and item.warehouse:
			stock_balance = get_stock_balance(
				item.item_code,
				item.warehouse,
				doc.transaction_date
			)
			
			if flt(stock_balance) < flt(item.qty):
				frappe.throw(
					_(
						"Insufficient stock for item {0} in warehouse {1}. "
						"Available: {2}, Required: {3}"
					).format(
						item.item_code,
						item.warehouse,
						stock_balance,
						item.qty
					)
				)

Quick Reference

ToolPurposeConfiguration
RuffPython linting & formattingpyproject.toml
PrettierJavaScript/Vue/SCSS formatting.pre-commit-config.yaml
ESLintJavaScript linting.pre-commit-config.yaml
SemgrepSecurity & correctness analysisGitHub Actions
Pre-commitAutomated pre-commit checks.pre-commit-config.yaml

Troubleshooting

If pre-commit hooks fail:
  1. Read the error message carefully
  2. Fix the reported issues (many are auto-fixed)
  3. Stage the fixed files: git add <files>
  4. Commit again: git commit -m "your message"
For persistent issues:
# Run pre-commit manually to see full output
pre-commit run --all-files
If Ruff is reordering your imports unexpectedly:
  1. Let Ruff auto-fix: ruff check --select=I --fix .
  2. Commit the changes
  3. Review the new import order matches the standard
If you have lines longer than 110 characters:
  1. Break the line using appropriate Python style
  2. Use parentheses for implicit line continuation
  3. Break method chains into multiple lines
# ✅ Good
result = (
	some_long_function_name(argument1, argument2, argument3)
	.filter(condition)
	.map(transform)
)

Build docs developers (and LLMs) love