Migrations & Patches
An app's database schema and data change as the code changes. Frappe handles
both with one command:
bench --site mysite.localhost migrate
Run migrate after you pull updates for any app on a site. It applies pending
patches and syncs schema from the DocType JSON files.
What bench migrate does
migrate runs in a fixed order:
- Run
before_migratehooks. - Run pre-model-sync patches from each app's
patches.txt. - Sync the schema from DocType JSON files.
- Run post-model-sync patches.
- Sync jobs, fixtures, dashboards, customizations, languages, and the portal menu.
- Run
after_migratehooks.
The patch and schema steps are the core of a migration. Everything else keeps
derived data in step with the latest code.
Schema changes
When you edit a DocType in Developer Mode, Frappe writes its definition to a
JSON file in the app source tree at
<app>/<module>/doctype/<doctype>/<doctype>.json. Installing an app on a site
creates the database tables from these files.
On migrate, Frappe compares the hash of each DocType JSON against the hash
stored in the database. If they differ, it reloads that DocType and applies the
schema change. Only changed DocTypes are touched.
Removing or renaming a field does not drop its database column. The column stays
but the field no longer shows in the form. This avoids data loss and lets you
write a patch that still reads the old values. Frappe does not support reverse
schema migrations.
Data migrations with patches
A patch is a one-off script that changes existing data to match new code. Each
patch runs once per site and is recorded in the Patch Log so it never runs
again.
Writing a patch
A patch is a Python module with an execute function:
import frappe
def execute():
# patch code here
frappe.db.set_value("System Settings", None, "country", "India")
Put patches in a patches package inside your app, grouped by version:
myapp
└── patches
└── v15_0
└── set_default_country.py
Then register the patch in myapp/patches.txt by its dotted path:
myapp.patches.v15_0.set_default_country
Pre and post model sync
patches.txt uses an INI-style format with two sections that decide when a
patch runs relative to the schema sync:
[pre_model_sync]
myapp.patches.v15_0.backup_old_field
[post_model_sync]
myapp.patches.v15_0.set_default_country
pre_model_syncpatches run before the schema is synced, so the DocType meta
still reflects the old JSON. Use this when your patch needs old fields that the
new schema removes.post_model_syncpatches run after the schema is synced, so the meta is up to
date. Most patches that only change data belong here.
A patches.txt with no section headers is read as the old flat format, where
every line is a single pre-model-sync patch.
Reloading a DocType inside a patch
If a pre-model-sync patch needs the latest schema for a specific DocType, reload
it from its JSON first with frappe.reload_doc:
import frappe
def execute():
frappe.reload_doc("myapp_module", "doctype", "my_doctype")
# patch code that needs the new schema
reload_doc(module, dt, dn) takes the module name, the document type (usually
"doctype"), and the document name. Pass force=True to reload even when the
hash is unchanged.
One-off statements
For small changes you do not need a separate module. Prefix a line in
patches.txt with execute: and Frappe runs it as a Python statement:
execute:frappe.delete_doc("Page", "applications", ignore_missing=True)
A common use is resetting permissions after a DocType ships new defaults, since
permission changes are not synced automatically:
execute:frappe.permissions.reset_perms("My DocType")
Re-running a patch
Every line in patches.txt must be unique, and a patch that has run will not
run again. To force a patch to run a second time, change the line so it looks
new. Add a trailing comment:
myapp.patches.v15_0.set_default_country #2026-06-29 re-run
The comment makes the line distinct, so Frappe treats it as a new patch and runs
it again.
Running a patch at the end
Prefix a patch with finally: to defer it until all other patches in the run
have finished:
finally:myapp.patches.v15_0.rebuild_caches