frappe-ops-upgrades
$
npx mdskill add Impertio-Studio/Frappe_Claude_Skill_Package/frappe-ops-upgradesExecute safe Frappe/ERPNext upgrades and recover from migration failures.
- Prevents broken upgrades from skipped patches and incompatible customizations.
- Integrates with bench update, migrate, and rollback commands.
- Analyzes version paths and breaking changes to guide execution.
- Delivers step-by-step commands for updates, migrations, and rollbacks.
SKILL.md
.github/skills/frappe-ops-upgradesView on GitHub ↗
---
name: frappe-ops-upgrades
description: >
Use when upgrading Frappe/ERPNext between major versions (v14 to v15, v15 to v16), troubleshooting failed migrations, or planning rollback.
Prevents broken upgrades from skipped patches, incompatible customizations, and missing pre-upgrade checks.
Covers version upgrade paths, bench update, migrate command, patch troubleshooting, rollback procedures, breaking changes per version.
Keywords: upgrade, migration, v14, v15, v16, bench update, bench migrate, rollback, patches, breaking changes, update failed, bench update error, migration error, patches failing, rollback after upgrade..
license: MIT
compatibility: "Claude Code, Claude.ai Projects, Claude API. Frappe v14-v16."
metadata:
author: OpenAEC-Foundation
version: "2.0"
---
# 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
```bash
# 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 versions
```
### What `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_utf8` and `formatted` no longer accepted
- **`db.set_value()` for Singles** — Use `frappe.db.set_single_value()` instead
- **`job_name` deprecated** — Use `job_id` parameter in `enqueue()`
- **`frappe.new_doc()` arguments** — `parent_doc`, `parentfield`, `as_dict` MUST be keyword args
- **`frappe.get_installed_apps()`** — No longer accepts `sort` or `frappe_last` args
- **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-lazy` class with native `loading="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 sitename` or `FRAPPE_SITE` env var
### Removed Features
- Event Streaming moved to separate app
- Cordova support removed
- `setup.py` removed (use `pyproject.toml`)
- `--make_copy` and `--restore` build flags removed (use `--hard-link`)
See [breaking-changes.md](references/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** — `creation` instead of `modified` for all list queries
- **`has_permission` hooks** — MUST return explicit `True`; `None` no 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
- `/apps` endpoint deprecated; `/app` reroutes 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 version` output format changed to "plain" (use `-f legacy` for old format)
- `override_doctype` hook classes MUST inherit from the overridden class
See [breaking-changes.md](references/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+]
```ini
[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_fields
```
### Patch Execution Rules
- Patches run in the order defined in `patches.txt`
- Each patch runs exactly ONCE — tracked in the `__patches` table
- 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
```python
# 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
```bash
# 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)
```bash
# 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 migrate` after 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)
```python
# hooks.py — define what to export
fixtures = [
"Custom Field",
"Property Setter",
{"dt": "Client Script", "filters": [["module", "=", "My Module"]]}
]
```
```bash
# Export fixtures to JSON in your app
bench --site mysite export-fixtures --app myapp
# Fixtures auto-sync on: bench --site mysite migrate
```
**NEVER** use Packages to modify standard/core DocTypes — use a Frappe App with Fixtures.
See [frappe-packages.md](references/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](references/examples.md) | Complete upgrade workflow examples |
| [anti-patterns.md](references/anti-patterns.md) | Common upgrade mistakes and fixes |
| [breaking-changes.md](references/breaking-changes.md) | Detailed breaking changes per version |
| [frappe-packages.md](references/frappe-packages.md) | Packages, fixtures, and moving customizations between sites |