Skip to main content
Fixtures are a powerful mechanism in Frappe/ERPNext for version controlling master data and configurations. They allow you to package data with your app that will be automatically synchronized across installations.

What Are Fixtures?

Fixtures are JSON representations of DocType records that are:
  • Stored in your app’s codebase
  • Version controlled with Git
  • Automatically synced during bench migrate
  • Used for non-transactional, configuration data

Common Use Cases

  • Custom Fields
  • Property Setters
  • Custom DocTypes and their metadata
  • Workflow definitions
  • Print Formats
  • Email Templates
  • Default master data (Item Groups, Customer Groups, etc.)

Defining Fixtures

In hooks.py

Define fixtures in your app’s hooks.py file:
# custom_app/hooks.py

fixtures = [
    # Export all records of a DocType
    "Custom Field",
    "Property Setter",
    
    # Export specific records using filters
    {
        "dt": "Custom Field",
        "filters": [
            ["name", "in", [
                "Sales Invoice-custom_reference_no",
                "Customer-custom_external_id",
                "Item-custom_manufacturer_code"
            ]]
        ]
    },
    
    # Export with more complex filters
    {
        "dt": "Workflow",
        "filters": [
            ["document_type", "=", "Purchase Order"]
        ]
    },
    
    # Export print formats
    {
        "dt": "Print Format",
        "filters": [
            ["standard", "=", "No"],
            ["module", "=", "Custom App"]
        ]
    }
]

Exporting Fixtures

1
Step 1: Create Your Customizations
2
Create the records you want to export through the ERPNext UI:
3
  • Add Custom Fields via Customize Form
  • Create Workflows, Print Formats, etc.
  • Configure Property Setters
  • 4
    Step 2: Define Fixture Filters
    5
    Add appropriate fixture definitions to hooks.py:
    6
    fixtures = [
        {
            "dt": "Custom Field",
            "filters": [
                ["dt", "in", ["Sales Invoice", "Purchase Invoice", "Customer"]]
            ]
        }
    ]
    
    7
    Step 3: Export to JSON
    8
    Run the export command:
    9
    bench --site sitename export-fixtures
    
    10
    This creates JSON files in your app’s fixtures directory.
    Fixtures are exported to the fixtures folder in your app directory. The framework automatically syncs these files during bench migrate.

    Real-World Examples from ERPNext

    Example 1: Custom Fields for Regional Compliance

    From ERPNext’s UAE localization:
    # erpnext/regional/united_arab_emirates/setup.py
    
    def make_custom_fields():
        is_zero_rated = dict(
            fieldname="is_zero_rated",
            label="Is Zero Rated",
            fieldtype="Check",
            fetch_from="item_code.is_zero_rated",
            fetch_if_empty=1,
            insert_after="description",
            print_hide=1,
        )
        
        is_exempt = dict(
            fieldname="is_exempt",
            label="Is Exempt",
            fieldtype="Check",
            fetch_from="item_code.is_exempt",
            insert_after="is_zero_rated",
            print_hide=1,
        )
        
        # Use create_custom_fields to create/update fields
        from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
        
        custom_fields = {
            "Sales Invoice Item": [is_zero_rated, is_exempt],
            "Purchase Invoice Item": [is_zero_rated, is_exempt]
        }
        
        create_custom_fields(custom_fields, update=True)
    

    Example 2: Property Setters

    fixtures = [
        {
            "dt": "Property Setter",
            "filters": [
                ["doc_type", "=", "Sales Invoice"],
                ["property", "=", "read_only"]
            ]
        }
    ]
    

    Example 3: Workflow Configuration

    fixtures = [
        # Export complete workflow
        {
            "dt": "Workflow",
            "filters": [["name", "=", "Purchase Order Approval"]]
        },
        # Also export workflow states and actions
        {
            "dt": "Workflow State",
            "filters": [["parent", "=", "Purchase Order Approval"]]
        },
        {
            "dt": "Workflow Action Master",
            "filters": [["workflow_name", "=", "Purchase Order Approval"]]
        }
    ]
    

    Creating Fixtures Programmatically

    Custom Field Creation

    # custom_app/setup.py
    import frappe
    from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
    
    def after_install():
        """Create custom fields after app installation"""
        make_custom_fields()
    
    def make_custom_fields():
        custom_fields = {
            "Sales Invoice": [
                dict(
                    fieldname="custom_payment_status",
                    label="Payment Status",
                    fieldtype="Select",
                    options="\nPending\nPartial\nPaid\nOverdue",
                    insert_after="status",
                    read_only=1
                ),
                dict(
                    fieldname="custom_external_reference",
                    label="External Reference",
                    fieldtype="Data",
                    insert_after="custom_payment_status",
                    unique=1
                )
            ],
            "Customer": [
                dict(
                    fieldname="custom_credit_rating",
                    label="Credit Rating",
                    fieldtype="Rating",
                    insert_after="customer_type"
                )
            ]
        }
        
        create_custom_fields(custom_fields, update=True)
    

    Fixture Sync Workflow

    1
    Development Phase
    2
  • Make changes in your development site
  • Export fixtures: bench --site dev.local export-fixtures
  • Commit fixture JSON files to Git
  • 3
    Deployment Phase
    4
  • Pull latest code: git pull
  • Run migrate: bench --site production.site migrate
  • Fixtures are automatically synced
  • Advanced Fixture Patterns

    Conditional Fixture Installation

    # custom_app/setup.py
    import frappe
    
    def after_install():
        """Install fixtures based on conditions"""
        country = frappe.db.get_single_value("System Settings", "country")
        
        if country == "United States":
            install_us_specific_fixtures()
        elif country == "United Kingdom":
            install_uk_specific_fixtures()
    
    def install_us_specific_fixtures():
        # Create US-specific custom fields
        custom_fields = {
            "Address": [
                dict(
                    fieldname="custom_state_code",
                    label="State Code",
                    fieldtype="Data",
                    insert_after="state"
                )
            ]
        }
        create_custom_fields(custom_fields)
    

    Master Data Fixtures

    fixtures = [
        # Export standard Item Groups
        {
            "dt": "Item Group",
            "filters": [
                ["name", "in", ["Products", "Raw Material", "Services"]]
            ]
        },
        # Export UOM data
        {
            "dt": "UOM",
            "filters": [
                ["enabled", "=", 1]
            ]
        },
        # Export custom tax templates
        {
            "dt": "Sales Taxes and Charges Template",
            "filters": [
                ["company", "=", ""]
            ]
        }
    ]
    

    ERPNext’s Built-in Fixtures

    ERPNext uses fixtures extensively for setup data:

    Installation Fixtures

    # erpnext/setup/setup_wizard/operations/install_fixtures.py
    
    def install(country=None):
        records = [
            # Address Template
            {"doctype": "Address Template", "country": country},
            
            # Item Groups
            {
                "doctype": "Item Group",
                "item_group_name": "All Item Groups",
                "is_group": 1,
                "parent_item_group": "",
            },
            {
                "doctype": "Item Group",
                "item_group_name": "Products",
                "is_group": 0,
                "parent_item_group": "All Item Groups",
                "show_in_website": 1,
            }
        ]
        
        from frappe.desk.page.setup_wizard.setup_wizard import make_records
        make_records(records)
    

    Best Practices

    1. Use Specific Filters

    fixtures = [
        {
            "dt": "Custom Field",
            "filters": [
                ["module", "=", "Custom App"],
                ["dt", "in", ["Sales Invoice", "Purchase Invoice"]]
            ]
        }
    ]
    

    2. Handle Fixture Dependencies

    Ensure parent records are exported before child records:
    fixtures = [
        "Workflow",  # Export workflow first
        "Workflow State",  # Then states
        "Workflow Action Master"  # Then actions
    ]
    

    3. Use Standard Fixtures for Config

    Store configuration in Single DocTypes and include as fixtures:
    fixtures = [
        {
            "dt": "Custom App Settings",
            "filters": [["name", "=", "Custom App Settings"]]
        }
    ]
    

    4. Test Fixture Installation

    Always test on a fresh site:
    # Create test site
    bench new-site test.local
    
    # Install app with fixtures
    bench --site test.local install-app custom_app
    
    # Verify fixtures were installed
    bench --site test.local console
    >>> frappe.get_all("Custom Field", {"module": "Custom App"})
    

    5. Document Fixture Changes

    Maintain a changelog for fixture updates:
    ## Changelog
    
    ### v2.1.0
    - Added custom fields for Sales Invoice: payment_gateway_id
    - Updated Property Setter for Customer: made tax_id mandatory
    
    ### v2.0.0
    - Added complete Purchase Order approval workflow
    - Created custom print format for Delivery Note
    

    Troubleshooting

    Fixtures Not Syncing

    If fixtures aren’t syncing during migrate, check:
    1. File Location: Ensure JSON files are in the fixtures folder
    2. hooks.py: Verify fixture definitions are correct
    3. Permissions: Check file permissions on fixture JSON files
    4. Cache: Clear cache with bench --site sitename clear-cache

    Handling Fixture Conflicts

    When multiple apps modify the same DocType:
    # Use update=True to merge changes
    from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
    
    custom_fields = {
        "Sales Invoice": [new_field_definition]
    }
    
    create_custom_fields(custom_fields, update=True)
    

    Fixture Export Errors

    # Check fixture definitions
    bench --site sitename export-fixtures --dry-run
    
    # Export with verbose output
    bench --site sitename export-fixtures --verbose
    

    Build docs developers (and LLMs) love