Most ERPNext configuration lives in the database, not in code. A field someone added through Customize Form, a new role, a workflow for purchase approvals: all of them are rows, and rows don’t travel with git pull. Fixtures are how a Frappe app takes those rows along.
How records get in and out
You list the DocTypes you want in the fixtures variable of your app’s hooks.py. An entry can be a plain DocType name, which exports every record of that type, or a dict with dt plus filters or or_filters to narrow it down.
Running bench export-fixtures reads that list and writes one JSON file per entry into <app>/fixtures/, named after the DocType (custom_field.json, property_setter.json and so on). System fields like owner, creation and modified_by are left out, so the files diff reasonably well in a pull request.
Going the other way, bench migrate and app installation both import every JSON file in that folder. The import is forced. It doesn’t compare timestamps, so whatever is in the file replaces what’s in the database.
A filtered fixtures list
# acme_custom/hooks.py
fixtures = [
{"dt": "Custom Field", "filters": [["module", "=", "Acme Custom"]]},
{"dt": "Property Setter", "filters": [["module", "=", "Acme Custom"]]},
{"dt": "Role", "filters": [["name", "in", ["Credit Controller"]]]},
]
bench --site erp.example.com export-fixtures --app acme_custom
Filtering on module only works if you set the Module on each Custom Field and Property Setter when you create it. That habit is most of the discipline fixtures need. It’s also how erpfly packages customizations: every record it creates carries the app’s module, so the export picks up those records and nothing that belongs to another app.
Where teams get burned
No filter. fixtures = ["Custom Field"] exports every custom field on the site, including ones created by other installed apps. Install your app somewhere else and it starts overwriting fields it doesn’t own.
Editing on production afterwards. Because the import is forced, a label changed in the browser on production reverts on the next migrate. Change the record on a development site, export again, commit.
Expecting deletes to sync. Removing a record from the JSON doesn’t remove it from sites that already have it. Fixture sync only inserts and updates. Delete it with a patch if it really has to go.
Forgetting dependencies. A Workflow refers to Workflow State and Role records. Export those alongside it so a fresh site has everything the workflow points at.
On v16, export-fixtures refuses DocType and Page as fixture entries. New DocTypes belong in the app’s module folder, created in developer mode.
Fixtures or Export Customizations
Customize Form has an Export Customizations button, available in developer mode. It writes the Custom Fields and Property Setters for one DocType (and its child tables) to <module>/custom/<doctype>.json, and they’re applied on migrate if you tick “sync on migrate”. That’s fine for a single form. Fixtures cover any DocType and a whole app’s worth of records from one list, which is why we prefer them. Our guide to keeping customizations through an ERPNext upgrade shows where fixtures fit in a staging routine, and the ERPNext customization page covers the rest of the packaging.