actualbudget/actual · error · Error
No sync server configured.
Error message
No sync server configured.
What it means
Thrown by saveServerPrefs when the local budget file has no sync server configured. Before syncing server-side preferences to the server via POST /server-prefs, it calls getServer(); if it returns null the user is operating in local-only mode (no server URL set), so there is no endpoint to push prefs to and the method throws instead of making a doomed network call.
Source
Thrown at packages/loot-core/src/server/preferences/app.ts:247
await _saveMetadataPrefs(prefsToSet);
return 'ok';
}
async function loadMetadataPrefs(): Promise<MetadataPrefs> {
return _getMetadataPrefs();
}
async function saveServerPrefs({ prefs }: { prefs: Record<string, string> }) {
const userToken = await asyncStorage.getItem('user-token');
if (!userToken) {
return { error: 'not-logged-in' };
}
try {
const serverConfig = getServer();
if (!serverConfig) {
throw new Error('No sync server configured.');
}
await post(serverConfig.SIGNUP_SERVER + '/server-prefs', {
token: userToken,
prefs,
});
} catch (err) {
if (err instanceof PostError) {
return {
error: err.reason || 'network-failure',
};
}
throw err;
}
return {};
}
View on GitHub (pinned to d4334cb6e6)
Solutions
- Configure a sync server URL (bootstrap/login with a server) before calling saveServerPrefs — check getServer() or the config first
- If local-only use is intended, skip saveServerPrefs entirely and persist prefs locally via savePrefs instead
- Call saveServerPrefs only after a successful login that returns a user-token and server config
- Handle the thrown error in the caller and surface a 'no sync server' prompt to the user
Example fix
// before
await app.saveServerPrefs({ prefs });
// after
import { getServer } from './server-config';
if (getServer()) {
await app.saveServerPrefs({ prefs });
} else {
await app.savePrefs({ localPrefs });
} Defensive patterns
Strategy: try-catch
Validate before calling
import { getServer } from './server-config';
if (!getServer()) {
throw new Error('Cannot save server prefs: no sync server configured.');
}
const hasToken = (await asyncStorage.getItem('user-token')) != null;
if (!hasToken) throw new Error('Cannot save server prefs: not logged in.'); Type guard
function hasSyncServer(cfg: ReturnType<typeof getServer> | null): cfg is NonNullable<ReturnType<typeof getServer>> {
return cfg != null && typeof cfg.SIGNUP_SERVER === 'string' && cfg.SIGNUP_SERVER.length > 0;
} Try / catch
try {
await app.saveServerPrefs({ prefs });
} catch (err) {
if (err instanceof Error && err.message === 'No sync server configured.') {
// fall back to local prefs storage
await app.savePrefs({ prefs });
} else {
throw err;
}
} Prevention
- Check getServer() (or the server URL pref) before any server-pref operation
- Treat local-only budgets as a distinct mode and route pref writes to local storage
- Only call saveServerPrefs after a successful login/bootstrap flow
- Surface the missing-server state in UI before allowing pref-saving actions
When it happens
Trigger: Calling the saveServerPrefs method (e.g. via the app API or changing global/server prefs from the UI) while no sync server URL is configured — typically in a local-only budget that has never gone through 'Sign in to server' or bootstrap, or after the server URL was removed/cleared from the prefs.
Common situations: Developers scripting against the API with a purely local budget; users switching from server-synced to local-only usage and then trying to save server prefs; fresh CI environments where the sync-server config step was skipped; desktop installs where the server URL was cleared after a budget reset.
Related errors
- Could not resolve on-disk budget id for syncId ${syncId} aft
- No sync server configured.
- Failed to parse mapping: ${String(message)}
- Sync ID is required for sync ${flag}. Set --sync-id or ACTUA
- TrieNode for key ${k} could not be found
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/b46a7164092bc47a.
Report an issue: GitHub.