Moving ERPNext customizations to version control with fixtures: when teams need it
Changes made through the ERPNext interface, such as custom fields, property setters, scripts and print formats, are stored in the database of the site where they were made. Nothing in that change is written to a file that git can track, so a staging site and a production site can quietly drift apart as different people edit each one. The first question is whether your team has reached the point where the database is the only record of how the system is configured. Teams usually reach that point when they run more than one site, when more than one developer changes the same forms, or when an auditor or internal control owner asks who changed a field and when.
The usual route out of this is to move the customizations into files. Frappe's documentation describes exporting customizations from a site so they can be stored with an app, and keeping them in a custom folder inside an app module so that they are loaded when the app is installed or migrated. The exported files are then committed to git like any other source code, which gives you a history, a review step and a way to rebuild a site from the same definitions. Before you commit to this, confirm in the current Frappe documentation which customization types are exported, how each one is named in the files, and whether any of them still need to be created by hand on a new site.
Treat the need as a decision for your team, not a default. A single site maintained by one administrator may not gain much from this, but a team running development, staging and production has to agree on which environment is the source of truth and who is allowed to change it. Plan for the overwrite behaviour before the first sync: when a migrate runs, exported definitions can replace what is currently in the database. Ask your partner or your own developers to walk through a test migration on a copy of production, and check the result in a git diff before any change reaches live users.
Which export route fits which customization
Export Customizations is the built-in route that writes selected customization records from a site into JSON files inside a custom app module. It suits teams that built most of their changes through the UI and want a fast snapshot of that work. The export captures what you select at the time, so it is a point-in-time copy rather than a live link to the database.
- Custom fields and property setters: Usually captured cleanly, since they are stored as records that describe changes to standard DocTypes.
- Print formats and reports: Exported when selected, but review the file for hard-coded company names, logos and addresses before committing.
- Scripts and workspaces: Client and server scripts and workspace layouts can be exported, but a server script is only meaningful on a site where server scripts are enabled.
- Maintenance: A person must rerun the export after every change and decide what to include, which makes this route easy to forget.
- Overwrite risk: Re-exporting replaces files, so confirm the diff in git before each commit.
Before relying on this route, confirm with your own test site which record types your exact ERPNext version exports and how it handles deleted records.
Fixtures are declared in the app's hooks.py file as a list of DocTypes and filters. On each migrate, Frappe exports matching records from the site into the app, or in some configurations loads them from the app into the site, so the declaration is the contract. Filters are what keep the output limited to your own customizations rather than every record of that type.
- Filtered custom fields: Use a filter on the owning app or the
namepattern so only your fields are captured. - Property setters: Also filterable, and they should be scoped to the standard DocTypes you changed on purpose.
- Roles, workspaces and scripts: Declare them explicitly with filters. An unfiltered declaration on Role or Workspace can pull in records from other apps or local experiments.
- Maintenance: The declaration lives in code and is reviewed like any other change, which gives an audit trail for each filter.
- Overwrite risk: On migrate, site records that match the filter can be overwritten by app files, so test against a copy of production before deploying.
It is worth confirming the exact fixture behaviour of your target version in the Frappe documentation before writing filters, since sync direction and option names have changed across releases.
Custom folders inside an app module hold files that the team writes or edits directly, such as Python controllers, JavaScript, Jinja print templates and JSON definitions for DocTypes the app owns. They are plain source code, reviewed and versioned like any other part of the application. This route is the only one that handles logic and new DocTypes, not just configuration records.
- Custom DocTypes and controllers: Live in the module folder with their JSON and Python files, and are the natural home for logic that a fixture cannot express.
- Print formats and client scripts: Can be kept as files when they need code review, with the database copy treated as a deployment artifact.
- Custom fields on standard DocTypes: Usually better as fixtures than as hand-written files, because the field definitions are records rather than code.
- Maintenance: Developers own the files directly, so it is the most work to keep consistent but the most transparent to review.
- Overwrite risk: Edits made in the UI to a file-backed DocType are overwritten on the next migrate, so the team must agree that the repository is the only place these change.
Ask your development partner or internal team to document which record types live in files and which live in fixtures, so no one has to reconstruct that decision during an incident.
Some customization records are safe to export as a whole, while others will carry data from the site unless a filter narrows them. The difference is whether the record is self-contained or whether it references live business data, users or environment-specific values. Deciding this per record type is the main design task before any export runs.
- Needs a filter: Custom fields and property setters on shared standard DocTypes, because a broad pattern can capture fields added by other apps or by local experiments.
- Needs a filter: Roles and role-to-permission mappings, which can include roles created for individual sites or customers.
- Needs a filter: Workspaces and reports, which may reference site-specific companies, accounts or saved filters.
- Needs manual review: Print formats and email templates, which often contain company identity, legal text or hard-coded contact details.
- Needs manual review: Server scripts and client scripts that call external services, because they may embed credentials, hostnames or test endpoints.
Before a first export, have someone who owns the business process check each record type against a clean test site, and record the decision in the repository so the rules are visible to the next reviewer.
Configuration options and when each fits
This table compares the four ways customizations leave the database and reach git: Export Customizations in the Desk, fixtures declared in hooks.py, JSON files in a module's custom folder, and sync on bench migrate. Read each row as a question: what gets captured, what gets missed, and what you pay for it later.
| Option | What it captures and misses | Main trade-off |
|---|---|---|
| Export Customizations (Desk) | Selected Custom Fields, Property Setters and Client Scripts exported to JSON from the chosen module; records outside the selection are not included | Fast to produce, but the export is a snapshot that must be repeated after every UI change |
| Fixtures in hooks.py | Filtered records of named DocTypes (for example, specific Custom Fields or Print Formats) that the app ships on install and migrate | Filters control scope precisely, but a broad filter can overwrite values edited on live sites |
| Custom folder JSON in a module | Customization files kept in the app's custom directory and loaded when the module is migrated; the app repository holds the source of truth | Clean git history and review, but developers must edit files by hand or re-export carefully |
| Sync on bench migrate | Applies JSON and fixture files to the target site during migration, updating existing records to match the files | Makes deployments repeatable, but local UI edits not reflected in files are overwritten |
| Records created by users (data) | Transactional and master data such as invoices, items and customers, which these mechanisms do not cover | Keep data out of customization files; move it through the Data Import tool or a migration plan |
| Server scripts created in the UI | Stored in the database only, unless exported, and disabled by default from version 15 | Hard to review in git; move logic into an app module when it must be versioned |
How to set up the export and check it
This sequence moves ERPNext customizations from a development site into a git repository and proves that a second site rebuilds the same configuration from those files. Each step ends with a check so the team knows the export is complete before it relies on it.
Create or pick the custom app and repository
Choose the custom app that will hold the customizations, or create it with bench new-app, then initialize a git repository for it and push it to the hosting platform your team uses. The engineer leading the work confirms the app is installed on the development site and that its folder structure is committed as a clean baseline. Done means the repository holds an empty baseline commit and the app appears in the bench apps list on the development site.
Enable export in the development site
On the development site only, enable developer mode in the site configuration so that changes made through the desk are written to files, and confirm that the custom app is the target module for new customizations. Keep production and test sites out of this step, since developer mode is meant for building, not for serving users. Done means a test change, such as a custom field, is recorded as a file inside the app's module folder rather than existing only in the database.
Define fixture filters
Decide which records belong in version control and write them into the app's hooks file as fixtures, using filters that limit each DocType to the customizations your team owns, such as custom fields, property setters, custom scripts or specific print formats. Exclude transactional data and anything that differs by environment, such as company records, users or API keys. Done means the hooks file lists each fixture with its filter, and a reviewer can explain from the file alone what will be exported.
Export the customizations
Run bench export-fixtures for the custom app on the development site so that the filtered records are written as JSON files in the app's fixtures folder, and export any custom scripts or other module-level items the app holds. Run the export again after each change, so files do not drift behind the database. Done means the fixtures folder contains one file per fixture type and the file count matches what the filters should select.
Review the diff before committing
Run git diff and read every changed file, checking for unintended records, environment-specific values such as domain names or email addresses, and deleted items that should still exist. Compare the exported files against the database on the development site by spot-checking several records in the desk, confirming each field value matches its JSON entry. Done means the diff contains only the changes you intended, and each spot-checked record matches its exported file.
Commit and push the customizations
Commit the app folder with a message that names the customizations included, then push the branch and open a review so a second person checks the diff. Keep the commit limited to the custom app so that ERPNext core code is not modified. Done means the reviewed commit is on the shared branch and the repository history shows the customizations as discrete, traceable changes.
Run migrate on a second site
Pull the committed app onto a second site, install it with bench install-app if it is not present, and run bench migrate so that the fixtures are imported from the repository. Confirm that the second site has no developer-mode-only customizations that were not in the repository by checking the custom fields, scripts and print formats in the desk. Done means the second site shows the same customizations as the development site and the migrate log reports no import errors.
Confirm overwrites and record the result
Test one deliberate change on the second site to see whether migrate overwrites it, since fixtures can replace records that were edited directly in the database. Record which customizations were overwritten, which survived, and any records that were skipped, and share that list with the team before the next release. Done means the team has a written note of the overwrite behaviour that they can apply to future migrations.
What overwrites on migrate and how to plan for it
Fixture sync on migrate can overwrite records that someone edited directly in the target site. If a Custom Field, Property Setter or other exported record was changed in production, the next migrate that loads the file from git may replace that change with the version in the repository. Before the first deploy, list every place where these records can be edited, including the desk UI on staging and production. Then confirm in the Frappe documentation which DocTypes are exported and how each record is matched on import. Treat any direct edit made outside git as a change that must be carried into the repository, or it will be lost on the next deploy.
Exported files can also carry stale values when developers edit the same customization in more than one place, such as a local bench, a shared development site and a feature branch. Each export reflects the state of the site it came from, so two branches that touch the same field can produce conflicting JSON that git merges without any warning. Name one environment as the source of truth for each customization type, usually the version-controlled app on a developer machine. Make every other environment a consumer that receives changes only through migrate, and write that rule into the repository's contributing notes so the team knows where each change starts.
Review the diff before every deploy. Run git diff on the custom folder of the app module, read each changed JSON file, and check that every removed or altered entry was intended. Take a backup of the target site first, then run the migrate in a staging copy that mirrors production data and compare the resulting records with what the diff predicted. Ask your partner or internal lead to confirm the exact overwrite behaviour for your ERPNext and Frappe version against the official documentation, and record the outcome in the release notes so the same check runs on every release.
Security and operational risks of exporting customizations
Moving customizations into git puts configuration, scripts and metadata in places they did not previously live. The cards below cover the exposure, review and drift risks that come with that move, and the checks to build into the workflow before the first export is committed.
Exported files may contain secrets
Fixture exports serialize the records and settings they capture, so a Custom Field, Print Format, Webhook or Notification can carry values that should never reach a repository, such as tokens, endpoint URLs with credentials or personal data in sample text. Before committing, scan the exported JSON and HTML for credentials and personal data, and confirm the repository's access and retention rules match the data classification of the site.
Server scripts need explicit review
Server Scripts run Python on document events or as API endpoints. From version 15 they are disabled by default and must be enabled with the server_script_enabled setting, so a script that exports cleanly may not run on a target site until someone turns that setting on. Treat every exported script as code: review it in a merge request, confirm who is allowed to enable the setting, and decide whether the logic belongs in a custom app instead.
Repository access becomes production access
Anyone who can merge to the branch that deploys to production can change business logic, permissions-adjacent behaviour and document workflows on live data. Restrict write access to the customization repository, require review from a second person for changes to scripts and workflows, and keep the deployment pipeline's credentials separate from developer accounts.
Environments drift apart
If changes are made in the UI on production, or exports are taken from one site and not another, the repository stops describing what runs. Adopt a rule that changes are made in development, exported and merged, and never edited directly in production. Compare the exported state with the live site after each deployment so drift is found before it causes a failed migration.
Merge conflicts in JSON exports
Exported DocType and Custom Field files are JSON with ordering and generated values that change between exports, so two developers editing the same form produce noisy conflicts that are easy to resolve incorrectly. Export from one developer's site at a time, keep changes to a given DocType in one branch, and review the diff for unintended property changes such as altered labels, options or default values.
Backup before every migrate
Running bench migrate applies pending patches and syncs customizations from app folders, and the overwrite behaviour can replace database changes that were never exported. Take a database backup and confirm it restores before each migrate on a shared environment, and record which commit was deployed so the site can be returned to a known state if a sync changes records unexpectedly.
Questions teams ask before exporting ERPNext customizations
Who does the work of moving ERPNext customizations into git?
Your team can do most of it, and a partner usually takes on the parts that need design decisions. The work splits into inventorying every customization (custom fields, property setters, scripts, print formats, reports and workflows), deciding which ones belong in an app's custom folder and which should stay in the database, exporting them, and then proving that a migrate on a clean copy reproduces the same behavior. Someone on your side must own the decisions about what is in scope, because each one changes what gets versioned and reviewed. Effort rises with how many customizations were made directly in the UI over the years. Ask any partner for a written scope that lists the inventory, the export set and the test steps, then request a quote from that scope.
How long does it take to set up customization version control?
No duration can be given from the sources available for this page, so the honest answer depends on the inventory. The main drivers are the number of custom fields, scripts, print formats and reports, whether those were built in the UI or in an app, and how many sites share the same customizations. A small, single-site setup with a clean inventory is far quicker to bring under git than a multi-site estate with years of ad hoc changes. Ask for a timeline as part of the written scope, with milestones for the inventory, the export, and the clean-environment test.
What must our team provide before the work starts?
You need three things in place before any export begins. First, a git repository your organization controls, with branch rules and review requirements that match how you already ship code. Second, at least one non-production environment that mirrors production's ERPNext version and installed apps, so exports and migrations can be tested without touching live data. Third, a named owner who can approve scope decisions, review exported files and sign off on the test results. Access to the bench host, database backups and the team that maintains your apps should also be confirmed in writing.
Can we export ERPNext customizations ourselves?
Yes, many teams do the first export in-house, using the Export Customizations option and the bench commands available in their installed version. The risk is not the export itself but the overwrite behavior: fixtures and custom folders are synced on migrate, so a change made in one environment can silently replace a change made in another. Teams that do it alone should document a rule for where customizations are made, run a migrate on a clean copy, and check the diff in git before merging. If your team lacks Frappe experience, a partner can review that process or run the first cycle with your staff.
What drives the effort up or down?
Four factors matter most. The number of customizations is the biggest, since each field, script and print format needs review and a test. The number of sites matters next, because every site that shares an app must match the exported state, and drift between sites multiplies the work. Legacy UI changes are the third factor: customizations made directly in the database, outside any app, often need to be re-created or audited before they can be exported cleanly. The fourth is how much of the logic relies on server scripts, which depend on whether server scripts are enabled on your hosting. Ask a partner to price each of these as separate line items in the quote.
What does the work cost and what recurs afterward?
Costs fall into one-time and recurring groups. The one-time cost covers the inventory, the export, the clean-environment test and the team's training on the new workflow. The recurring cost covers reviewing changes that land in git, running migrate tests before upgrades, and keeping the repository and environments aligned. ERPNext itself is free to self-host under GPL-3.0, according to the ERPNext pricing page, so your spend is on people, infrastructure and any support plan you choose. Request a quote that separates the one-time export from any ongoing retainer for upgrades and maintenance.
Will customizations survive a later ERPNext upgrade?
They can, but only if they are versioned and tested against each target release. Frappe's supported versions page lists which major versions are currently supported, so check your installed version against it before you plan the export. Exported fixtures and app code still need to be re-tested on the new version, because a breaking change in a standard DocType can affect a custom field or script. Plan for a test migrate on a copy of production as part of every upgrade, and confirm with your partner whether upgrade testing is included in the written scope.
How DeployOpen scopes and delivers customization version control
DeployOpen treats moving ERPNext customizations into git as a fixed-scope piece of engineering work. We inventory what lives only in the database, decide what belongs in an app and what can stay as exported fixtures, and agree the result in writing before any export or refactor begins.
How we scope
- Inventory custom fields, property setters, scripts, print formats, reports and workspaces that exist only on the target site.
- Classify each item as fixture, app code, or site-specific data that should never enter the repository.
- Agree in writing which sites, versions and export lists are covered, and what the client must test before sign-off.
How we build
- Set up the repository layout, custom folders in the app module and the export list used for each customization type.
- Export customizations from a clean staging copy, review the generated JSON diffs and remove environment-specific values.
- Test a full rebuild on a fresh bench, confirming that migrate applies the exported customizations and overwrites nothing unintended.
How we hand over
- Deliver the repository, export runbook and a short guide to the overwrite behaviour your team must plan for on each migrate.
- Document the release procedure: which changes are exported, who reviews the diff and how production is updated.
- Walk your developers through the workflow on a real change so they can repeat it without us.
How we support
- Engagements are fixed-scope, and ongoing support, upgrades and continuous testing are available as a retainer.
- Review new customizations before they merge, so fixtures do not drift from what the app expects after ERPNext upgrades.
- Confirm with your team and your ERPNext version which upgrade notes apply before each minor or major update.

