
Frappe Ops Upgrades
- 23 installs
- 159 repo stars
- Updated July 8, 2026
- openaec-foundation/frappe_claude_skill_package
Helps with ai & agent building tasks.
About
frappe-ops-upgrades is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- frappe-ops-upgrades
- AI & Agent Building
- AI-coding skill
Frappe Ops Upgrades by the numbers
- 23 all-time installs (skills.sh)
- Ranked #10,032 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/frappe_claude_skill_package --skill frappe-ops-upgradesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 23 |
|---|---|
| repo stars | ★ 159 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/frappe_claude_skill_package ↗ |
What it does
Helps with ai & agent building tasks.
Files
Version Upgrades
Complete guide for upgrading Frappe/ERPNext between major versions, handling failed migrations, and rolling back safely.
Versions: v14 → v15 → v16
---
Quick Reference: Upgrade Commands
| Task | Command |
|---|---|
| Full update | bench update |
| Update specific app | bench update --pull --app erpnext |
| Switch branch | bench switch-to-branch version-15 frappe erpnext |
| Run migrations only | bench --site mysite migrate |
| Check migration readiness | bench --site mysite ready-for-migration |
| Backup before upgrade | bench --site mysite backup |
| Restore from backup | bench --site mysite restore /path/to/backup.sql.gz |
| Re-run failed patch | Add #YYYY-MM-DD suffix in patches.txt |
---
Decision Tree: Upgrade Strategy
Need to upgrade?
├── Single minor version bump (e.g., v15.10 → v15.20)?
│ └── YES → Run `bench update` directly
├── Major version jump (e.g., v14 → v15)?
│ ├── Have custom apps?
│ │ ├── YES → Test on staging FIRST, check breaking changes
│ │ └── NO → Follow standard upgrade path
│ └── Multiple major versions (v14 → v16)?
│ └── ALWAYS upgrade one version at a time: v14 → v15 → v16
└── Production environment?
├── YES → ALWAYS test on staging clone first
└── NO → Proceed with standard upgrade---
Pre-Upgrade Checklist
ALWAYS complete these steps before ANY major version upgrade:
1. Full backup — bench --site mysite backup --with-files 2. Test on staging — Clone production to a staging bench and test there first 3. Check breaking changes — Review the breaking changes section below 4. Audit custom apps — Run custom apps against new version's API changes 5. Check Python/Node versions — v15 requires Node 18+; v16 requires Node 24+, Python 3.14+ 6. Disable scheduler — bench --site mysite scheduler disable 7. Check pending jobs — bench --site mysite ready-for-migration 8. Read release notes — Check GitHub release notes for each version
---
Standard Upgrade Process
Step-by-Step
# 1. Backup all sites
bench backup-all-sites
# 2. Switch to target version branch
bench switch-to-branch version-15 frappe erpnext
# 3. Update (pulls code, installs deps, builds, migrates)
bench update
# 4. Verify
bench --site mysite migrate # if not done by update
bench version # confirm versionsWhat bench update Executes (In Order)
1. Backup all sites 2. Pull latest code for all apps (git pull) 3. Install Python requirements (pip install) 4. Install Node requirements (yarn install) 5. Build static assets (bench build) 6. Run migrations on all sites (bench migrate) 7. Restart bench processes
---
v14 → v15 Breaking Changes
Environment Requirements
| Requirement | v14 | v15 |
|---|---|---|
| Node.js | v14+ | v18+ |
| Python packaging | setup.py | pyproject.toml |
Backend Breaking Changes
- `db.set()` removed — Use
doc.db_set()instead - `db.sql()` parameters removed —
as_utf8andformattedno longer accepted - `db.set_value()` for Singles — Use
frappe.db.set_single_value()instead - `job_name` deprecated — Use
job_idparameter inenqueue() - `frappe.new_doc()` arguments —
parent_doc,parentfield,as_dictMUST be keyword args - `frappe.get_installed_apps()` — No longer accepts
sortorfrappe_lastargs - Method override order reversed — Last override now takes precedence
- Timezone functions renamed —
convert_utc_to_user_timezone→convert_utc_to_system_timezone
Frontend Breaking Changes
- Vue 2 → Vue 3 — All Vue components MUST be migrated
- Window globals removed —
get_today→frappe.datetime.get_today,user→frappe.session.user - `this` in Client Scripts — Local scope access no longer supported
- Image lazy loading — Replace
website-image-lazyclass with nativeloading="lazy"
Security Changes
- Server Scripts disabled by default — Enable:
bench set-config -g server_script_enabled 1 - "Desk User" role added — Replaces "All" role for desk user permissions
- `currentsite.txt` removed — Use
bench use sitenameorFRAPPE_SITEenv var
Removed Features
- Event Streaming moved to separate app
- Cordova support removed
setup.pyremoved (usepyproject.toml)--make_copyand--restorebuild flags removed (use--hard-link)
See breaking-changes.md for the complete list.
---
v15 → v16 Breaking Changes
Environment Requirements
| Requirement | v15 | v16 |
|---|---|---|
| Node.js | v18+ | v24+ |
| Python | 3.10+ | 3.14+ |
Backend Breaking Changes
- Default sort order changed —
creationinstead ofmodifiedfor all list queries - `has_permission` hooks — MUST return explicit
True;Noneno longer accepted - `frappe.get_doc(doctype, name, field=value)` — No longer updates values
- DB commits in document hooks — No longer allowed to prevent data integrity issues
- `frappe.sendmail(now=True)` — No longer commits transactions implicitly
- `db.get_value()` for Singles — Now returns proper types instead of strings
- State-changing methods require POST —
/api/method/logout,/api/method/upload_file, etc.
Separated Modules (Install Separately)
- Energy Points →
frappe/eps - Newsletter →
frappe/newsletter - Backup Integrations →
frappe/offsite_backups - Blog →
frappe/blog
Frontend Breaking Changes
- Report/Dashboard/Page JS evaluated as IIFEs (no global scope pollution)
- Awesome Bar redesigned, moved to sidebar (
Cmd+K) - List view right sidebar removed
/appsendpoint deprecated;/appreroutes to/desk
Configuration Changes
- Site config cached for up to one minute (changes not immediate)
- Country field requires valid ISO 3166 ALPHA-2 code
bench versionoutput format changed to "plain" (use-f legacyfor old format)override_doctypehook classes MUST inherit from the overridden class
See breaking-changes.md for the complete list.
---
Patch System
How Patches Work
Patches are one-off data migration scripts that run during bench migrate. They are defined in each app's patches.txt file.
patches.txt Format [v14+]
[pre_model_sync]
# Runs BEFORE schema sync — use for data prep
myapp.patches.v15_0.prepare_data_for_migration
[post_model_sync]
# Runs AFTER schema sync — use for data that needs new schema
myapp.patches.v15_0.migrate_data_to_new_fieldsPatch Execution Rules
- Patches run in the order defined in
patches.txt - Each patch runs exactly ONCE — tracked in the
__patchestable - To re-run a patch, append a date comment:
myapp.patches.v15_0.fix #2025-03-20 - One-off statements:
execute:frappe.delete_doc('Page', 'old_page', ignore_missing=True)
Writing a Patch
# myapp/patches/v15_0/migrate_field_data.py
import frappe
def execute():
# ALWAYS reload if you need the NEW schema
frappe.reload_doc("module_name", "doctype", "doctype_name")
# Perform data migration
frappe.db.sql("""
UPDATE `tabSales Invoice`
SET new_field = old_field
WHERE old_field IS NOT NULL
""")Debugging Stuck Patches
# Check which patches have run
bench --site mysite console
>>> frappe.db.sql("SELECT * FROM __patches WHERE patch LIKE '%stuck_patch%'")
# Remove a patch record to force re-run
>>> frappe.db.sql("DELETE FROM __patches WHERE patch = 'myapp.patches.v15_0.broken_patch'")
>>> frappe.db.commit()
# Then re-run migrate
bench --site mysite migrate---
Rollback Procedure
Immediate Rollback (Within Hours)
# 1. Stop all processes
bench stop
# 2. Restore database from pre-upgrade backup
bench --site mysite restore /path/to/pre-upgrade-backup.sql.gz \
--with-public-files /path/to/files.tar \
--with-private-files /path/to/private-files.tar
# 3. Switch back to previous version branch
bench switch-to-branch version-14 frappe erpnext
# 4. Install old dependencies
bench setup requirements
# 5. Build old assets
bench build
# 6. Start bench
bench start # or: sudo bench restart (production)Critical Rules for Rollback
- ALWAYS keep pre-upgrade backups for at least 7 days
- NEVER run
bench migrateafter restoring to old branch — schema is already correct - ALWAYS restore files alongside database — file references may break otherwise
- NEVER attempt rollback after users have created new data on the upgraded version
---
Frappe Packages: Moving Customizations Between Sites
Frappe Packages (v14+) are lightweight UI-built applications — bundles of Custom Module Defs distributed as .tar.gz tarballs. For Custom Fields, Property Setters, and DocPerms on standard DocTypes, use Fixtures instead.
Quick Reference: Package vs Fixtures vs App
| Mechanism | Use When | CLI Command |
|---|---|---|
| Package | UI-built DocTypes, Scripts, Web Pages | UI only (Package Import/Release) |
| Fixtures | Custom Fields, Property Setters, DocPerms | bench --site mysite export-fixtures |
| Frappe App | Full development workflow, CI/CD, tests | bench get-app, bench install-app |
Package Workflow (UI-Based)
1. Create a Package document → assign Custom Module Defs to it 2. Create a Package Release → exports to [bench]/sites/[site]/packages/ as [package]-[version].tar.gz 3. On target site, create Package Import → attach tarball, check Activate 4. System migrates data like an app migration; use Force to overwrite existing files
Fixtures Workflow (CLI-Based)
# hooks.py — define what to export
fixtures = [
"Custom Field",
"Property Setter",
{"dt": "Client Script", "filters": [["module", "=", "My Module"]]}
]# Export fixtures to JSON in your app
bench --site mysite export-fixtures --app myapp
# Fixtures auto-sync on: bench --site mysite migrateNEVER use Packages to modify standard/core DocTypes — use a Frappe App with Fixtures.
See frappe-packages.md for the complete reference including decision trees, limitations, and best practices.
---
Custom App Compatibility Checks
Before upgrading, audit each custom app:
1. Check deprecated APIs — Search for removed functions listed in breaking changes 2. Check `setup.py` — Must migrate to pyproject.toml for v15+ 3. Check Vue components — Must be Vue 3 compatible for v15+ 4. Check `patches.txt` — Ensure patches use [pre_model_sync]/[post_model_sync] sections [v14+] 5. Check hooks.py — Verify no removed hooks are used 6. Run tests — bench --site test_site run-tests --app myapp
---
Decision Tree: In-Place vs Fresh Install
Choosing upgrade strategy:
├── Small site (< 10 GB database)?
│ └── In-place upgrade is usually fine
├── Large site (> 50 GB database)?
│ ├── Many custom apps? → Fresh install + data migration
│ └── Standard apps only? → In-place with extended downtime window
├── Skipping multiple versions (v13 → v15)?
│ └── ALWAYS fresh install — sequential upgrades are too risky
└── Critical production with zero-downtime requirement?
└── Fresh install on parallel server + DNS switch---
Version Differences Summary
| Feature | v14 | v15 | v16 |
|---|---|---|---|
| Python packaging | setup.py | pyproject.toml | pyproject.toml |
| Vue version | Vue 2 | Vue 3 | Vue 3 |
| Node.js minimum | v14 | v18 | v24 |
| Python minimum | 3.8 | 3.10 | 3.14 |
| Server Scripts | Enabled | Disabled default | Disabled default |
| Default sort | modified | modified | creation |
| patches.txt sections | Yes | Yes | Yes |
| Workspace sidebar | No | No | Yes |
| Separated modules | — | Event Streaming | Blog, Newsletter, EPS |
---
Reference Files
| File | Contents |
|---|---|
| examples.md | Complete upgrade workflow examples |
| anti-patterns.md | Common upgrade mistakes and fixes |
| breaking-changes.md | Detailed breaking changes per version |
| frappe-packages.md | Packages, fixtures, and moving customizations between sites |
Upgrade Anti-Patterns
Anti-Pattern 1: Skipping Multiple Major Versions
WRONG: Upgrading directly from v13 → v15Why it breaks: Each major version has patches that depend on the previous version's schema. Skipping versions causes missing patches and schema mismatches.
Correct approach: ALWAYS upgrade one major version at a time: v13 → v14 → v15 → v16.
---
Anti-Pattern 2: Upgrading Production Without Staging Test
WRONG: bench switch-to-branch version-15 frappe erpnext # on production
bench updateWhy it breaks: Custom apps may use deprecated APIs, custom scripts may fail, data patches may encounter unexpected data states.
Correct approach: ALWAYS clone production to staging, test the full upgrade there, then apply to production.
---
Anti-Pattern 3: Forgetting to Enable Server Scripts After v15
WRONG: Upgrade to v15, wonder why Server Scripts stopped workingWhy it breaks: v15 disables Server Scripts by default for security.
Correct approach: After v15 upgrade, explicitly enable if needed:
bench set-config -g server_script_enabled 1---
Anti-Pattern 4: Not Checking Python/Node Versions
WRONG: Upgrade to v16 while running Node 18 and Python 3.10Why it breaks: v16 requires Node 24+ and Python 3.14+. Build and runtime failures occur.
Correct approach: ALWAYS upgrade system dependencies BEFORE switching app branches.
---
Anti-Pattern 5: Running bench migrate After Rollback
WRONG: Restore old backup → bench migrateWhy it breaks: The restored database already has the correct schema for the old version. Running migrate with new code expectations corrupts data.
Correct approach: After restoring a backup and switching to the old branch, run bench build and bench setup requirements only. NEVER run bench migrate.
---
Anti-Pattern 6: Ignoring patches.txt Section Order
WRONG: Putting schema-dependent patches in [pre_model_sync]Why it breaks: [pre_model_sync] patches run BEFORE schema sync. If your patch references new fields, they do not exist yet.
Correct approach: Use [pre_model_sync] for data preparation and [post_model_sync] for data that needs the new schema.
---
Anti-Pattern 7: Not Backing Up Files With Database
WRONG: bench --site mysite backup # database onlyWhy it breaks: File references in the database point to files on disk. Restoring database without files creates broken links.
Correct approach: ALWAYS use --with-files for pre-upgrade backups:
bench --site mysite backup --with-files---
Anti-Pattern 8: Using bench update --reset in Production
WRONG: bench update --reset # on production with local patchesWhy it breaks: --reset runs git reset --hard, destroying ALL local changes including custom patches and hotfixes.
Correct approach: NEVER use --reset in production. Resolve git conflicts manually or maintain changes in a proper branch.
---
Anti-Pattern 9: Not Reloading DocType in Patches
# WRONG:
def execute():
# Trying to use new field without reload
for doc in frappe.get_all("Sales Invoice", fields=["new_field"]):
pass # Fails: new_field doesn't exist in old schemaCorrect approach: ALWAYS reload the DocType before accessing new fields:
def execute():
frappe.reload_doc("accounts", "doctype", "sales_invoice")
for doc in frappe.get_all("Sales Invoice", fields=["new_field"]):
pass---
Anti-Pattern 10: Upgrading Without Checking Custom App Compatibility
WRONG: Switch branch → bench update → discover custom app breaksWhy it breaks: Custom apps may use deprecated APIs, removed hooks, or incompatible Vue 2 components.
Correct approach: ALWAYS audit custom apps BEFORE switching branches. Search for deprecated APIs listed in breaking changes, fix them, commit, then upgrade.
Breaking Changes Reference
v14 → v15 Complete Breaking Changes
Removed Python APIs
| Removed | Replacement |
|---|---|
frappe.db.set() | doc.db_set() |
frappe.db.touch() | No replacement needed |
frappe.db.clear_table() | Direct SQL or custom logic |
frappe.db.update() | frappe.db.set_value() |
frappe.db.set_temp() / get_temp() | Use frappe.cache |
frappe.db.sql(as_utf8=True) | Remove parameter |
frappe.db.sql(formatted=True) | Remove parameter |
frappe.db.set_value() for Singles | frappe.db.set_single_value() |
frappe.db.set_value(for_update=True) | Remove parameter |
frappe.compare() | from frappe.utils import compare |
frappe.local.rollback_observers | DB transaction hooks |
frappe.db.add_before_commit | DB transaction hooks |
Changed Python APIs
| API | Change |
|---|---|
frappe.new_doc() | parent_doc, parentfield, as_dict MUST be kwargs |
frappe.get_installed_apps() | sort, frappe_last args removed |
enqueue(job_name=...) | Use job_id parameter instead |
get_year_ending() | Returns datetime.date instead of string |
get_timespan_date_range() | Returns datetime.date tuples |
convert_utc_to_user_timezone() | Renamed to convert_utc_to_system_timezone() |
get_time_zone() | Renamed to get_system_timezone() |
validate_from_to_dates() | Skips validation if either date is empty |
| Method override execution | Last override takes precedence (was first) |
search_link() / search_widget() | Returns message key instead of custom keys |
Removed Frontend APIs
| Removed | Replacement |
|---|---|
window.get_today | frappe.datetime.get_today |
window.show_alert | frappe.show_alert |
window.user | frappe.session.user |
window.roles | frappe.user_roles |
| Query report globals | frappe.query_report.get_filter_value() |
this in Client Scripts | Not supported; use cur_frm |
<div class="website-image-lazy"> | <img loading="lazy"> |
Vue Migration (v2 → v3)
| Package | v14 Version | v15 Version |
|---|---|---|
| Vue | 2.x | 3.x |
| Vuex | 3.x | 4.0.2 |
| vue-router | 2.x | 4.1.5 |
| vuedraggable | 2.24.3 | 4.1.0 |
Configuration Changes
| Change | Details |
|---|---|
| Server Scripts | Disabled by default; enable with server_script_enabled |
| "Desk User" role | New catch-all role for desk users |
currentsite.txt | No longer sets default site |
| Node.js minimum | v18 (was v14) |
| Python packaging | pyproject.toml (was setup.py) |
| Build flags | --make_copy, --restore removed; use --hard-link |
| SocketIO | Requires namespacing: io('${url}/${frappe.local.site}') |
Removed Features
- Event Streaming (moved to separate app)
- Cordova support
/fixtures/custom_scriptsimport- "Error Snapshot" DocType (use Error Log)
- Session device differentiation
Removed Python Dependencies
No longer available for indirect import: googlemaps, urllib3, gitdb, pyasn1, pypng, google-auth-httplib2, schedule, pycryptodome.
---
v15 → v16 Complete Breaking Changes
Removed/Separated Modules
| Module | New App Repository |
|---|---|
| Energy Points | frappe/eps |
| Newsletter | frappe/newsletter |
| Backup Integrations | frappe/offsite_backups |
| Blog | frappe/blog |
Changed Python APIs
| API | Change |
|---|---|
| Default sort order | creation instead of modified for all list queries |
has_permission hooks | MUST return explicit True; None not accepted |
frappe.get_doc(dt, name, field=val) | No longer updates values |
frappe.sendmail(now=True) | No longer commits transactions |
db.get_value() for Singles | Returns proper types (not strings) |
db.value_cache | Changed to nested defaultdict |
db.count(cache=True) | Uses transaction cache instead of Redis |
!=None condition | Correctly generates IS NOT NULL |
meta.get_valid_columns() | Excludes virtual fields |
frappe.flags.in_test | Deprecated; use frappe.in_test |
site_cache decorator | No longer supports unhashable args (dicts) |
| Document hooks | DB commits not allowed (data integrity) |
| State-changing methods | MUST use POST (/api/method/logout, etc.) |
override_doctype classes | MUST inherit from overridden class |
Removed Translation APIs
| Removed | Details |
|---|---|
get_translated_dict hook | Removed entirely |
frappe.get_lang_dict() | Removed |
doc.meta.__messages | No longer holds doc-specific translations |
| Currency/timezone translations | No longer translated |
Frontend Changes
| Change | Details |
|---|---|
| Report/Dashboard/Page JS | Evaluated as IIFEs (no global scope) |
| Awesome Bar | Redesigned, moved to sidebar (Cmd+K) |
| List view sidebar | Removed; filters moved to top |
| Map view | Paginated (20 items, not all) |
/apps endpoint | Deprecated; /app → /desk |
Configuration Changes
| Change | Details |
|---|---|
| Site config caching | Cached up to 1 minute |
| Country field | Requires ISO 3166 ALPHA-2 code |
| Time field default | Removed; only "Now" option sets current time |
bench version output | "plain" format default; use -f legacy |
| System Console | Restricted to Administrator |
Removed DocTypes
- Transaction Log (was France/Germany compliance)
Removed Features
- GeoIP MaxMind database features
Upgrade Examples
Example 1: Standard v14 → v15 Upgrade on Staging
# Clone production to staging
bench --site production.example.com backup --with-files
bench new-site staging.example.com --admin-password admin
bench --site staging.example.com restore \
/path/to/production-database.sql.gz \
--with-public-files /path/to/files.tar \
--with-private-files /path/to/private-files.tar
# Switch to v15
bench switch-to-branch version-15 frappe erpnext
# Update (installs deps, builds, migrates)
bench update
# Verify key functionality
bench --site staging.example.com console
>>> frappe.get_doc("Company", frappe.defaults.get_defaults().company)
>>> frappe.db.count("Sales Invoice")
# Check for errors in logs
tail -f logs/frappe.logExample 2: Upgrading with Custom Apps
# 1. Backup
bench --site mysite backup --with-files
# 2. Check custom app compatibility FIRST
cd apps/custom_app
# Search for removed APIs
grep -r "db\.set(" . --include="*.py"
grep -r "as_utf8" . --include="*.py"
grep -r "frappe.compare(" . --include="*.py"
# 3. Fix deprecated code in custom app BEFORE switching branches
# Example: db.set() → doc.db_set()
# Example: frappe.db.set_value("Single", None, ...) → frappe.db.set_single_value(...)
# 4. Commit custom app fixes
cd apps/custom_app
git add -A && git commit -m "fix: migrate deprecated APIs for v15 compatibility"
# 5. Switch Frappe/ERPNext to v15
bench switch-to-branch version-15 frappe erpnext
# 6. Update
bench updateExample 3: Enabling Server Scripts After v15 Upgrade
# Server Scripts are disabled by default in v15+
# If your site uses Server Scripts, Script Reports, or System Console:
bench set-config -g server_script_enabled 1
# Restart to apply
bench restartExample 4: Writing a Data Migration Patch
# myapp/patches/v15_0/migrate_old_field_to_new.py
import frappe
def execute():
# Reload DocType to get new schema
frappe.reload_doc("selling", "doctype", "sales_order")
# Migrate data from old field to new field
frappe.db.sql("""
UPDATE `tabSales Order`
SET new_status_field = CASE
WHEN old_status = 'Open' THEN 'Draft'
WHEN old_status = 'Closed' THEN 'Completed'
ELSE old_status
END
WHERE old_status IS NOT NULL
""")
frappe.db.commit()Add to patches.txt:
[post_model_sync]
myapp.patches.v15_0.migrate_old_field_to_newExample 5: Full Rollback After Failed v15 Upgrade
# 1. Stop everything
bench stop
# 2. Check which backup to restore
ls sites/mysite/private/backups/
# 3. Restore pre-upgrade backup
bench --site mysite restore \
sites/mysite/private/backups/20250320_120000-mysite-database.sql.gz \
--with-public-files sites/mysite/private/backups/20250320_120000-mysite-files.tar \
--with-private-files sites/mysite/private/backups/20250320_120000-mysite-private-files.tar
# 4. Switch back to v14
bench switch-to-branch version-14 frappe erpnext
# 5. Reinstall v14 dependencies
bench setup requirements
# 6. Rebuild v14 assets
bench build
# 7. Start
bench startExample 6: Handling Stuck Patch During Migration
# Migration fails with: "PatchError: myapp.patches.v15_0.broken_patch"
# Check the error log
tail -50 logs/frappe.log
# Option A: Fix the patch and re-run
# Edit the patch file, then:
bench --site mysite migrate
# Option B: Skip the patch (if safe to skip)
bench --site mysite console
>>> frappe.db.sql("INSERT INTO __patches (patch) VALUES ('myapp.patches.v15_0.broken_patch')")
>>> frappe.db.commit()
# Then re-run migrate to continue with remaining patches
bench --site mysite migrate
# Option C: Force re-run a previously executed patch
# In patches.txt, add date suffix:
# myapp.patches.v15_0.broken_patch #2025-03-20
bench --site mysite migrateExample 7: v15 → v16 Upgrade — Handling Sort Order Change
# v16 changes default sort from 'modified' to 'creation'
# If your code relies on modified-first ordering, update explicitly:
# BEFORE (worked in v15, breaks in v16):
items = frappe.get_all("Sales Invoice", limit=10)
# Returns sorted by 'creation' in v16, was 'modified' in v15
# AFTER (explicit, works in both):
items = frappe.get_all("Sales Invoice", order_by="modified desc", limit=10)Example 8: Installing Separated Modules After v16 Upgrade
# v16 separates Blog, Newsletter, Energy Points, Backup Integrations
# If you used these features, install them as separate apps:
bench get-app https://github.com/frappe/blog.git
bench --site mysite install-app blog
bench get-app https://github.com/frappe/newsletter.git
bench --site mysite install-app newsletter
bench get-app https://github.com/frappe/eps.git
bench --site mysite install-app eps
bench get-app https://github.com/frappe/offsite_backups.git
bench --site mysite install-app offsite_backupsFrappe Packages — Complete Reference
What Are Frappe Packages?
Frappe Packages (v14+) are lightweight applications built entirely from the Framework UI — no command-line app scaffolding required. A Package is a collection of Module Defs bundled together for distribution as a gzipped tarball. Packages can be imported into other Frappe sites, functioning like mini-apps.
Key distinction: Packages are NOT the same as Frappe Apps. They are UI-created bundles of customizations and modules, designed for portability without requiring a full app structure (setup.py/pyproject.toml, GitHub repo, etc.).
---
What Can Be Packaged
A Package contains Module Defs — any module marked as "Custom" can be assigned to a Package. This includes:
| Component | Packageable | Notes |
|---|---|---|
| Custom DocTypes | Yes | DocTypes created via UI |
| Server Scripts | Yes | API, Document Event, Permission Query |
| Client Scripts | Yes | Via Custom Module Def |
| Web Pages | Yes | Built in Framework UI |
| Reports | Yes | Script Reports, Query Reports |
| Print Formats | Yes | Custom print templates |
| Dashboards | Yes | Dashboard charts and shortcuts |
| Workflows | Yes | Workflow definitions and actions |
| Workspace | Yes | Custom workspace layouts |
| Notification | Yes | Email/system notifications |
What CANNOT Be Packaged Directly
- Custom Fields on standard DocTypes — use Fixtures instead (see below)
- Property Setters — use Fixtures
- Custom DocPerms — use Fixtures
- Standard/core DocType modifications — these require a proper Frappe App
---
Package Lifecycle
1. Create a Package
Create a Package document in the Frappe UI:
- Set package name, publisher, README, LICENSE
- The Package is designed to be pushed to a git repository
2. Assign Modules to the Package
For each Custom Module Def you want to include:
- Open the Module Def
- Set the Package field to your package name
- ONLY "Custom" type Module Defs can be assigned to a Package
3. Create a Package Release
Create a Package Release document:
- The system exports all associated modules to
[bench]/sites/[site]/packages/ - Bundles the directory into
[package]-[version].tar.gz - Download the tarball for distribution
4. Import on Target Site
On the target site, create a Package Import document:
- Attach the
.tar.gzfile - Check Activate to extract into
[bench]/sites/[sitename]/packages/ - The system runs migrations (like an app migration)
- Force option: overwrites existing files; otherwise unchanged files are skipped
- A log records the operation output
---
Fixtures: Exporting Customizations Without a Package
For exporting Custom Fields, Property Setters, Custom DocPerms, and other record-level customizations, use the Fixtures mechanism via hooks.py in a Frappe App.
Define Fixtures in hooks.py
# Export ALL Custom Fields
fixtures = [
"Custom Field",
"Property Setter",
"Custom DocPerm",
"Client Script",
"Server Script",
"Workflow",
"Print Format",
]
# Export with filters (selective)
fixtures = [
{
"dt": "Custom Field",
"filters": [["module", "=", "My Custom Module"]]
},
{
"dt": "Property Setter",
"filters": [["module", "=", "My Custom Module"]]
},
{
"dt": "Client Script",
"filters": [["module", "=", "My Custom Module"]]
},
]Export and Import Commands
# Export fixtures to JSON files in your app
bench --site mysite export-fixtures --app myapp
# Fixtures auto-sync on install/update:
bench --site mysite install-app myapp
bench --site mysite migrate
# Import individual JSON documents
bench --site mysite import-doc /path/to/file.json
# Import all JSON files from a directory
bench --site mysite import-doc /path/to/directory/
# Export a single document as JSON
bench --site mysite export-json "Custom Field" "Sales Invoice-custom_field_name"Excluded Fields (Auto-stripped During Export)
These system fields are automatically excluded from fixture exports:
modified_by,creation,owner,idx,lft,rgt- Child table fields:
docstatus,doctype,modified,name
---
Decision Tree: Package vs App vs Fixtures
Need to move customizations between sites?
├── Only Custom Fields / Property Setters / DocPerms?
│ └── Use FIXTURES in a Frappe App's hooks.py
├── Custom DocTypes + Server Scripts + Web Pages (all built in UI)?
│ ├── Need a full development workflow (CI/CD, tests, versioning)?
│ │ └── Use a FRAPPE APP
│ └── Quick distribution without app scaffolding?
│ └── Use a FRAPPE PACKAGE
├── Modifying core/standard DocTypes?
│ └── ALWAYS use a FRAPPE APP (Packages cannot modify standard DocTypes)
├── Need pip-installable distribution?
│ └── Use a FRAPPE APP
└── One-off data transfer between staging and production?
└── Use FIXTURES with export-fixtures / import-doc---
Limitations
1. Not a replacement for proper apps — Packages lack test infrastructure, CI/CD integration, pip packaging, and proper version management 2. No version pinning — Packages do not declare Frappe version dependencies; compatibility is the publisher's responsibility 3. Custom Module Defs only — You cannot package modifications to standard/core DocTypes 4. No dependency resolution — Packages cannot declare dependencies on other packages or apps 5. No rollback mechanism — Importing a package is a one-way operation; ALWAYS backup before importing 6. No CLI commands — Unlike apps, there is no bench get-package or bench install-package; everything goes through the UI (Package Import document) 7. Limited migration control — Package imports run migrations automatically; you cannot control patch execution order
---
Best Practices
- ALWAYS backup the target site before importing a package
- ALWAYS test package imports on a staging site first
- NEVER use Packages for mission-critical production customizations — use a proper Frappe App instead
- ALWAYS use Fixtures (not Packages) for Custom Fields, Property Setters, and DocPerms on standard DocTypes
- ALWAYS include a README and LICENSE in your Package
- Use the Force option only when you explicitly want to overwrite existing files
- Keep Package Releases versioned — use semantic versioning in release names