This guide provides comprehensive information for AI agents (like Cursor) working with the Actual Budget codebase.
Actual Budget is a local-first personal finance tool written in TypeScript/JavaScript. It's 100% free and open-source with synchronization capabilities across devices.
- Repository: https://github.com/actualbudget/actual
- Community Docs: Documentation is part of the monorepo at
packages/docs/. Published at https://actualbudget.org/docs - License: MIT
- Primary Language: TypeScript (with React)
- Build System: Yarn 4 workspaces (monorepo)
# Type checking (ALWAYS run before committing)
yarn typecheck
# Linting and formatting (with auto-fix)
yarn lint:fix
# Run all tests
yarn test
# Start development server (browser)
yarn start
# Start with sync server
yarn start:server-dev
# Start desktop app development
yarn start:desktop- ALWAYS run yarn commands from the root directory - never run them in child workspaces
- Use
yarn workspace <workspace-name> run <command>for workspace-specific tasks - Tests run once and exit by default (using
vitest --run)
ALL commit messages and PR titles MUST be prefixed with [AI]. No exceptions.
See PR and Commit Rules for the full specification, including git safety rules, pre-commit checklist, and PR workflow.
The project uses lage (a task runner for JavaScript monorepos) to efficiently run tests and other tasks across multiple workspaces:
- Parallel execution: Runs tests in parallel across workspaces for faster feedback
- Smart caching: Caches test results to skip unchanged packages (cached in
.lage/directory) - Dependency awareness: Understands workspace dependencies and execution order
- Continues on error: Uses
--continueflag to run all packages even if one fails
Lage Commands:
# Run all tests across all packages
yarn test # Equivalent to: lage test --continue
# Run tests without cache (for debugging/CI)
yarn test:debug # Equivalent to: lage test --no-cache --continueConfiguration is in lage.config.js at the project root.
The core application logic that runs on any platform.
-
Business logic, database operations, and calculations
-
Platform-agnostic code
-
Exports for both browser and node environments
-
Test commands:
# Run all loot-core tests yarn workspace @actual-app/core run test # Or run tests across all packages using lage yarn test
The React-based UI for web and desktop.
-
React components using functional programming patterns
-
E2E tests using Playwright
-
Vite for bundling
-
Commands:
# Development yarn workspace @actual-app/web start:browser # Build yarn workspace @actual-app/web build # E2E tests yarn workspace @actual-app/web e2e # Visual regression tests yarn workspace @actual-app/web vrt
Electron wrapper for the desktop application.
- Window management and native OS integration
- E2E tests for Electron-specific features
Public API for programmatic access to Actual.
-
Node.js API
-
Designed for integrations and automation
-
Commands:
# Build yarn workspace @actual-app/api build # Run tests yarn workspace @actual-app/api test # Or use lage to run all tests yarn test
Synchronization server for multi-device support.
- Express-based server
- Currently transitioning to TypeScript (mostly JavaScript)
- Commands:
yarn workspace @actual-app/sync-server start
Reusable React UI components.
- Shared components like Button, Input, Menu, etc.
- Theme system and design tokens
- Icons (375+ icons in SVG/TSX format)
CRDT (Conflict-free Replicated Data Type) implementation for data synchronization.
- Protocol buffers for serialization
- Core sync logic
Service for handling plugins/extensions.
Custom ESLint rules specific to Actual.
no-untranslated-strings: Enforces i18n usageprefer-trans-over-t: Prefers Trans component over t() functionprefer-logger-over-console: Enforces using logger instead of console inpackages/loot-core/typography: Typography rulesprefer-if-statement: Prefers explicit if statements
Documentation website built with Docusaurus.
- Documentation is part of the monorepo
- Built with Docusaurus 3
- Commands:
yarn workspace docs start yarn workspace docs build yarn start:docs # From root
When implementing changes:
- Read relevant files to understand current implementation
- Make focused, incremental changes
- Run type checking:
yarn typecheck - Run linting:
yarn lint:fix - Run relevant tests
- Fix any linter errors that are introduced
Unit Tests (Vitest)
The project uses lage for running tests across all workspaces efficiently.
# Run all tests across all packages (using lage)
yarn test
# Run tests without cache (for debugging)
yarn test:debug
# Run tests for a specific package
yarn workspace @actual-app/core run testE2E Tests (Playwright)
# Run E2E tests for web
yarn e2e
# Desktop Electron E2E (includes full build)
yarn e2e:desktop
# Visual regression tests
yarn vrt
# Visual regression in Docker (consistent environment)
yarn vrt:docker
# Run E2E tests for a specific package
yarn workspace @actual-app/web e2eTesting Best Practices:
- Minimize mocked dependencies - prefer real implementations
- Use descriptive test names
- Vitest globals are available:
describe,it,expect,beforeEach, etc. - For sync-server tests, globals are explicitly defined in config
TypeScript configuration uses:
- Incremental compilation
- Strict type checking with
typescript-strict-plugin - Platform-specific exports in
loot-core(node vs browser)
Always run yarn typecheck before committing.
- Use
Transcomponent instead oft()function when possible - All user-facing strings must be translated
- Generate i18n files:
yarn generate:i18n - Custom ESLint rules enforce translation usage
- Wrap standalone financial numbers with
FinancialTextor applystyles.tnumdirectly if wrapping is not possible
Type Usage:
- Use TypeScript for all code
- Prefer
typeoverinterface - Avoid
enum- use objects or maps - Avoid
anyorunknownunless absolutely necessary - Look for existing type definitions in the codebase
- Avoid type assertions (
as,!) - prefersatisfies - Use inline type imports:
import { type MyType } from '...'
Naming:
- Use descriptive variable names with auxiliary verbs (e.g.,
isLoaded,hasError) - Named exports for components and utilities (avoid default exports except in specific cases)
Code Structure:
- Functional and declarative programming patterns - avoid classes
- Use the
functionkeyword for pure functions - Prefer iteration and modularization over code duplication
- Structure files: exported component/page, helpers, static content, types
- Create new components in their own files
React Patterns:
- The project uses React Compiler (
babel-plugin-react-compiler) in the desktop-client. The compiler auto-memoizes component bodies, so you can omit manualuseCallback,useMemo, andReact.memowhen adding or refactoring code; prefer inline callbacks and values unless a stable identity is required by a non-compiled dependency. - Don't use
React.FunctionComponentorReact.FC- type props directly - Don't use
React.*patterns - use named imports instead - Use
<Link>instead of<a>tags - Use custom hooks from
src/hooks(not react-router directly):useNavigate()fromsrc/hooks(not react-router)useDispatch(),useSelector(),useStore()fromsrc/redux(not react-redux)
- Avoid unstable nested components
- Use
satisfiesfor type narrowing
JSX Style:
- Declarative JSX, minimal and readable
- Avoid unnecessary curly braces in conditionals
- Use concise syntax for simple statements
- Prefer explicit expressions (
condition && <Component />)
Imports are automatically organized by ESLint with the following order:
- React imports (first)
- Built-in Node.js modules
- External packages
- Actual packages (
loot-core,@actual-app/components- legacy patternloot-designmay appear in old code) - Parent imports
- Sibling imports
- Index imports
Always maintain newlines between import groups.
- Don't directly reference platform-specific imports (
.api,.electron) - Use conditional exports in
loot-corefor platform-specific code - Platform resolution happens at build time via package.json exports
Never:
- Import from
uuidwithout destructuring: useimport { v4 as uuidv4 } from 'uuid' - Import colors directly - use theme instead
- Import
@actual-app/web/*inloot-core
Git Commands:
See PR and Commit Rules for complete git safety rules, commit message requirements, and PR workflow.
import { type ComponentType } from 'react';
// ... other imports
type MyComponentProps = {
// Props definition
};
export function MyComponent({ prop1, prop2 }: MyComponentProps) {
// Component logic
return (
// JSX
);
}import { describe, it, expect, beforeEach } from 'vitest';
// ... imports
describe('ComponentName', () => {
it('should behave as expected', () => {
// Test logic
expect(result).toBe(expected);
});
});/package.json- Root workspace configuration, scripts/lage.config.js- Lage task runner configuration/eslint.config.mjs- ESLint configuration (flat config format)/tsconfig.json- Root TypeScript configuration/.cursorignore,/.gitignore- Ignored files/yarn.lock- Dependency lockfile (Yarn 4)
/README.md- Project overview/CONTRIBUTING.md- Points to community docs/upcoming-release-notes/- Release notes for next version/CODEOWNERS- Code ownership definitions/packages/docs/- Documentation website (Docusaurus)
packages/*/lib-dist/- Built outputpackages/*/dist/- Built outputpackages/*/build/- Built outputpackages/desktop-client/playwright-report/- Test reportspackages/desktop-client/test-results/- Test results.lage/- Lage task runner cache (improves test performance)
packages/loot-core/src/client/- Client-side core logicpackages/loot-core/src/server/- Server-side core logicpackages/loot-core/src/shared/- Shared utilitiespackages/loot-core/src/types/- Type definitionspackages/desktop-client/src/components/- React componentspackages/desktop-client/src/hooks/- Custom React hookspackages/desktop-client/e2e/- End-to-end testspackages/component-library/src/- Reusable componentspackages/component-library/src/icons/- Icon components (auto-generated, don't edit)packages/docs/docs/- Documentation source files (Markdown)packages/docs/docs/contributing/- Developer documentation
# Run all tests across all packages (recommended)
yarn test
# E2E test for a specific file
yarn workspace @actual-app/web run playwright test accounts.test.ts --browser=chromium# Browser build
yarn build:browser
# Desktop build
yarn build:desktop
# API build
yarn build:api
# Sync server build
yarn build:serverTypeScript uses project references. Run yarn typecheck from root to check all packages.
# Run tests in debug mode (without parallelization)
yarn test:debug
# Run specific E2E test with headed browser
yarn workspace @actual-app/web run playwright test --headed --debug accounts.test.tsIcons in packages/component-library/src/icons/ are auto-generated. Don't manually edit them.
- Run
yarn typecheckto see all type errors - Check if types are imported correctly
- Look for existing type definitions in
packages/loot-core/src/types/ - Use
satisfiesinstead ofasfor type narrowing
- Run
yarn lint:fixto auto-fix many issues - Check ESLint output for specific rule violations
- Custom rules:
actual/no-untranslated-strings- Add i18nactual/prefer-trans-over-t- Use Trans componentactual/prefer-logger-over-console- Use logger- Check
eslint.config.mjsfor complete rules
- Check if test is running in correct environment (node vs web)
- For Vitest: check
vitest.config.tsorvitest.web.config.ts - For Playwright: check
playwright.config.ts - Ensure mock minimization - prefer real implementations
- Lage cache issues: Clear cache with
rm -rf .lageif tests behave unexpectedly - Tests continue on error: With
--continueflag, all packages run even if one fails
- Check
tsconfig.jsonfor path mappings - Check package.json
exportsfield (especially for loot-core) - Verify platform-specific imports (
.electron,.api) - Use absolute imports in
desktop-client(enforced by ESLint)
- Clean build artifacts:
rm -rf packages/*/dist packages/*/lib-dist packages/*/build - Reinstall dependencies:
yarn install - Check Node.js version (requires >=22)
- Check Yarn version (requires ^4.9.1)
- Located alongside source files or in
__tests__directories - Use
.test.ts,.test.tsx,.spec.jsextensions - Vitest is the test runner
- Minimize mocking - prefer real implementations
- Located in
packages/desktop-client/e2e/ - Use Playwright test runner
- Visual regression snapshots in
*-snapshots/directories - Page models in
e2e/page-models/for reusable page interactions - Mobile tests have
.mobile.test.tssuffix
- Snapshots stored per test file in
*-snapshots/directories - Use Docker for consistent environment:
yarn vrt:docker
- Community Documentation: https://actualbudget.org/docs/contributing/
- Discord Community: https://discord.gg/pRYNYr4W5A
- GitHub Issues: https://github.com/actualbudget/actual/issues
- Feature Requests: Label "needs votes" sorted by reactions
Before committing changes, ensure:
- Commit and PR rules followed (see PR and Commit Rules)
-
yarn typecheckpasses -
yarn lint:fixhas been run - Relevant tests pass
- User-facing strings are translated
- Prefer
typeoverinterface - Named exports used (not default exports)
- Imports are properly ordered
- Platform-specific code uses proper exports
- No unnecessary type assertions
See PR and Commit Rules for complete PR creation rules, including title prefix requirements, labeling, and PR template handling.
When performing code reviews (especially for LLM agents): see CODE_REVIEW_GUIDELINES.md for specific guidelines.
- Bundle Size: Check with rollup-plugin-visualizer
- Type Checking: Uses incremental compilation
- Testing: Tests run in parallel by default
- Linting: ESLint caches results for faster subsequent runs
# List all workspaces
yarn workspaces list
# Run command in specific workspace
yarn workspace <workspace-name> run <command>
# Run command in all workspaces
yarn workspaces foreach --all run <command>
# Install production dependencies only (for server deployment)
yarn install:server- Node.js: >=22
- Yarn: ^4.9.1 (managed by packageManager field)
- Browser Targets: Electron >= 35.0, modern browsers (see browserslist)
The codebase is actively being migrated:
- JavaScript → TypeScript: sync-server is in progress
- Classes → Functions: Prefer functional patterns
- React.* → Named Imports: Legacy React.* patterns being removed
When working with older code, follow the newer patterns described in this guide.
| Service | Command | Port | Required |
|---|---|---|---|
| Web Frontend (Vite) | yarn start |
3001 | Yes |
| Sync Server | yarn start:server-dev |
5006 | Optional (sync features only) |
All storage is SQLite (file-based via better-sqlite3). No external databases or services are needed.
yarn startbuilds the plugins-service worker, loot-core browser backend, and starts the Vite dev server on port 3001.yarn start:server-devstarts both the sync server (port 5006) and the web frontend together.- The Vite HMR dev server serves many unbundled modules. In constrained environments, the browser may hit
ERR_INSUFFICIENT_RESOURCES. If that happens, useyarn build:browserfollowed by serving the built output frompackages/desktop-client/build/with proper COOP/COEP headers (Cross-Origin-Opener-Policy: same-origin,Cross-Origin-Embedder-Policy: require-corp).
Standard commands documented in package.json scripts and the Quick Start section above:
yarn lint/yarn lint:fix(uses oxlint + oxfmt)yarn test(lage across all workspaces)yarn typecheck(tsgo + lage typecheck)
When running the app for manual testing or demos, use "View demo" on the initial setup screen (after selecting "Don't use a server"). This creates a test budget pre-populated with realistic sample data (accounts, transactions, categories, and budgeted amounts), which is far more useful than starting with an empty budget.
- The
enginesfield requires Node.js >=22 and Yarn ^4.9.1. The.nvmrcspecifiesv22/*. - Pre-commit hook runs
lint-staged(oxfmt + oxlint) via Husky. Runyarn prepareonce after install to set up hooks. - Lage caches test results in
.lage/. If tests behave unexpectedly, clear withrm -rf .lage. - Native modules (
better-sqlite3,bcrypt) require build tools (gcc,make,python3). These are pre-installed in the Cloud VM. - All yarn commands must be run from the repository root, never from child workspaces.