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

  1. Configure a sync server URL (bootstrap/login with a server) before calling saveServerPrefs — check getServer() or the config first
  2. If local-only use is intended, skip saveServerPrefs entirely and persist prefs locally via savePrefs instead
  3. Call saveServerPrefs only after a successful login that returns a user-token and server config
  4. 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

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


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/b46a7164092bc47a. Report an issue: GitHub.