laurent22/joplin · critical · JoplinError
mustUpgradeApp
mustUpgradeApp
Error message
In order to synchronise, please upgrade your application to version %s+
What it means
checkIfCanSync() throws JoplinError with code MustUpgradeApp when the sync target's recorded minimum app version (s.appMinVersion, read from the target's info.json) is greater than the running app version. The only escape hatch is forward compatibility: the target's appMinVersion is exactly '3.7.0' (the Joplin 3.7 format this client understands) and the target has no locked notes (noteLockKey === null). Otherwise the client refuses to sync because a newer client has already written a format it cannot safely read.
Source
Thrown at packages/lib/services/synchronizer/syncInfoUtils.ts:617
syncInfo.ppk = ppk;
saveLocalSyncInfo(syncInfo);
}
export function masterKeyById(id: string) {
return localSyncInfo().masterKeys.find(mk => mk.id === id);
}
export const checkIfCanSync = (s: SyncInfo, appVersion: string) => {
const isForwardCompatible = () => {
// Forward compatibility: This version of Joplin supports the Joplin 3.7 sync target format
if (s.appMinVersion !== forwardCompatibleAppMinVersion) return false;
// Older Joplin versions don't support sync targets with locked notes
if (s.noteLockKey !== null) return false;
return true;
};
if (compareVersions(appVersion, s.appMinVersion) < 0 && !isForwardCompatible()) {
throw new JoplinError(_('In order to synchronise, please upgrade your application to version %s+', s.appMinVersion), ErrorCode.MustUpgradeApp);
}
};
View on GitHub (pinned to 683240968b)
Solutions
- Update Joplin on this device to at least the version named in the message (s.appMinVersion) — this is the intended fix and non-negotiable for a genuinely newer target format.
- If all clients are meant to be old, recreate or point to a sync target that was never touched by a newer client (old targets keep their lower appMinVersion).
- For app builders embedding this library, call checkIfCanSync(syncInfo, appVersion) up front and show an upgrade prompt instead of letting the error abort mid-sync.
- Do not attempt to edit info.json's appMinVersion manually — a newer format may genuinely be present and older clients will corrupt it.
Example fix
// before
try {
await synchronizer().start();
} catch (error) {
// generic handler; sync fails cryptically mid-run
}
// after
checkIfCanSync(localSyncInfo(), appVersion); // throws MustUpgradeApp early with a clear message
try {
await synchronizer().start();
} catch (error) {
if (error.code === ErrorCode.MustUpgradeApp) {
return showUpgradePrompt(error.message); // never retry
}
throw error;
} Defensive patterns
Strategy: validation
Validate before calling
import { compareVersions } from '@joplin/lib/utils/compareVersions';
const syncInfo = localSyncInfo();
if (compareVersions(appVersion, syncInfo.appMinVersion) < 0) {
// Show 'upgrade required' UI before starting sync; do not attempt the sync
} Try / catch
try {
await sync();
} catch (error) {
if (error.code === ErrorCode.MustUpgradeApp) {
// Unrecoverable by retrying: prompt the user to upgrade the app, then stop
} else {
throw error;
}
} Prevention
- Call checkIfCanSync(syncInfo, appVersion) at sync start and translate MustUpgradeApp into an upgrade prompt.
- Keep all clients on a sync target within the same major version; upgrade every device, not just one.
- Never downgrade a client below the sync target's appMinVersion, and never hand-edit info.json to bypass the check.
- For test/CI sync targets, recreate them with the oldest client you support.
When it happens
Trigger: A device running an older Joplin syncs against a target that a newer Joplin (which bumped appMinVersion in info.json) has already upgraded; downgrading the app after a newer client used the same sync target; a fresh install of an old build pointed at a shared target; the target contains locked notes (noteLockKey set), which kills the forward-compat exception even at appMinVersion 3.7.0.
Common situations: Mixed-version fleets: one device auto-updates to a new release, syncs, and raises the target's minimum version; Linux distro or portable builds pinned to old versions syncing with up-to-date phones; rolling back a client to work around a regression and finding sync locked out; CI with a cached old sync target.
Related errors
- Cannot initialise synchroniser.
- Got metadata for path but could not fetch content: ${path}
- No item with ID ${itemId}
- Please specify the sync target path.
- Synchronisation is already in progress.
AI-assisted analysis of laurent22/joplin@683240968b (2026-08-21).
Data as JSON: /api/errors/82fcf0e62f2b1605.
Report an issue: GitHub.