Wealthfolio logo Wealthfolio
Download
Docs
API Reference

API Reference

Complete reference for Wealthfolio addon APIs.


API Reference

Complete reference for APIs available to Wealthfolio addons. All functions require appropriate permissions in manifest.json.

Context Overview

The AddonContext is provided to your addon’s enable function:

export interface AddonContext {
  ui: {
    root: HTMLElement;
  };
  sidebar: SidebarAPI;
  router: RouterAPI;
  onDisable: (callback: () => void) => void;
  api: HostAPI;
}

Basic usage:

export default function enable(ctx: AddonContext) {
  // Access APIs from event handlers, effects, or async helpers
  void ctx.api.accounts.getAll().then((accounts) => {
    ctx.api.logger.info(`Loaded ${accounts.length} accounts`);
  });

  // UI integration
  ctx.sidebar.addItem({
    /* ... */
  });
  ctx.router.add({
    /* ... */
  });

  // Cleanup
  ctx.onDisable(() => {
    // cleanup code
  });
}

API Domains

The API is organized into host-brokered domains:

DomainDescriptionKey Functions
AccountsAccount managementgetAll, create
PortfolioHoldings and valuationsgetHoldings, getIncomeSummary, update, recalculate
ActivitiesTrading transactionsgetAll, create, import, search, update
MarketMarket data and symbolssearchTicker, sync, getProviders, fetchDividends
PerformancePerformance metricscalculateHistory, calculateSummary
AssetsAsset profilesgetProfile, updateProfile, updateQuoteMode
QuotesPrice quotesupdate, getHistory
GoalsFinancial goalsgetAll, create, update, getFunding, saveFunding
Contribution LimitsInvestment limitsgetAll, create, update, calculateDeposits
Exchange RatesCurrency ratesgetAll, update, add
SettingsApp configurationget, update, backupDatabase
FilesFile operationsopenCsvDialog, openSaveDialog
SnapshotsHoldings snapshotsgetAll, getByDate, save, checkImport, importSnapshots, delete
EventsReal-time eventsonUpdateComplete, onSyncComplete, onDrop
SecretsSecure storageget, set, delete
StorageDurable key-value storeget, set, delete
NetworkBrokered HTTPS requestsrequest
LoggerLogging operationserror, info, warn, debug, trace
NavigationRoute navigationnavigate
QueryReact Query integrationgetClient, invalidateQueries, refetchQueries
ToastUser notificationssuccess, error, warning, info

Accounts API

Manage user accounts with full CRUD operations.

getAll(): Promise<Account[]>

Retrieves all user accounts.

const accounts = await ctx.api.accounts.getAll();

interface Account {
  id: string;
  name: string;
  currency: string;
  isActive: boolean;
  totalValue?: number;
  createdAt: string;
  updatedAt: string;
}

create(account: AccountCreate): Promise<Account>

Creates a new account with validation.

const newAccount = await ctx.api.accounts.create({
  name: 'My Investment Account',
  currency: 'USD',
  isActive: true,
});

Data Validation: Account creation includes validation for currency codes, name uniqueness, and other business rules.


Portfolio API

Access portfolio data, holdings, and performance information with real-time updates.

Methods

getHoldings(accountId: string): Promise<Holding[]>

Gets all holdings for a specific account with current valuations.

const holdings = await ctx.api.portfolio.getHoldings('account-123');

// Example holding structure
interface Holding {
  id: string;
  accountId: string;
  symbol: string;
  quantity: number;
  marketValue: number;
  totalCost: number;
  averagePrice: number;
  gainLoss: number;
  gainLossPercent: number;
  currency: string;
  assetType: 'Stock' | 'ETF' | 'Bond' | 'Crypto' | 'Other';
}

getHolding(accountId: string, assetId: string): Promise<Holding | null>

Gets a specific holding with detailed information.

const holding = await ctx.api.portfolio.getHolding('account-123', 'AAPL');

update(): Promise<void>

Triggers a portfolio update/recalculation across all accounts.

await ctx.api.portfolio.update();

recalculate(): Promise<void>

Forces a complete portfolio recalculation from scratch.

await ctx.api.portfolio.recalculate();

getIncomeSummary(): Promise<IncomeSummary[]>

Gets comprehensive income summary across all accounts.

const income = await ctx.api.portfolio.getIncomeSummary();

getHistoricalValuations(accountId?: string, startDate?: string, endDate?: string): Promise<AccountValuation[]>

Gets historical portfolio valuations for charts and analysis.

const history = await ctx.api.portfolio.getHistoricalValuations(
  'account-123', // optional, omit for all accounts
  '2024-01-01',
  '2024-12-31'
);

getLatestValuations(accountIds: string[]): Promise<AccountValuation[]>

Gets latest valuations for a set of accounts.

const valuations = await ctx.api.portfolio.getLatestValuations(['account-1', 'account-2']);

Activities API

Manage trading activities, transactions, and data imports with advanced search capabilities.

Methods

getAll(accountId?: string): Promise<ActivityDetails[]>

Gets all activities, optionally filtered by account.

// All activities across all accounts
const allActivities = await ctx.api.activities.getAll();

// Activities for specific account
const accountActivities = await ctx.api.activities.getAll('account-123');

search(page: number, pageSize: number, filters: ActivitySearchFilters, searchKeyword: string, sort?: ActivitySort): Promise<ActivitySearchResponse>

Advanced search with pagination and filters.

const results = await ctx.api.activities.search(
  1, // page
  50, // pageSize
  {
    // filters
    accountIds: ['account-123'],
    activityTypes: ['BUY'],
    symbol: 'AAPL',
  },
  'AAPL', // searchKeyword
  { id: 'date', desc: true } // sort
);

create(activity: ActivityCreate): Promise<Activity>

Creates a new activity with validation.

const activity = await ctx.api.activities.create({
  accountId: 'account-123',
  activityType: 'BUY',
  symbol: 'AAPL',
  quantity: 100,
  unitPrice: 150.5,
  currency: 'USD',
  date: '2024-12-01',
  isDraft: false,
});

update(activity: ActivityUpdate): Promise<Activity>

Updates an existing activity with conflict detection.

const updated = await ctx.api.activities.update({
  ...existingActivity,
  quantity: 150,
  unitPrice: 145.75,
});

saveMany(request: ActivityBulkMutationRequest): Promise<ActivityBulkMutationResult>

Efficiently creates, updates, or deletes multiple activities in a single transaction.

const result = await ctx.api.activities.saveMany({
  creates: [
    { accountId: 'account-123', activityType: 'BUY' /* ... */ },
    { accountId: 'account-123', activityType: 'DIVIDEND' /* ... */ },
  ],
  updates: [],
  deleteIds: [],
});

import(activities: ActivityImport[]): Promise<ImportActivitiesResult>

Imports validated activities with duplicate detection.

const imported = await ctx.api.activities.import(checkedActivities);

checkImport(activities: ActivityImport[]): Promise<ActivityImport[]>

Validates activities before import with error reporting.

const validated = await ctx.api.activities.checkImport(activities);

getImportMapping(accountId: string): Promise<ImportMappingData>

Get import mapping configuration for an account.

const mapping = await ctx.api.activities.getImportMapping('account-123');

saveImportMapping(mapping: ImportMappingData): Promise<ImportMappingData>

Save import mapping configuration.

const savedMapping = await ctx.api.activities.saveImportMapping(mapping);

Activity Types Reference

TypeUse CaseCash ImpactHoldings Impact
BUYPurchase securitiesDecreases cashIncreases quantity
SELLDispose of securitiesIncreases cashDecreases quantity
DIVIDENDCash dividend receivedIncreases cashNo change
INTERESTInterest earnedIncreases cashNo change
DEPOSITAdd fundsIncreases cashNo change
WITHDRAWALRemove fundsDecreases cashNo change
TRANSFER_INAssets moved inVariesIncreases quantity
TRANSFER_OUTAssets moved outVariesDecreases quantity
FEEBrokerage feesDecreases cashNo change
TAXTaxes paidDecreases cashNo change

Market Data API

Access market data, search symbols, and sync with external providers.

Methods

Throughout the Market, Assets, and Quotes APIs, assetId is an asset’s identifier. For a listed security this is its symbol (e.g. AAPL); cash uses $CASH-<CCY>.

searchTicker(query: string): Promise<SymbolSearchResult[]>

Search for ticker symbols across multiple data providers.

const results = await ctx.api.market.searchTicker('AAPL');

syncHistory(): Promise<void>

Syncs historical market data for all portfolio holdings.

await ctx.api.market.syncHistory();

sync(assetIds: string[], refetchAll: boolean): Promise<void>

Syncs market data for specific assets with cache control.

// Sync latest data (uses cache if recent)
await ctx.api.market.sync(['AAPL', 'MSFT', 'GOOGL'], false);

// Force refresh all data
await ctx.api.market.sync(['AAPL', 'MSFT', 'GOOGL'], true);

getProviders(): Promise<MarketDataProviderInfo[]>

Gets available market data providers and their status.

const providers = await ctx.api.market.getProviders();

fetchDividends(symbol: string, options?: FetchDividendsOptions): Promise<DividendEvent[]>

Fetches dividend history for a symbol.

const dividends = await ctx.api.market.fetchDividends('AAPL', {
  startDate: '2024-01-01',
  endDate: '2024-12-31',
});

Assets API

Access and manage asset profiles and data sources.

Methods

getProfile(assetId: string): Promise<Asset>

Gets detailed asset profile information.

const asset = await ctx.api.assets.getProfile('AAPL');

updateProfile(payload: UpdateAssetProfile): Promise<Asset>

Updates asset profile information.

const updatedAsset = await ctx.api.assets.updateProfile({
  id: 'AAPL',
  name: 'Apple Inc.',
  kind: 'INVESTMENT',
  // ... displayCode, notes, quoteMode, providerConfig
});

updateQuoteMode(assetId: string, quoteMode: string): Promise<Asset>

Switches an asset between market-fetched and manual quotes (MARKET or MANUAL).

const asset = await ctx.api.assets.updateQuoteMode('AAPL', 'MANUAL');

Quotes API

Manage price quotes and historical data.

Methods

update(assetId: string, quote: Quote): Promise<void>

Updates quote information for an asset.

await ctx.api.quotes.update('AAPL', {
  symbol: 'AAPL',
  price: 150.5,
  date: '2024-12-01',
  // ... other quote data
});

getHistory(assetId: string): Promise<Quote[]>

Gets historical quotes for an asset.

const history = await ctx.api.quotes.getHistory('AAPL');

Performance API

Calculate portfolio and account performance metrics with historical analysis.

Methods

calculateHistory(itemType: 'account' | 'symbol', itemId: string, startDate?: string, endDate?: string): Promise<PerformanceResult>

Calculates detailed performance history for charts and analysis.

const history = await ctx.api.performance.calculateHistory(
  'account',
  'account-123',
  '2024-01-01',
  '2024-12-31'
);

calculateSummary(args: { itemType: 'account' | 'symbol'; itemId: string; startDate?: string | null; endDate?: string | null; }): Promise<PerformanceResult>

Calculates comprehensive performance summary with key metrics.

const summary = await ctx.api.performance.calculateSummary({
  itemType: 'account',
  itemId: 'account-123',
  startDate: '2024-01-01',
  endDate: '2024-12-31',
});

calculateAccountsSimple(accountIds: string[]): Promise<SimplePerformanceResult[]>

Calculates simple performance metrics for multiple accounts efficiently.

const performance = await ctx.api.performance.calculateAccountsSimple([
  'account-123',
  'account-456',
]);

Exchange Rates API

Manage currency exchange rates for multi-currency portfolios.

Methods

getAll(): Promise<ExchangeRate[]>

Gets all exchange rates.

const rates = await ctx.api.exchangeRates.getAll();

update(updatedRate: ExchangeRate): Promise<ExchangeRate>

Updates an existing exchange rate.

const updatedRate = await ctx.api.exchangeRates.update({
  id: 'rate-123',
  fromCurrency: 'USD',
  toCurrency: 'EUR',
  rate: 0.85,
  // ... other rate data
});

add(newRate: Omit<ExchangeRate, 'id'>): Promise<ExchangeRate>

Adds a new exchange rate.

const newRate = await ctx.api.exchangeRates.add({
  fromCurrency: 'USD',
  toCurrency: 'GBP',
  rate: 0.75,
  // ... other rate data
});

Contribution Limits API

Manage investment contribution limits and calculations.

Methods

getAll(): Promise<ContributionLimit[]>

Gets all contribution limits.

const limits = await ctx.api.contributionLimits.getAll();

create(newLimit: NewContributionLimit): Promise<ContributionLimit>

Creates a new contribution limit.

const limit = await ctx.api.contributionLimits.create({
  name: 'RRSP 2024',
  limitType: 'RRSP',
  maxAmount: 30000,
  year: 2024,
  // ... other limit data
});

update(id: string, updatedLimit: NewContributionLimit): Promise<ContributionLimit>

Updates an existing contribution limit.

const updatedLimit = await ctx.api.contributionLimits.update('limit-123', {
  name: 'Updated RRSP 2024',
  maxAmount: 31000,
  // ... other updated data
});

calculateDeposits(limitId: string): Promise<DepositsCalculation>

Calculates deposits for a specific contribution limit.

const deposits = await ctx.api.contributionLimits.calculateDeposits('limit-123');

Goals API

Manage financial goals and funding rules.

Methods

getAll(): Promise<Goal[]>

Gets all goals.

const goals = await ctx.api.goals.getAll();

create(goal: unknown): Promise<Goal>

Creates a new goal.

const goal = await ctx.api.goals.create({
  name: 'Retirement Fund',
  targetAmount: 500000,
  targetDate: '2040-01-01',
  // ... other goal data
});

update(goal: Goal): Promise<Goal>

Updates an existing goal.

const updatedGoal = await ctx.api.goals.update({
  ...existingGoal,
  targetAmount: 600000,
});

getFunding(goalId: string): Promise<GoalAllocation[]>

Gets funding rules for a specific goal.

const funding = await ctx.api.goals.getFunding('goal-123');

saveFunding(goalId: string, rules: GoalAllocation[]): Promise<GoalAllocation[]>

Saves funding rules for a specific goal.

const savedFunding = await ctx.api.goals.saveFunding('goal-123', [
  { goalId: 'goal-123', accountId: 'account-456', percentage: 50 },
  // ... other funding rules
]);

getAllocations() and updateAllocations() still exist as deprecated compatibility shims. Prefer getFunding(goalId) and saveFunding(goalId, rules) for new addons.


Settings API

Manage application settings and configuration.

Methods

get(): Promise<Settings>

Gets application settings.

const settings = await ctx.api.settings.get();

update(settingsUpdate: Partial<Settings>): Promise<Settings>

Updates application settings.

const updatedSettings = await ctx.api.settings.update({
  ...currentSettings,
  baseCurrency: 'EUR',
  // ... other settings
});

backupDatabase(): Promise<{ filename: string }>

Creates a database backup.

const backup = await ctx.api.settings.backupDatabase();

Files API

Handle file operations and dialogs.

Methods

openCsvDialog(): Promise<null | string | string[]>

Opens a CSV file selection dialog.

const files = await ctx.api.files.openCsvDialog();
if (files) {
  // Process selected files
}

openSaveDialog(fileContent: Uint8Array | Blob | string, fileName: string): Promise<unknown>

Opens a file save dialog.

const result = await ctx.api.files.openSaveDialog(fileContent, 'export.csv');

Snapshots API

Manage holdings snapshots for accounts that use holdings tracking mode.

Methods

getAll(accountId: string, dateFrom?: string, dateTo?: string): Promise<SnapshotInfo[]>

Gets snapshots for an account, optionally filtered by date range.

const snapshots = await ctx.api.snapshots.getAll('account-123', '2024-01-01', '2024-12-31');

getByDate(accountId: string, date: string): Promise<Holding[]>

Gets holdings from a snapshot date.

const holdings = await ctx.api.snapshots.getByDate('account-123', '2024-12-31');

save(accountId: string, holdings: SnapshotHoldingInput[], cashBalances: Record<string, string>, snapshotDate?: string): Promise<void>

Saves a holdings snapshot.

await ctx.api.snapshots.save(
  'account-123',
  [{ symbol: 'AAPL', quantity: '10', currency: 'USD' }],
  { USD: '250.00' },
  '2024-12-31'
);

checkImport(accountId: string, snapshots: SnapshotInput[]): Promise<CheckSnapshotImportResult>

Validates snapshot import data before saving.

const preview = await ctx.api.snapshots.checkImport('account-123', parsedSnapshots);

importSnapshots(accountId: string, snapshots: SnapshotInput[]): Promise<SnapshotImportResult>

Imports validated snapshots.

const imported = await ctx.api.snapshots.importSnapshots('account-123', parsedSnapshots);

delete(accountId: string, date: string): Promise<void>

Deletes a snapshot for a date.

await ctx.api.snapshots.delete('account-123', '2024-12-31');

Secrets API

Securely store and retrieve sensitive data like API keys and tokens. Secrets are stored in the OS keyring and scoped to your addon ID.

Methods

set(key: string, value: string): Promise<void>

Stores a secret value encrypted and scoped to your addon.

// Store API key securely
await ctx.api.secrets.set('api-key', 'your-secret-api-key');

// Store user credentials
await ctx.api.secrets.set('auth-token', userAuthToken);

get(key: string): Promise<string | null>

Retrieves a secret value (returns null if not found).

const apiKey = await ctx.api.secrets.get('api-key');
if (apiKey) {
  // Use the value locally inside this addon only.
  // For HTTP Authorization headers, prefer ctx.api.network.request({ auth }).
}

delete(key: string): Promise<void>

Permanently deletes a secret.

await ctx.api.secrets.delete('old-api-key');

Security Note: Secrets are scoped by addon ID. Other addons cannot access your secrets, and you cannot access theirs. For brokered network auth, declare secrets.use and pass auth: { type: 'bearer' | 'basic', secretKey: '...' }; Wealthfolio injects the Authorization header on the backend.


Storage API

Durable per-addon key-value storage. Use this instead of localStorage, which is unavailable in the sandboxed (opaque-origin) iframe and throws. Values are opaque strings owned by your addon: storage survives addon updates, is cleared on uninstall, replicates across paired devices, and is scoped so no other addon can read it. Storage is a baseline capability — it needs no permissions entry.

Methods

get(key: string): Promise<string | null>

Retrieves a stored value (resolves to null if not set).

const raw = await ctx.api.storage.get('prefs');
const prefs = raw ? JSON.parse(raw) : defaults;

set(key: string, value: string): Promise<void>

Stores a string value. Keys are ≤ 128 characters and limited to the charset [A-Za-z0-9_.:-]. Values are capped at roughly 250 KB each — because storage replicates across a user’s paired devices, set rejects an oversized value instead of failing to sync later. Use many small keys rather than one large blob, and keep device-local caches out of storage.

await ctx.api.storage.set('prefs', JSON.stringify(prefs));

delete(key: string): Promise<void>

Permanently deletes a stored value.

await ctx.api.storage.delete('prefs');

Network API

Send brokered HTTPS requests to hosts declared in manifest.json. Browser fetch still has normal CORS restrictions and should not be used for secrets or authorization headers.

Manifest

{
  "permissions": [
    {
      "category": "network",
      "functions": ["request"],
      "purpose": "Fetch market data from api.example.com"
    },
    {
      "category": "secrets",
      "functions": ["use"],
      "purpose": "Inject the user's API token into brokered requests"
    }
  ],
  "network": {
    "allowedHosts": ["api.example.com"]
  }
}

allowedHosts is declared by the addon. approvedHosts is managed by Wealthfolio after the user approves hosts during install or update.

request(request: NetworkRequest): Promise<NetworkResponse>

const response = await ctx.api.network.request({
  url: 'https://api.example.com/v1/quotes',
  method: 'GET',
  headers: {
    Accept: 'application/json',
  },
  auth: {
    type: 'bearer',
    secretKey: 'api-token',
  },
});

if (response.status === 200) {
  const payload = JSON.parse(response.body);
}

auth.type selects the scheme Wealthfolio injects:

  • 'bearer'Authorization: Bearer <secret>
  • 'basic'Authorization: Basic <secret>. Store the already base64-encoded user:pass string as the secret; the broker only prefixes the scheme. This is the way to reach APIs like SimpleFin Bridge whose access URLs embed credentials — extract user:pass at setup, base64-encode it, and save it with ctx.api.secrets.set.

Network requests are constrained:

  • HTTPS only
  • Host must match a declared and user-approved host
  • Local, private, link-local, and metadata IPs are blocked
  • Redirects are disabled
  • Request bodies are limited to 1 MB
  • Response bodies are limited to 2 MB
  • Authorization cannot be supplied in headers; use auth.secretKey
  • Supported methods: GET, POST, PUT, PATCH, DELETE, HEAD

Logger API

Provides logging functionality with automatic addon prefix.

Methods

error(message: string): void

Logs an error message.

ctx.api.logger.error('Failed to fetch data from API');

info(message: string): void

Logs an informational message.

ctx.api.logger.info('Data sync completed successfully');

warn(message: string): void

Logs a warning message.

ctx.api.logger.warn('API rate limit approaching');

debug(message: string): void

Logs a debug message.

ctx.api.logger.debug('Processing 100 activities');

trace(message: string): void

Logs a trace message for detailed debugging.

ctx.api.logger.trace('Entering function processActivity');

Event System

Listen to real-time events for responsive addon behavior.

The listener methods return promises. Inside enable(ctx), keep enable synchronous and store cleanup functions when the listener promises resolve:

export default function enable(ctx: AddonContext) {
  let unlistenPortfolio: (() => void) | undefined;

  void ctx.api.events.portfolio
    .onUpdateComplete(() => {
      refreshPortfolioData();
    })
    .then((unlisten) => {
      unlistenPortfolio = unlisten;
    });

  ctx.onDisable(() => {
    unlistenPortfolio?.();
  });
}

Portfolio Events

onUpdateStart(callback: EventCallback): Promise<UnlistenFn>

Fires when portfolio update starts.

const unlistenStart = await ctx.api.events.portfolio.onUpdateStart((event) => {
  console.log('Portfolio update started');
  showLoadingIndicator();
});

onUpdateComplete(callback: EventCallback): Promise<UnlistenFn>

Fires when portfolio calculations are updated.

const unlistenPortfolio = await ctx.api.events.portfolio.onUpdateComplete((event) => {
  console.log('Portfolio updated:', event.payload);
  // Refresh your addon's data
  refreshPortfolioData();
});

// Clean up on disable
ctx.onDisable(() => {
  unlistenPortfolio();
});

onUpdateError(callback: EventCallback): Promise<UnlistenFn>

Fires when portfolio update encounters an error.

const unlistenError = await ctx.api.events.portfolio.onUpdateError((event) => {
  console.error('Portfolio update failed:', event.payload);
  showErrorMessage();
});

Market Events

onSyncStart(callback: EventCallback): Promise<UnlistenFn>

Fires when market data sync starts.

const unlistenSyncStart = await ctx.api.events.market.onSyncStart(() => {
  console.log('Market sync started');
  showSyncIndicator();
});

onSyncComplete(callback: EventCallback): Promise<UnlistenFn>

Fires when market data sync is completed.

const unlistenMarket = await ctx.api.events.market.onSyncComplete(() => {
  console.log('Market data updated!');
  // Update price displays
  updatePriceDisplays();
});

Import Events

onDropHover(callback: EventCallback): Promise<UnlistenFn>

Fires when files are hovered over for import.

const unlistenHover = await ctx.api.events.import.onDropHover((event) => {
  console.log('File hover detected');
  showDropZone();
});

onDrop(callback: EventCallback): Promise<UnlistenFn>

Fires when files are dropped for import.

const unlistenImport = await ctx.api.events.import.onDrop((event) => {
  console.log('File dropped:', event.payload);
  // Trigger import workflow
  handleFileImport(event.payload.files);
});

onDropCancelled(callback: EventCallback): Promise<UnlistenFn>

Fires when file drop is cancelled.

const unlistenCancel = await ctx.api.events.import.onDropCancelled(() => {
  console.log('File drop cancelled');
  hideDropZone();
});

Navigate programmatically within the Wealthfolio application.

Methods

Navigate to a specific route in the application.

// Navigate to a specific account
await ctx.api.navigation.navigate('/accounts/account-123');

// Navigate to portfolio overview
await ctx.api.navigation.navigate('/portfolio');

// Navigate to activities page
await ctx.api.navigation.navigate('/activities');

// Navigate to settings
await ctx.api.navigation.navigate('/settings');

Navigation Routes: The navigation API uses the same route structure as the main application. Common routes include /accounts, /portfolio, /activities, /goals, and /settings.


Query API

Access an addon-owned React Query client and ask the host to refresh selected caches.

Methods

getClient(): QueryClient

Gets a QueryClient instance for the addon sandbox. This is not the raw host QueryClient.

const queryClient = ctx.api.query.getClient();

// Use standard React Query methods
const accounts = await queryClient.fetchQuery({
  queryKey: ['accounts'],
  queryFn: () => ctx.api.accounts.getAll(),
});

When the addon calls invalidateQueries or refetchQueries on this client with a string query key, Wealthfolio mirrors that request to the host cache.

invalidateQueries(queryKey: string | string[]): void

Invalidates queries to trigger refetch.

// Invalidate specific query
ctx.api.query.invalidateQueries(['accounts']);

// Invalidate multiple related queries
ctx.api.query.invalidateQueries(['portfolio', 'holdings']);

// Invalidate all account-related queries
ctx.api.query.invalidateQueries(['accounts']);

refetchQueries(queryKey: string | string[]): void

Triggers immediate refetch of queries.

// Refetch portfolio data
ctx.api.query.refetchQueries(['portfolio']);

// Refetch multiple queries
ctx.api.query.refetchQueries(['accounts', 'holdings']);

Integration with Events

Combine Query API with event listeners for reactive data updates:

export default function enable(ctx: AddonContext) {
  let unlistenPortfolio: (() => void) | undefined;
  let unlistenMarket: (() => void) | undefined;

  // Invalidate relevant queries when portfolio updates
  void ctx.api.events.portfolio
    .onUpdateComplete(() => {
      ctx.api.query.invalidateQueries(['portfolio', 'holdings', 'performance']);
    })
    .then((unlisten) => {
      unlistenPortfolio = unlisten;
    });

  // Invalidate market data queries when sync completes
  void ctx.api.events.market
    .onSyncComplete(() => {
      ctx.api.query.invalidateQueries(['quotes', 'assets']);
    })
    .then((unlisten) => {
      unlistenMarket = unlisten;
    });

  ctx.onDisable(() => {
    unlistenPortfolio?.();
    unlistenMarket?.();
  });
}

UI Integration APIs

Prefer declaring sidebar entries in manifest.json under contributes.links.sidebar (see below) — the host renders them without booting your addon and they survive reloads. The runtime addItem API is still available for dynamic items an addon adds while running.

Declarative (preferred)

"contributes": {
  "routes": [{ "id": "my-addon", "path": "/addons/my-addon" }],
  "links": {
    "sidebar": [
      { "id": "my-addon", "route": "my-addon", "label": "My Addon", "icon": "wallet", "order": 100 }
    ]
  }
}

A route is a durable addon page (host-renderable before the addon boots — the lazy-activation surface); a link is a placement in a host slot (only "sidebar" is consumed today) that references a declared route id of the same addon. The runtime router.add({ id }) must equal contributes.routes[].id.

addItem(item: SidebarItem): SidebarItemHandle

For dynamic, runtime-added entries:

const sidebarItem = ctx.sidebar.addItem({
  id: 'my-addon',
  label: 'My Addon',
  route: '/addons/my-addon',
  icon: 'wallet', // Optional — an AddonIconName (see below)
  order: 100, // Lower numbers appear first
});

// Remove when addon is disabled
ctx.onDisable(() => {
  sidebarItem.remove();
});

The icon field is typed as AddonIconName from @wealthfolio/addon-sdk. Import that type for autocomplete and a compile-time error on any invalid name:

import type { AddonIconName } from '@wealthfolio/addon-sdk';

const icon: AddonIconName = 'wallet'; // ✓
// const icon: AddonIconName = 'spinner'; // ✗ type error

Icons come from a curated set of Phosphor icons (duotone weight) that Wealthfolio bundles and renders — the sidebar is host UI, so an addon names an icon rather than shipping one. Matching is case- and separator-insensitive ('ChartLine', 'chart-line', and 'chartline' are equivalent), and an unknown or omitted name renders a neutral caret-right fallback.

This applies only to the sidebar icon. Inside your own route/page you render with your own React, so you can use any icon there — Lucide, Phosphor, or your own SVGs.

The 80 supported names, by group (preview every glyph at phosphoricons.com):

  • Money & financewallet, coins, dollar, dollar-circle, bank, credit-card, piggy-bank, receipt, invoice, hand-coins, vault, chart-line-up, chart-line, trend-up, trend-down, percent, scales, calculator
  • Charts & analyticschart-bar, chart-pie, chart-pie-slice, chart-donut, gauge, target, presentation
  • Assetshouse, buildings, car, airplane, bicycle, diamond, bitcoin, storefront, briefcase, package, cube
  • Generalstar, heart, gift, trophy, medal, lightning, sparkle, bell, tag, bookmark, flag, fire, rocket, lightbulb, graduation-cap, barbell, fork-knife, coffee, wine, shopping-cart, shopping-bag, basket
  • Time & placecalendar, calendar-dots, calendar-check, clock, hourglass, globe, map-pin, compass
  • Productivityfolder, files, notebook, clipboard-text, list-checks, sliders, wrench, toolbox, puzzle-piece, plugs-connected, app-window, squares-four, stack, kanban

Router API

Register the renderer for your addon’s pages. The host owns a single React root per addon and mounts the route’s component itself, so hand it a component rather than managing a root by hand.

add(route: RouteConfig): void

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { AddonContext, AddonEnableFunction } from '@wealthfolio/addon-sdk';
import { MyPage } from './pages/MyPage';

let addonCtx: AddonContext | undefined;

const MyRoute = () => (
  <QueryClientProvider client={addonCtx!.api.query.getClient() as QueryClient}>
    <MyPage ctx={addonCtx!} />
  </QueryClientProvider>
);

const enable: AddonEnableFunction = (ctx) => {
  addonCtx = ctx;
  ctx.router.add({ id: 'my-addon', path: '/addons/my-addon', component: MyRoute });
  ctx.onDisable(() => {
    addonCtx = undefined;
  });
};

export default enable;

Provide exactly one of component (preferred — the host manages mount/unmount in its single root) or render (a legacy imperative escape hatch given the container element); if both are set, component wins. Do not call createRoot yourself — a per-route root leaves an orphaned tree whose re-renders never reach the DOM. The component receives the current route as a { location } prop (AddonRouteLocation with pathname/search/hash/params); the sandbox has no react-router provider, so useLocation() / useParams() are unavailable.

When a route is also declared in manifest.json contributes.routes, the runtime router.add({ id }) must use the same id — a mismatch renders a blank “route is not available” page. Runtime router.add still works for routes an addon adds dynamically while running.

Addon routes must stay inside the addon’s namespace. For an addon with id my-addon, use /addons/my-addon or /addon/my-addon. For ids ending in -addon, Wealthfolio also allows the stripped slug, for example swingfolio-addon can use /addons/swingfolio.


Error Handling

API Error Types

interface APIError {
  code: string;
  message: string;
  details?: any;
}

Common error codes:

  • PERMISSION_DENIED - Insufficient permissions
  • NOT_FOUND - Resource not found
  • VALIDATION_ERROR - Invalid data provided
  • NETWORK_ERROR - Connection issues
  • RATE_LIMITED - Too many requests

Best Practices

try {
  const accounts = await ctx.api.accounts.getAll();
} catch (error) {
  if (error.code === 'PERMISSION_DENIED') {
    ctx.api.logger.error('Missing account permissions');
    // Show user-friendly message
  } else if (error.code === 'NETWORK_ERROR') {
    ctx.api.logger.warn('Network issue, retrying...');
    // Implement retry logic
  } else {
    ctx.api.logger.error('Unexpected error:', error);
    // General error handling
  }
}

Advanced Usage

Batch Operations

// Efficient batch processing
const activities = await Promise.all([
  ctx.api.activities.getAll('account-1'),
  ctx.api.activities.getAll('account-2'),
  ctx.api.activities.getAll('account-3'),
]);

// Batch create
const newActivities = await ctx.api.activities.saveMany({
  creates: [
    {
      /* activity 1 */
    },
    {
      /* activity 2 */
    },
    {
      /* activity 3 */
    },
  ],
});

Real-time Updates

export default function enable(ctx: AddonContext) {
  const unsubscribers: Array<() => void> = [];

  // Listen for multiple events
  void Promise.all([
    ctx.api.events.portfolio.onUpdateComplete(() => refreshData()),
    ctx.api.events.market.onSyncComplete(() => updatePrices()),
    ctx.api.events.import.onDrop((event) => handleImport(event)),
  ]).then((listeners) => {
    unsubscribers.push(...listeners);
  });

  // Clean up all listeners
  ctx.onDisable(() => {
    unsubscribers.forEach((unsub) => unsub());
  });
}

Caching Strategies

// Simple in-memory cache
const cache = new Map();
let unlistenPortfolio: (() => void) | undefined;

void ctx.api.events.portfolio
  .onUpdateComplete(() => {
    cache.delete('accounts');
  })
  .then((unlisten) => {
    unlistenPortfolio = unlisten;
  });

async function getCachedAccounts() {
  if (cache.has('accounts')) {
    return cache.get('accounts');
  }

  const accounts = await ctx.api.accounts.getAll();
  cache.set('accounts', accounts);

  return accounts;
}

ctx.onDisable(() => {
  unlistenPortfolio?.();
  cache.clear();
});

TypeScript Support

Full TypeScript definitions are provided for all APIs:

import type {
  AddonContext,
  Account,
  Activity,
  Holding,
  PerformanceHistory,
  PerformanceSummary,
  // ... and many more
} from '@wealthfolio/addon-sdk';

// Type-safe API usage
const accounts: Account[] = await ctx.api.accounts.getAll();
const holdings: Holding[] = await ctx.api.portfolio.getHoldings(accounts[0].id);

Performance Tips

  1. Use batch operations when possible
  2. Implement caching for expensive operations
  3. Listen to relevant events only
  4. Clean up resources in disable function
  5. Use React.memo for expensive components
  6. Debounce user inputs for search/filter

Ready to build? Check out the Getting Started guide to see these APIs in action!