Skip to content

ERPNext 6 min read Updated

Upgrading ERPNext without breaking your customizations

What breaks when you upgrade ERPNext a major version: core edits, Server Scripts, custom fields and patches. Plus a staging routine and testing checklist.

Written by the erpfly team, people who build on Frappe and Odoo for a living.

Upgrading ERPNext keeps your customizations intact as long as they live in the right places: custom fields and property setters in the database or in an app’s fixtures, logic in your own Frappe app, and nothing edited inside the frappe or erpnext folders. What breaks is everything else. Edits to core files get overwritten or block the update, Server Scripts fail at runtime when they call functions that changed, and custom app patches run in the wrong order. The safe routine is always the same: back up with files, restore onto a staging copy, upgrade there, test the processes people actually use, and only then touch production.

Below is how we run a major version upgrade like v15 to v16, and what we check before we’re willing to call it done.

What actually breaks during an upgrade

Edits to core files

This is the big one. Someone needed a field hidden on the Sales Invoice print, or a validation relaxed, and changed a .py or .js file inside apps/erpnext directly. It worked, and it was never written down.

When you move to a new major branch, those changes either conflict and stop the update or get thrown away with --reset. Either way the behaviour your team relies on disappears. Find them before you start:

cd ~/frappe-bench/apps/erpnext
git status --short
git diff --stat

cd ~/frappe-bench/apps/frappe
git status --short

Any output from git status means someone changed core. Each change needs a new home, usually a custom field, a property setter, a doc_events hook or an override_doctype_class entry in your own app’s hooks.py.

Server Scripts and Client Scripts calling things that moved

Server Scripts are stored in the database, so they survive the upgrade itself. That’s the trap. They don’t fail during bench migrate. They fail on Tuesday morning when someone submits a Delivery Note and the script calls a method whose signature changed, or reads a field that was renamed.

Client Scripts have the same problem with a different cause. Scripts that reach into the DOM, use undocumented form internals or depend on a particular layout tend to break when the desk UI changes between majors.

Pull every script out of the database before you upgrade so you can read them in one place:

# bench --site staging.example.com console
for s in frappe.get_all(
    "Server Script",
    fields=["name", "script_type", "reference_doctype", "disabled", "script"],
):
    print(f"--- {s.name} ({s.script_type}, {s.reference_doctype}, disabled={s.disabled})")
    print(s.script)

Do the same for Client Script. If you have more than a handful of long scripts, this is a good moment to read our take on Server Scripts vs a custom app. Code in an app can be tested with bench run-tests before the upgrade. Code in a Server Script can only be tested by clicking.

Custom fields created in the UI vs fixtures

Custom fields added through Customize Form are rows in the Custom Field table. They survive upgrades fine. The problem is that they exist only on that one site, with no history, so you can’t easily tell which fields are still used or who added them.

Custom fields exported as fixtures from your own app are better. They’re in Git, they get recreated on every bench migrate, and you can install the same app on staging and get an identical schema. Set a Module on the fields and property setters that belong to your app, then declare them in hooks.py:

fixtures = [
    {"dt": "Custom Field", "filters": [["module", "=", "Acme Customizations"]]},
    {"dt": "Property Setter", "filters": [["module", "=", "Acme Customizations"]]},
]

Then export them into the app:

bench --site erp.example.com export-fixtures --app acme_customizations

One thing to watch: a standard field added by the new version can collide with a custom field of the same fieldname. Check your custom fieldnames against the new version’s DocType JSON for the documents you’ve customised most.

Patches in your own apps

Your custom app’s patches.txt runs during bench migrate, alongside the core patches. Since v14 the file has [pre_model_sync] and [post_model_sync] sections. Pre-sync patches run before DocTypes are reloaded, so they’re where you copy data out of a field that’s about to change type or be renamed. Post-sync patches run after, so anything that writes to a newly added field belongs there. Put a patch in the wrong section and migrate either fails halfway or quietly does nothing.

Old patches that already ran won’t run again, because Frappe records them in Patch Log. New patches you write for the upgrade should be safe to run twice anyway, in case you have to restore and retry.

Custom apps without a matching branch

Every app on the bench has to support the new Frappe major. That includes marketplace apps and your own. If a third-party app you depend on has no branch for the new version yet, you aren’t upgrading yet, no matter what else is ready.

For your own apps, update the Frappe version range in pyproject.toml and create a branch for the new major so you can still patch the old one.

The routine we follow

1. Take a full backup of production

bench --site erp.example.com backup --with-files
ls -lh ~/frappe-bench/sites/erp.example.com/private/backups/

That gives you the database dump plus public and private file archives. Copy them off the server. A backup that only exists on the machine you’re about to upgrade doesn’t count.

2. Restore onto a staging bench that matches production

Same app versions as production, on a separate server or at least a separate bench. Then restore, and stop the site from emailing customers or running scheduled jobs:

bench --site staging.example.com restore \
  ~/backups/20260320_020000-erp_example_com-database.sql.gz \
  --with-public-files ~/backups/20260320_020000-erp_example_com-files.tar \
  --with-private-files ~/backups/20260320_020000-erp_example_com-private-files.tar

bench --site staging.example.com set-config mute_emails 1
bench --site staging.example.com disable-scheduler

Skip muting emails once and you’ll send a batch of payment reminders from a test server. People only make that mistake once.

3. Switch branches and migrate on staging

cd ~/frappe-bench
bench switch-to-branch version-16 frappe erpnext hrms --upgrade
# switch your custom apps to their matching branch too
bench setup requirements
bench build
bench --site staging.example.com migrate

Check the release notes for the new major’s required Python and Node versions before this step. Major releases have raised both in the past, and a virtualenv on the wrong Python gives confusing errors.

A few bench update flags matter for minor updates within a version. --reset discards local changes to apps, which is exactly what you want only after you’ve rescued any core edits. --no-backup skips the automatic backup, which we never use on production. --patch, --build and --requirements run just that one step.

4. Read the migrate output, all of it

A migrate that finishes isn’t the same as a migrate that worked. Look for patches that were skipped, fixtures that failed to sync and deprecation warnings mentioning your apps. Then check the error log in the desk for anything new.

Testing checklist before production

Test with real users on the staging copy, working through their actual week. Our minimum list:

  • Create, submit and cancel one of each document your team uses daily: Quotation, Sales Order, Delivery Note, Sales Invoice, Purchase Receipt, Payment Entry, Journal Entry.
  • Trigger every enabled Server Script at least once, on the event it’s attached to.
  • Open the forms with Client Scripts and check buttons and field behaviour, not just that the page loads.
  • Print every custom Print Format you send to customers. Jinja templates that access fields directly can break without raising an error.
  • Run your Script Reports and Query Reports for last month and compare totals to production.
  • Walk through each Workflow, including rejection paths, as a user with the right role (not Administrator).
  • Run bench --site staging.example.com run-tests --app acme_customizations if your app has tests.
  • Check integrations: payment gateways, e-invoicing, bank feeds, anything calling your API with a key.
  • Re-enable the scheduler briefly and confirm scheduled jobs complete.

Our opinion: fix the customizations, not the upgrade

When an upgrade goes badly, teams often decide to stay on the old version. That’s the worst outcome. You’re now on a release that will stop getting fixes, and the next jump is bigger.

The upgrade didn’t cause the breakage. The customizations were fragile and the upgrade revealed it. Treat the staging run as an audit. Every core edit becomes a hook. Every long Server Script becomes app code with a test. Every UI-created custom field that matters gets a module and becomes a fixture. It’s more work than patching things until they run, and it’s the only way the next upgrade is boring.

We’d also avoid upgrading production in the first weeks after a new major is released, unless you need a specific feature. Let the early point releases land and give your third-party apps time to catch up.

Getting help with the hard parts

If your staging run turns up a pile of core edits and Server Scripts, erpfly can turn them into a proper Frappe app with fixtures, hooks and tests, so they upgrade cleanly next time. See ERPNext custom module development or our broader ERPNext customization service. And if you’re wondering whether it’s worth it, our guide to ERPNext customization costs covers how rework like this is usually priced.

Sources

The official documentation and source code this page was checked against.

  1. 01 Database Migrations, Frappe Framework documentation docs.frappe.io
  2. 02 Hooks, Frappe Framework documentation docs.frappe.io
  3. 03 Supported Versions, ERPNext wiki github.com

Try it on your own module

Describe what you need. You'll see the generated code before anything touches your site.

ERPNext v16

Keep reading

Create your account

Free to start. No card needed.

By signing up you agree to our terms and privacy policy.