# frappe-migration > Create Frappe database patches and migrations. Use when creating migrations, patches, data transformations, schema changes, or when user mentions database migration, data update, or patch. - Author: devanshughosh - Repository: DarthV98/12thPassClassroom - Version: 20260125222424 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-06 - Source: https://github.com/DarthV98/12thPassClassroom - Web: https://mule.run/skillshub/@@DarthV98/12thPassClassroom~frappe-migration:20260125222424 --- --- name: frappe-migration description: Create Frappe database patches and migrations. Use when creating migrations, patches, data transformations, schema changes, or when user mentions database migration, data update, or patch. --- # Frappe Migration/Patch Development ## Patch Location Patches are in `lms/patches/v{version}/` (e.g., `lms/patches/v2_0/`). ## Creating a Patch 1. Create patch file: `lms/patches/v2_0/{patch_name}.py` 2. Register in `lms/patches.txt` ## Patch Registration Add to `lms/patches.txt`: ``` [post_model_sync] lms.patches.v2_0.my_patch_name #DD-MM-YYYY ``` Two sections: - `[pre_model_sync]` - Runs before DocType schema changes - `[post_model_sync]` - Runs after DocType schema changes (most common) ## Basic Patch Pattern ```python import frappe def execute(): # Check if migration is needed (idempotency) if not frappe.db.exists("Custom Field", {"dt": "User", "fieldname": "my_field"}): return # Perform migration records = frappe.get_all("User", {"my_field": "old_value"}, ["name"]) for user in records: frappe.db.set_value("User", user.name, "my_field", "new_value") ``` ## Common Patch Patterns ### Field Value Update ```python import frappe def execute(): # Update single field value for matching records frappe.db.sql(""" UPDATE `tabLMS Course` SET status = 'Published' WHERE published = 1 AND status = 'Draft' """) ``` ### Data Transformation ```python import frappe def execute(): # Get records needing transformation records = frappe.get_all( "LMS Quiz Submission", filters={"result": ["is", "not set"]}, fields=["name", "score", "passing_score"] ) for record in records: # Calculate new value result = "Pass" if record.score >= record.passing_score else "Fail" # Update frappe.db.set_value( "LMS Quiz Submission", record.name, "result", result ) ``` ### Batch Data Migration ```python import frappe def execute(): # Migrate data from old to new structure old_records = frappe.get_all( "Old DocType", fields=["name", "field1", "field2", "user"] ) for record in old_records: # Check if already migrated if frappe.db.exists("New DocType", {"old_reference": record.name}): continue # Create new record frappe.get_doc({ "doctype": "New DocType", "old_reference": record.name, "new_field": record.field1, "user": record.user }).insert(ignore_permissions=True) ``` ### Delete Old Data ```python import frappe def execute(): # Delete obsolete records frappe.db.delete("Old DocType", {"status": "Archived"}) # Or delete specific document if frappe.db.exists("Custom Field", "User-old_field"): frappe.delete_doc("Custom Field", "User-old_field", force=True) ``` ### Update Permissions ```python import frappe def execute(): # Reset permissions for a doctype frappe.permissions.reset_perms("LMS Course") ``` ### Execute Inline For simple operations, use inline execution in `patches.txt`: ``` execute:frappe.delete_doc("DocType", "Old DocType", ignore_missing=True, force=True) execute:frappe.permissions.reset_perms("LMS Course") ``` ## Idempotency Checks Always make patches idempotent (safe to run multiple times): ```python def execute(): # Check field exists before updating if not frappe.db.has_column("LMS Course", "new_field"): return # Check if already migrated already_done = frappe.db.get_value( "LMS Settings", None, "migration_completed" ) if already_done: return # Do migration... # Mark as done frappe.db.set_single_value("LMS Settings", "migration_completed", 1) ``` ## SQL Patterns ```python # Direct SQL update frappe.db.sql(""" UPDATE `tabLMS Course` SET category = 'General' WHERE category IS NULL """) # Select with SQL results = frappe.db.sql(""" SELECT name, title FROM `tabLMS Course` WHERE published = 1 """, as_dict=True) # Parameterized query frappe.db.sql(""" UPDATE `tabUser` SET status = %s WHERE role = %s """, ("Active", "Student")) ``` ## Error Handling ```python def execute(): try: # Migration logic pass except Exception as e: frappe.log_error( f"Migration failed: {str(e)}", "Patch Error: my_patch_name" ) raise ``` ## Running Patches ```bash # Run all pending patches bench --site migrate # Run specific patch manually (for testing) bench --site run-patch lms.patches.v2_0.my_patch_name ``` ## Large Data Migrations For large datasets, use batching: ```python import frappe def execute(): batch_size = 1000 offset = 0 while True: records = frappe.db.sql(""" SELECT name, field FROM `tabLarge Table` WHERE needs_migration = 1 LIMIT %s OFFSET %s """, (batch_size, offset), as_dict=True) if not records: break for record in records: frappe.db.set_value( "Large Table", record.name, "migrated_field", transform(record.field) ) # Commit each batch frappe.db.commit() offset += batch_size ```