actualbudget/actual · error · Error

Invalid budget id "${id}". Check the id of your budget in th

Error message

Invalid budget id "${id}". Check the id of your budget in the Advanced section of the settings page.

What it means

Budget ids become a directory name under the document dir, so getBudgetDir sanitizes them: any character outside [A-Za-z0-9-_] (slashes, dots, spaces, unicode) is rejected to prevent path traversal outside the budget directory. The error tells the user to verify the budget id configured in the Advanced settings section.

Source

Thrown at packages/loot-core/src/platform/server/fs/shared.ts:27

    throw new Error('Document directory is not set');
  }
  return documentDir;
};

export const getBudgetDir = id => {
  if (!id) {
    throw new Error('getDocumentDir: id is falsy: ' + id);
  }

  // TODO: This should be better
  //
  // A cheesy safe guard. The id is generated from the budget name,
  // so it provides an entry point for the user to accidentally (or
  // intentionally) access other parts of the system. Always
  // restrict it to only access files within the budget directory by
  // never allowing slashes.
  if (id.match(/[^A-Za-z0-9\-_]/)) {
    throw new Error(
      `Invalid budget id "${id}". Check the id of your budget in the Advanced section of the settings page.`,
    );
  }

  return join(getDocumentDir(), id);
};

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Open Settings > Advanced, check the budget id, and correct it to contain only letters, digits, dashes, or underscores.
  2. Sanitize/validate ids on input: reject or transform anything not matching /^[A-Za-z0-9-_]+$/.
  3. If an id was derived from a budget name, slugify it (strip or replace disallowed characters) before use.

Example fix

// before
const id = budgetName; // e.g. 'My Budget/2024'
const dir = getBudgetDir(id);

// after
const id = budgetName.replace(/[^A-Za-z0-9\-_]/g, '-');
if (!/^[A-Za-z0-9\-_]+$/.test(id)) {
  throw new Error('Budget id must be alphanumeric, dash, or underscore');
}
const dir = getBudgetDir(id);
Defensive patterns

Strategy: validation

Validate before calling

const SAFE_ID = /^[A-Za-z0-9\-_]+$/;
if (!SAFE_ID.test(budgetId)) {
  throw new Error(`Budget id "${budgetId}" contains invalid characters`);
}

Type guard

function isValidBudgetId(id: string): boolean {
  return /^[A-Za-z0-9\-_]+$/.test(id);
}

Try / catch

try {
  const dir = getBudgetDir(id);
} catch (e) {
  if (String(e.message).startsWith('Invalid budget id')) {
    // surface settings-page guidance to the user
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a budget id containing '/', '\', '..', whitespace, or other special characters to getBudgetDir; a hand-edited budget id in config/settings; constructing ids from user input without sanitization.

Common situations: A user pasted an incorrect or corrupted id into the Advanced > budget id setting; a self-hosted setup script generated an id from a budget name containing slashes; automated tooling injected URL-encoded or path-like ids.

Related errors


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