Skip to content
Menu
Building on Frappe 6 min read ·

Why we build custom work as a separate Frappe app, and what happens at upgrade time

Bizmap engineering team
The stance
Patching core is borrowing against your next upgrade at a bad rate.
A question about this?Ask the team that wrote this. An engineer, not a ticket system, replies.Talk to an engineer →

Every ERPNext project reaches the point where the standard product stops and the business carries on. A field the business needs, a workflow with one more approval, a report the finance team has built in Excel for ten years, an integration with a system nobody has heard of. How that work is done decides whether your next upgrade is a weekend or a project.

Our practice is simple to state: custom work goes into a separate Frappe app, in your repository, and we never patch ERPNext or Frappe core. This piece explains why, what still breaks at upgrade time even when you do it properly, and how we run an upgrade.

Three ways to change ERPNext

There are broadly three places a change can live.

Configuration, stored in the database. Custom fields, property setters (changing a label, making a field mandatory), workflows, print formats, notifications, client scripts and server scripts can all be created from the browser. They are quick, visible to an administrator and need no deployment.

A custom app. A Frappe app is a Python package with its own DocTypes, hooks, scripts, reports and patches, installed on the site next to ERPNext. Through its hooks it can react to any document event, override a standard DocType's class or a whitelisted method, add scheduled jobs, ship fixtures (custom fields and property setters exported as files) and run data patches during migration.

Changes to core. Editing files inside the ERPNext or Frappe repositories on the server. It works on the day, and it is one of the most common reasons an ERPNext site cannot be upgraded.

Kind of change Where it lives Upgrade risk
Custom fields, property setters Database, exported as fixtures in an app Low, unless a new standard field takes the same name
Workflows, print formats, notifications Database or app fixtures Low to medium; print formats break when fields move
Client scripts and server scripts Database Medium: invisible to version control unless exported
Custom DocTypes, reports, scheduled jobs Custom app Low: the code is yours and the framework API is stable
Overrides of standard classes and methods Custom app Medium to high: depends on core internals
Edits to core files Inside ERPNext or Frappe Certain: every update overwrites or conflicts with them

Why a separate app

The upgrade sees a clean core. When ERPNext and Frappe are untouched, updating them is a version change rather than a merge. The upgrade question becomes "does our app still work against the new version?", which is a question you can test.

The code has an owner and a history. An app is a git repository. Every change has an author, a date and a reason. Configuration typed into a production database has none of these, and a server script that only exists on the live site is one restore away from disappearing.

It can be moved, removed and supported across versions. An app can be installed on a fresh site, on a test copy, or on another client's site. Our own products are built the same way: US Payroll is an app installed beside ERPNext and Frappe HR, and runs on version 15 or 16. The advanced approval workflow is an app that runs on Frappe with or without ERPNext.

It can be read by the next developer. You should not depend on us forever. An app in your repository, with documentation, is something another competent Frappe developer can pick up.

We still use database configuration. A custom field or a property setter is often the right answer. The difference is that we export it as a fixture in the app, so it is versioned with everything else and recreated on any new site.

What still breaks at upgrade time

A separate app removes the worst problem. It does not make upgrades free. These are the places we look first.

Overrides of standard behaviour

An app that replaces a standard DocType's controller class, or overrides a whitelisted method, depends on how the core code is structured. When the core class renames a method, splits it or changes its arguments, the override either fails loudly or, worse, silently stops being called. Every override goes on the upgrade checklist, with the core function it shadows.

We prefer document event hooks (validate, on submit, on cancel) to class overrides where both would work, because they depend on a public contract rather than internals.

Client-side code that reaches into the form

Form scripts that read and set fields through the documented form API are usually fine. Scripts that manipulate the page's HTML structure or depend on a particular layout break when the interface changes, and the interface changes in major versions.

Field collisions and moved fields

A new ERPNext version can introduce a standard field with the same purpose, or sometimes the same name, as a custom field you added years ago. It can also move or rename a field your report, print format or integration reads. Raw SQL in reports is the most fragile here, because nothing checks a column name until the query runs.

The platform underneath

Major Frappe versions move to newer Python and Node.js versions and update their dependencies. Code that relied on a library's old behaviour, or an app that pins an old version of a package, fails at install time rather than in use. Better then than in production, but it needs time in the plan.

Server scripts and data patches

Server scripts stored in the database are not checked against the new version until they run. Old data patches in a custom app can fail on a fresh install if they assume data that no longer exists. We export scripts into the app and keep patches idempotent.

How we run an upgrade

Our migration and upgrades service states the short version: upgrades run on a copy first, with a rollback plan and a checklist of custom code that needs attention. In practice that means:

  1. Inventory every customisation. Custom apps and their overrides, custom fields, property setters, client and server scripts, print formats, workflows, reports and integrations. Anything that only exists in the database is exported into an app first.
  2. Read the release notes against the inventory. Each item on the list gets a note: unaffected, needs a check, or needs a change.
  3. Copy production. A full backup restored to a separate environment, with outgoing email and integrations switched off so the copy cannot send anything to a customer or a bank.
  4. Upgrade the copy and run the migration. Fix what fails, in the custom app, never in core.
  5. Test the business, not only the code. Automated tests where the app has them, then the key users walk through the flows that matter: the purchase cycle, the month-end close, the reports the board reads.
  6. Plan the cutover and the way back. A fresh backup immediately before, a defined window, and a written rollback step if the go or no-go check fails.

Sub Zero Insulation Technologies is an example of an upgrade running alongside new work: the engagement covered both a B2B portal with a 3D configurator and an ERPNext v14 to v16 upgrade, with the configurator's quotations flowing into ERPNext v16.

When you inherit a site with core changes

Sites inherited from another partner sometimes have edits in core. The way out follows the same steps. Compare the installed ERPNext and Frappe code against the official release of the same version to find every change. Decide which changes are still needed. Move those into a custom app, using hooks, fixtures or overrides. Restore core to the official code and test on a copy. Only then plan the upgrade. It is slower than upgrading over the top, and it is the only route that does not leave the next upgrade in the same state.

If this is your situation

If your ERPNext is on an older version, has changes in core, or carries customisations nobody has written down, look at how we handle custom Frappe apps and migration and upgrades. If another partner started the project and it has stalled, read about ERPNext rescue. Then tell us what you are running and an engineer will reply with questions.

Start a conversation

Want this for your operation?

Tell us which case looked closest to yours.