Building a custom Frappe app for ERPNext

A custom Frappe app holds ERPNext changes that are too large or too code-heavy for the UI, while keeping the standard ERPNext code untouched. This page explains when to choose an app, how the build and hooks work, and what to plan for upgrades.

On this page

When a custom Frappe app is the right home for ERPNext changes

A custom Frappe app is the right home for ERPNext changes once your logic has to be versioned, reviewed, tested and deployed like any other software. Custom fields, client scripts and server scripts cover many needs, but they live in the site database, so a change made in one environment does not travel to the next unless someone exports it and manages it by hand. When business rules span several DocTypes, need unit tests, call external services or must be rolled out identically across staging and production, the ERP team needs a codebase. In practice, that threshold is usually reached when a server script grows past a few lines, when several people edit the same logic, or when an audit asks who changed a posting rule and when.

In the Frappe Framework, a custom app is a Python package with its own DocTypes, server and client scripts, fixtures, print formats, reports and a hooks.py file that tells the framework how to attach your code to ERPNext. You create the skeleton with bench new-app, install it on a site with bench install-app, and keep it in a repository separate from ERPNext. That separation matters for ownership: ERPNext is licensed under GPL-3.0 and the Frappe Framework under MIT, so your app is code you write and license, and you decide who can read and change it. Confirm the license for your own app with your legal team before publishing it, and confirm the Frappe and ERPNext versions your app targets against the supported versions page before you commit to a major version.

The decision is less about code volume than about who will maintain it. Ask whether your team has Python and Frappe skills to own the app after go-live, whether you have a staging site that mirrors production for testing migrations, and whether your hosting allows custom apps at all. Server scripts are disabled by default from version 15 and are not available on Frappe Cloud public shared benches, so an app may be the only route for some logic. Get a written scope that lists each rule the app will enforce, the hooks it will use and the upgrade test plan, so the cost of building and maintaining it is visible before work starts.

Which changes belong in an app and which stay in the UI

Custom fields and property setters cover most day-to-day adjustments: adding a field to a form, making a standard field mandatory, hiding a section or changing a label. These changes are stored as database records rather than code, so they can be made by a functional administrator and reviewed in the UI. They are the first layer to try because they carry the least risk during upgrades, though some of them must be exported to version control to survive a rebuild.

  • Custom fields: Add new fields to existing DocTypes, including fields that fetch values from linked masters.
  • Property setters: Change attributes of standard fields, such as required, read-only or default values, without editing ERPNext source.
  • Customize Form: Adjusts form layout and field behavior on a per-site basis.
  • Export requirement: Records created here live in the database, so they must be exported to git to be reproducible across environments.
  • Limit: These tools cannot add new DocTypes, business rules with branching logic or background jobs.

Before approving a request, check whether a field or property change meets the need, and confirm with your team how these records will be exported and promoted between sites.

How hooks extend and override ERPNext without forking it

The hooks.py file in your custom app is the contract between your code and ERPNext. It tells the Frappe Framework what to run and when, so you can react to ERPNext behavior without editing ERPNext's own files. The main mechanisms are document event hooks (doc_events), which run your Python when a document is validated, saved, submitted or cancelled; scheduled task hooks, which run functions on daily, hourly or other intervals through the scheduler; and class-level hooks such as extend_doctype_class and override_doctype_class, which attach behavior to or replace the Python controller behind a DocType. Document events and scheduled tasks are the lower-risk choices because they call your code alongside the standard logic instead of substituting for it. Confirm the exact hook keys, event names and function signatures in the Frappe Framework documentation for the version you run, because this page does not carry a hook reference and the names have changed across major releases.

Overriding a controller class is where upgrade risk concentrates. Your subclass inherits from the base class as it exists in one ERPNext release, and that base class can change its methods, validation order or field handling in the next. An override that copies or replaces a method can silently skip new checks, so a successful upgrade may still produce wrong postings, tax lines or stock movements. Before you choose an override, ask whether a document event, a custom field, a client script or a server script can meet the requirement. Where a class override is unavoidable, keep it narrow, call the parent method rather than duplicating its logic where the framework allows, and write regression tests for the documents it touches. Plan a review of every override in each upgrade, and budget that review as recurring work, not a one-time task.

Before you rely on any hook in production, run a short verification on a staging site with a copy of your data. Check that the hook fires once per expected event, that it does not fire on unrelated DocTypes, and that a failure inside your code blocks or logs the transaction the way you intend. Record each hook, the ERPNext and Frappe versions it was tested against, and the owner who reviews it when versions change. Ask any implementation partner to deliver this register with the code, since it is the document your team will need when the next major release arrives.

How to build and structure a custom app step by step

This sequence takes a custom Frappe app from an empty scaffold to a version-controlled app installed and migrated on a staging site that matches the production ERPNext version. Each step ends with a check so problems surface before anything reaches production.

  1. Match the bench to the target ERPNext version

    Before writing any code, confirm which ERPNext and Frappe Framework major version the live system runs, and create or select a bench on the same version. ERPNext v15 and v16 are both supported according to Frappe's supported versions page, but an app built on one major version is not expected to run unchanged on the other, so the version must be fixed first. Ask your implementation lead to record the exact ERPNext and Frappe versions in the project notes. The step is done when bench version on the development machine reports the same major versions as production, and the ERPNext app is listed in the bench's apps directory.

  2. Scaffold the app with bench new-app

    Run bench new-app with the app name your team has agreed on, which creates the package skeleton, hooks file, module folder and project metadata. Fill in the title, publisher, description and license during the prompts, because these values end up in the app's metadata and are hard to change later. The developer then adds the app to a development site with bench get-app if it lives in a separate repository, or edits the scaffold in place. The check is that the new app directory exists under the bench's apps folder and that it appears in the bench's app list.

  3. Create the module and DocTypes

    Define the module that will group the app's DocTypes, then create each DocType with its fields, naming rule, permissions and any child tables. DocTypes are stored as JSON files inside the app, so the definitions travel with the code under version control. Keep the first set narrow: only the records the business process truly needs, with clear link fields to standard ERPNext masters such as Customer, Item or Sales Invoice. The check is that the DocType JSON files appear under the module folder and that the DocTypes open in the desk for a user with the intended role.

  4. Add hooks for overrides and extensions

    Register the app's extensions in the hooks file, for example doc_events to run Python on document saves or submits, doctype_js to attach client-side logic to standard forms, and override_doctype_class where a standard controller must change. Prefer event hooks over class overrides, because overrides depend on the internal structure of the ERPNext version and need more retesting at each upgrade. Record which standard DocTypes each hook touches, so reviewers can judge upgrade risk. The check is that the hooks load without errors on bench --site startup and that a test save on a linked standard document triggers the expected handler once.

  5. Write tests for the custom behavior

    Write automated tests with the Frappe test framework for the business rules the app adds, such as a validation that blocks a sales invoice or a calculation that sets a custom field. Run the tests against a site with the app installed, using the test commands in the bench, and include at least one test that checks a standard ERPNext flow still works with the hooks active. Tests are the only reliable early signal that a later ERPNext patch has broken an override. The check is a passing test run on a clean test site, and the same run is repeated after each change before the code is committed.

  6. Commit the app to version control

    Initialize a Git repository inside the app folder, add a .gitignore for local site files and build artifacts, and commit the app code, DocType JSON, hooks, patches and tests together. Use a remote repository your organization controls and protect the main branch with review requirements. Pin the ERPNext version the app was tested against in the README so the next upgrade starts from a known state. The check is that a fresh clone of the repository contains every file needed to rebuild the app, with no site-specific data in the commit.

  7. Install the app on a staging site

    Create or restore a staging site from a recent production backup, then install the app with bench --site <staging-site> install-app using the app name from the scaffold. The install step runs the app's install hooks and creates its DocTypes in the staging database. Confirm the app appears in the installed apps list for that site before going further. Use a copy of production data, not live data, and document where the backup came from. The check is that the installed apps list on the staging site shows the new app and the custom DocTypes appear in the desk.

  8. Run migrate and verify DocType changes

    Run bench --site <staging-site> migrate to apply schema changes, patches and fixture syncs from the committed code. Then compare the database schema and the DocType definitions in the desk against what the JSON files describe, and rerun the test suite on the migrated site. Repeat the migrate on a second copy from the same backup to confirm the run is repeatable. The check is that migrate completes without errors, the new columns or tables are present, and the tests pass on the migrated staging site. Only then should the same commit move toward production through your change process.

Customization options compared by effort and upgrade risk

This table compares six ways to change ERPNext, from a custom field to a fork, by what each route changes, where it runs, how exposed it is to upgrades and when it fits. Read it top to bottom: each route moves further from the standard product, so the upgrade effort rises with each row. The main takeaway is that most changes belong in the lower-risk routes, and a fork is the option this page advises against.

RouteWhat it changesWhere it runsUpgrade exposureWhen it fits
Custom field or Customize FormAdds fields or property setters to existing DocTypesDatabase metadata in the siteLow, but confirm what survives each upgradeA missing data point on a standard form
Client scriptBrowser-side behaviour on form eventsUser's browserLow to medium, tied to form field namesField filters, buttons and form logic
Server scriptPython on document events or as API endpointsRestricted server environment in the siteMedium, and disabled by default from version 15Small server logic that needs no repository
Custom app with new DocTypesAdds its own DocTypes, pages and reportsSeparate app installed on the benchLow, because ERPNext core is untouchedA new business object ERPNext does not model
Custom app with controller overridesReplaces or extends ERPNext controller logic through hooksSeparate app that loads over ERPNextHigh, since overridden methods can change between releasesCore behaviour must change, and the team can test each release
Fork of ERPNextEdits ERPNext source directlyCopied codebase maintained by your teamVery high, every upstream release must be merged by handNot recommended, as it removes the upgrade path the custom app keeps

Version control and deployment decisions to make early

These decisions shape how the custom app is built, tested and shipped, and they are hard to reverse once customizations sit in production. Settle them before the first DocType is created, then write them into the build plan so every release follows the same path.

Repository layout and branching per major version

Keep one branch per supported ERPNext major version, so a version 15 fix does not block a version 16 build. Decide whether the app repository holds only custom code or also exported customizations, and how the app folder maps to the bench apps directory. Confirm which major version your Frappe Cloud or self-hosted environment runs before choosing the branch model, since support depends on a listed version.

Pinning the app to a compatible ERPNext release

Record the exact ERPNext and Frappe versions the app was tested against, and treat minor release updates as planned changes rather than automatic pulls. ERPNext ships minor releases often on both supported majors, so an unpinned bench can drift between test and production. Ask your partner or internal team to document the pin in the repository and in the release checklist.

Exported fixtures versus hand-written code

Decide which customizations live as fixtures exported from the database, such as custom fields and property setters, and which are written as code in the app's Python and JavaScript files. Fixtures are quick to create but can overwrite production edits on sync, while code is reviewed and tested like any other change. Write down the rule for each object type so developers do not guess during a release.

Running migrate in a controlled window

Treat bench --site <site> migrate as a change with its own window, sign-off and rollback owner, because it applies schema changes, patches and fixture syncs in one run. Plan for downtime on the affected site, confirm the list of pending patches before starting, and assign someone to watch background workers and error logs. Ask whether your hosting model allows you to schedule this run yourself.

Backup before every migrate

Take a full site backup, including the database and files, immediately before each migrate and keep it until the release is verified. Test restoring that backup to a staging site on the same ERPNext version, because an untested backup gives no assurance during an incident. Include the app's own files and the bench configuration in the backup scope, not only the database.

Container images for Docker and Kubernetes

The frappe_docker repository provides production images and Compose files for ERPNext, with services for MariaDB, Redis, workers, the scheduler, Socket.io and Nginx. Build a custom image that layers your app onto the official base, and tag it with the app commit and ERPNext version. Note that the single-file demo setup is meant only for short-lived evaluation and should not carry your build.

Questions teams ask before building a custom Frappe app

What drives the cost of building a custom Frappe app for ERPNext?

The main cost driver is how many DocTypes, server-side rules, integrations and reports the app must own, and how much of the existing ERPNext behavior it has to override. Cost splits into design and discovery, development, testing, deployment, and handover to your team. Ask any quote to itemize these phases, to state which DocTypes and hooks are in scope, and to say which integrations need credentials, sandbox access or vendor support. One-time build cost is separate from recurring work such as upgrade testing and patch review, which continues for as long as the app runs on ERPNext. Compare quotes on the written scope, not on headline effort.

How long does a first custom app take to build?

Duration depends almost entirely on scope: a small app that adds a few fields, a validation rule and one report is far shorter than an app that adds new DocTypes, approval flows and a bank or payroll integration. Timing also depends on how fast your team answers business rule questions and how quickly test data is available. No supplied figure sets a standard duration here, so ask for a timeline tied to named deliverables and milestones, and check whether it includes a staging round, user acceptance testing and a deployment window. Treat any timeline without a listed scope as a placeholder.

Can an internal developer maintain a custom Frappe app?

Yes, if they know Python, the Frappe framework and the ERPNext codebase they are extending, and if the app has tests and clear documentation. Maintenance means reading release notes for each ERPNext minor and major version, rerunning the app's tests, fixing overrides that no longer match changed functions, and keeping the app installed on every site. Check whether at least two people can release and roll back the app, because a single maintainer is a common operational risk. A partner can build the app and hand over a runbook, or stay on a retainer for upgrades and patch review.

What does the work need from our team to go smoothly?

Three things matter most. First, a staging environment that matches production in ERPNext version, installed apps and hosting setup, so that tests reflect real behavior. Second, sample data that covers your edge cases, such as multi-currency invoices, serial or batch items, and returns, with personal data masked or synthetic. Third, named decision owners from finance, operations and IT who can approve business rules in writing before development starts. Ask the partner to list these inputs in the scope document with an owner and a date for each, so delays are visible early.

Can we avoid building a custom app at all?

Often, yes. Many changes are covered by custom fields, property setters, client scripts, print formats, Report Builder reports, notifications and assignment rules, and Server Scripts on versions where they are enabled. Server Scripts are disabled by default from version 15 and must be turned on with server_script_enabled. Frappe Cloud public shared benches do not allow them, so check your hosting first. Move logic into a custom app when it needs version control, unit tests, complex hooks, background jobs or a reusable package across sites. Ask for a written comparison of no-code and app options for each requirement before committing to code.

What happens to our custom app when ERPNext is upgraded?

The app must be tested against each target ERPNext version before it is deployed, because overridden functions, changed DocTypes and removed hooks can break silently. Frappe's supported versions page lists version 14 as end of life since January 31, 2026, version 15 as supported until the end of 2027, and version 16 until the end of 2029, so plan the upgrade path against those dates. Ask whether the app pins its dependencies, how its compatibility is recorded, and who owns the upgrade test suite. Schedule upgrades in a staging copy of production first, and keep a tested rollback plan.

Who owns the code and the license after the app is delivered?

Ownership should be settled in the written scope before development starts, including who holds the repository, who can merge changes and whether the app is kept private or published. ERPNext is licensed under GPL-3.0 and the Frappe Framework under MIT, according to their repositories. Your custom app's license is a choice you make, but it interacts with the code it extends, so have your legal team review it before distribution. Also confirm where the code lives, such as your own Git server, and that your team holds administrator access from day one.

How custom apps survive ERPNext upgrades

The upgrade path is the largest long-term cost of a custom Frappe app for ERPNext, and it starts the day the app is scoped. Each ERPNext major version can introduce breaking changes and app splits, so any class you override, any hook you register and any DocType you extend has to be checked again when the platform moves. Before you commit to a design, confirm which ERPNext and Frappe versions your organization will run, then check the lifecycle dates on the supported versions page. Those dates set your deadlines, and a custom app that depends on a version nearing end of life forces an upgrade on someone else's schedule. Ask the implementation partner to list every override in the app and the upstream file or method each one replaces, because that list is what you will re-test at each upgrade.

A test upgrade on staging should be a routine, not an event. Keep a staging bench that mirrors production in code, apps and a recent anonymized copy of the database, then run bench update or the migration steps against it in a branch of your app repository. Review the release notes for each ERPNext and companion app version in the path, pin your app's dependencies to the matching branch, and run bench migrate and build assets before anyone tests by hand. Record the time and the errors, because that record becomes your estimate for the production window. Confirm with your partner whether the upgrade is quoted as a fixed scope with this routine included.

A regression suite turns that routine into evidence. Cover the overridden controller methods, the document event hooks, custom DocTypes and any scheduled jobs with automated tests that run inside the bench test framework, and add end-to-end checks for the workflows your finance and stock teams depend on, such as posting a sales invoice, creating a stock entry and closing a period. Run the suite on every upgrade candidate and treat any failing override as a blocking defect. Ask for the suite as a deliverable with the app, and ask who maintains it after handover, since a test suite that nobody runs gives you no protection at the next major version.

How DeployOpen scopes and delivers custom ERPNext apps

DeployOpen plans, builds, tests, documents and hands over custom Frappe apps that extend ERPNext inside your own infrastructure, without forking ERPNext itself. Work starts with a written scope that defines which DocTypes, hooks and integrations belong in the app and how they will be verified.

How we scope

  • We map which accounting, stock or production changes must live in a custom app rather than in fields, scripts or exported customizations.
  • The written scope lists the DocTypes, hooks, reports and integrations the app will contain, along with the ERPNext and Frappe versions it targets.
  • Engagements are fixed-scope, so deliverables and timelines are agreed in writing before any app code is written.

How we build

  • We scaffold the app with bench new-app and organize DocTypes, controllers and hooks so ERPNext upgrades touch as little custom code as possible.
  • Overrides and extensions are built through documented hooks, and each one is tested against the ERPNext version named in the scope.
  • Code lives in version control with a branching model, code review and a deployment path from development to staging and production.

How we hand over

  • You receive the app repository, installation and migration steps, and a record of every hook and override and why it exists.
  • Test cases, data import checks and a rollback plan are delivered so your team can reproduce results on its own environment.
  • We document how to run the app across upgrades, including which overrides to re-verify when ERPNext ships a new minor or major release.

How we support

  • Ongoing support, upgrades and continuous testing are available as a retainer, scoped separately from the build.
  • A retainer can cover re-testing the app after ERPNext minor releases and planning the work needed for major version upgrades.
  • Confirm with us during scoping which parts of the handover, support window and access model you need before go-live.

Sources and further reading

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.