SDK Reference
The Plugin SDK provides everything your plugin needs to interact with Treeline. It’s passed to your views via props when they mount.
import type { PluginSDK } from "@treeline-money/plugin-sdk";
interface Props { sdk: PluginSDK;}let { sdk }: Props = $props();Database
Section titled “Database”sdk.sql()
Section titled “sdk.sql()”Execute a read-only SQL query and return objects keyed by column name. This is the recommended method for most queries.
sql<T = Record<string, unknown>>( sql: string, params?: QueryParam[]): Promise<T[]>Parameters:
sql- SQL SELECT query with?placeholders for parametersparams- Optional array of values to bind to placeholders
Returns: Array of row objects with column names as keys
Example:
// Simple queryconst accounts = await sdk.sql<Account>("SELECT * FROM accounts");
// Parameterized query (recommended for user input)const transactions = await sdk.sql<Transaction>( "SELECT * FROM transactions WHERE amount > ? AND posted_date > ?", [100, "2024-01-01"]);
// With type parameterinterface Transaction { transaction_id: string; amount: number; description: string; posted_date: string;}const results = await sdk.sql<Transaction>( "SELECT * FROM transactions LIMIT 10");console.log(results[0].amount); // access by column nameQuery Parameter Types:
type QueryParam = string | number | boolean | null | string[] | number[];sdk.query()
Section titled “sdk.query()”Execute a read-only SQL query and return raw row arrays. Use sdk.sql() instead if you want objects keyed by column name.
query<T = unknown[]>( sql: string, params?: QueryParam[]): Promise<T[]>Parameters:
sql- SQL SELECT query with?placeholders for parametersparams- Optional array of values to bind to placeholders
Returns: Array of raw row arrays (values in column order)
Example:
const rows = await sdk.query("SELECT COUNT(*) FROM transactions");console.log(rows[0][0]); // access by indexsdk.execute()
Section titled “sdk.execute()”Execute a write SQL query (INSERT, UPDATE, DELETE, CREATE, DROP).
execute( sql: string, params?: QueryParam[]): Promise<{ rowsAffected: number }>Parameters:
sql- SQL write query with?placeholdersparams- Optional array of values to bind
Returns: Object with rowsAffected count
Example:
const schema = sdk.getSchemaName();
// Insert with parametersawait sdk.execute( `INSERT INTO ${schema}.items (id, name, amount) VALUES (?, ?, ?)`, [crypto.randomUUID(), "New Item", 100]);
// Updateconst result = await sdk.execute( `UPDATE ${schema}.items SET amount = ? WHERE id = ?`, [150, itemId]);console.log(`Updated ${result.rowsAffected} rows`);
// Deleteawait sdk.execute( `DELETE FROM ${schema}.items WHERE id = ?`, [itemId]);sdk.getSchemaName()
Section titled “sdk.getSchemaName()”Get the schema name for this plugin’s tables.
getSchemaName(): stringReturns: The plugin’s schema name (e.g., "plugin_goals")
Example:
const schema = sdk.getSchemaName(); // "plugin_my_plugin"
// Use in queriesawait sdk.execute(` CREATE TABLE IF NOT EXISTS ${schema}.items ( id VARCHAR PRIMARY KEY, name VARCHAR NOT NULL )`);Notifications
Section titled “Notifications”sdk.toast
Section titled “sdk.toast”Show toast notifications to the user.
toast: { show: (message: string, description?: string) => void; success: (message: string, description?: string) => void; error: (message: string, description?: string) => void; warning: (message: string, description?: string) => void; info: (message: string, description?: string) => void;}Example:
// Success messagesdk.toast.success("Saved!", "Your changes have been saved");
// Error with detailssdk.toast.error("Failed to save", error.message);
// Warningsdk.toast.warning("Heads up", "This action cannot be undone");
// Infosdk.toast.info("Tip", "Press Cmd+K to open the command palette");
// Generic (same as info)sdk.toast.show("Hello", "This is a notification");Navigation
Section titled “Navigation”sdk.openView()
Section titled “sdk.openView()”Navigate to another view in the application.
openView(viewId: string, props?: Record<string, unknown>): voidParameters:
viewId- The view ID to openprops- Optional props to pass to the view
Example:
// Open a built-in viewsdk.openView("accounts");
// Open with propssdk.openView("query", { initialQuery: "SELECT * FROM transactions LIMIT 10"});
// Open another plugin's viewsdk.openView("budget-view");Data Events
Section titled “Data Events”sdk.onDataRefresh()
Section titled “sdk.onDataRefresh()”Subscribe to data refresh events. Called when data changes (sync, import, etc.).
onDataRefresh(callback: () => void): () => voidParameters:
callback- Function to call when data is refreshed
Returns: Unsubscribe function
Example:
import { onMount, onDestroy } from "svelte";
let unsubscribe: (() => void) | null = null;
onMount(() => { // Subscribe to data changes unsubscribe = sdk.onDataRefresh(() => { loadData(); // Reload your data });});
onDestroy(() => { // Clean up subscription if (unsubscribe) unsubscribe();});sdk.emitDataRefresh()
Section titled “sdk.emitDataRefresh()”Notify other views that data has changed. Call this after modifying data.
emitDataRefresh(): voidExample:
async function saveItem(item: Item) { await sdk.execute( `INSERT INTO ${sdk.getSchemaName()}.items (id, name) VALUES (?, ?)`, [item.id, item.name] );
// Notify other views sdk.emitDataRefresh();
sdk.toast.success("Saved!");}sdk.updateBadge()
Section titled “sdk.updateBadge()”Update the badge count shown on this plugin’s sidebar item.
updateBadge(count: number | undefined): voidParameters:
count- Badge count to display (0 or undefined to hide)
Example:
// Show a badgesdk.updateBadge(5);
// Hide the badgesdk.updateBadge(0);sdk.updateBadge(undefined);
// Update based on dataconst pending = await sdk.sql<{ count: number }>( `SELECT COUNT(*) as count FROM ${sdk.getSchemaName()}.tasks WHERE done = false`);sdk.updateBadge(pending[0]?.count || 0);sdk.theme
Section titled “sdk.theme”Theme utilities for adapting to light/dark mode.
theme: { current: () => "light" | "dark"; subscribe: (callback: (theme: string) => void) => () => void;}sdk.theme.current()
Section titled “sdk.theme.current()”Get the current theme.
const theme = sdk.theme.current(); // "light" or "dark"sdk.theme.subscribe()
Section titled “sdk.theme.subscribe()”Subscribe to theme changes.
const unsubscribe = sdk.theme.subscribe((theme) => { console.log("Theme changed to:", theme);});
// Later, unsubscribeunsubscribe();Full Example:
<script lang="ts"> import { onMount } from "svelte";
let theme = $state<"light" | "dark">("light");
onMount(() => { theme = sdk.theme.current();
sdk.theme.subscribe((newTheme) => { theme = newTheme as "light" | "dark"; }); });</script>
<div class:dark={theme === "dark"}> <!-- Content adapts to theme --></div>Currency
Section titled “Currency”sdk.currency
Section titled “sdk.currency”Currency formatting utilities that respect the user’s currency preference.
currency: { format: (amount: number, currency?: string) => string; formatCompact: (amount: number, currency?: string) => string; formatAmount: (amount: number) => string; getSymbol: (currency: string) => string; getUserCurrency: () => string; supportedCurrencies: string[];}sdk.currency.format()
Section titled “sdk.currency.format()”Format an amount with currency symbol.
sdk.currency.format(1234.56); // "$1,234.56" (user's currency)sdk.currency.format(1234.56, "EUR"); // "EUR 1,234.56"sdk.currency.format(-50); // "-$50.00"sdk.currency.formatCompact()
Section titled “sdk.currency.formatCompact()”Format large amounts compactly.
sdk.currency.formatCompact(1234567); // "$1.2M"sdk.currency.formatCompact(50000); // "$50K"sdk.currency.formatCompact(999); // "$999"sdk.currency.formatCompact(1234567, "GBP"); // "GBP 1.2M"sdk.currency.formatAmount()
Section titled “sdk.currency.formatAmount()”Format just the number without currency symbol.
sdk.currency.formatAmount(1234.56); // "1,234.56"sdk.currency.formatAmount(-99.99); // "-99.99"sdk.currency.getSymbol()
Section titled “sdk.currency.getSymbol()”Get the symbol for a currency code.
sdk.currency.getSymbol("USD"); // "$"sdk.currency.getSymbol("EUR"); // "EUR"sdk.currency.getSymbol("GBP"); // "GBP"sdk.currency.getSymbol("JPY"); // "JPY"sdk.currency.getUserCurrency()
Section titled “sdk.currency.getUserCurrency()”Get the user’s configured currency code.
const currency = sdk.currency.getUserCurrency(); // "USD"sdk.currency.supportedCurrencies
Section titled “sdk.currency.supportedCurrencies”List of supported currency codes.
sdk.currency.supportedCurrencies; // ["USD", "EUR", "GBP", "JPY", ...]Settings & State
Section titled “Settings & State”sdk.settings
Section titled “sdk.settings”Persistent settings scoped to your plugin. Survives app restarts.
settings: { get: <T extends Record<string, unknown>>() => Promise<T>; set: <T extends Record<string, unknown>>(settings: T) => Promise<void>;}Example:
interface MySettings { showCompleted: boolean; sortOrder: "asc" | "desc";}
// Load settingsconst settings = await sdk.settings.get<MySettings>();console.log(settings.showCompleted); // true or false
// Save settingsawait sdk.settings.set<MySettings>({ showCompleted: true, sortOrder: "desc",});sdk.state
Section titled “sdk.state”Ephemeral state scoped to your plugin. Cleared on app restart.
state: { read: <T>() => Promise<T | null>; write: <T>(state: T) => Promise<void>;}Example:
interface MyState { selectedId: string | null; scrollPosition: number;}
// Read stateconst state = await sdk.state.read<MyState>();
// Write stateawait sdk.state.write<MyState>({ selectedId: "item-123", scrollPosition: 500,});When to use which:
| Use Case | Tool |
|---|---|
| User preferences | sdk.settings |
| UI configuration | sdk.settings |
| Selected items (temporary) | sdk.state |
| Scroll position | sdk.state |
| Form drafts | sdk.state |
Platform Utilities
Section titled “Platform Utilities”sdk.modKey
Section titled “sdk.modKey”Platform-aware modifier key string.
modKey: "Cmd" | "Ctrl"Example:
sdk.modKey; // "Cmd" on Mac, "Ctrl" on Windows/Linux
// In UI<span>Press {sdk.modKey}+K to search</span>sdk.formatShortcut()
Section titled “sdk.formatShortcut()”Format a keyboard shortcut for display.
formatShortcut(shortcut: string): stringParameters:
shortcut- Shortcut string usingmodfor platform modifier
Returns: Formatted shortcut for current platform
Example:
sdk.formatShortcut("mod+p"); // "Cmd+P" on Mac, "Ctrl+P" on Windowssdk.formatShortcut("mod+shift+k"); // "Cmd+Shift+K" on Macsdk.formatShortcut("mod+enter"); // "Cmd+Enter" on MacRegistration Types
Section titled “Registration Types”These types are used when registering views, commands, and sidebar items in activate().
ViewDefinition
Section titled “ViewDefinition”interface ViewDefinition { id: string; // Unique view ID name: string; // Display name (shown in tab) icon: string; // Lucide icon name mount: (target: HTMLElement, props: { sdk: PluginSDK }) => () => void; allowMultiple?: boolean; // Can multiple instances be open?}SidebarItem
Section titled “SidebarItem”interface SidebarItem { id: string; // Unique ID label: string; // Display label icon: string; // Lucide icon name sectionId: string; // Section this belongs to ("main") viewId: string; // View to open when clicked shortcut?: string; // Keyboard shortcut hint order?: number; // Sort order within section}Command
Section titled “Command”interface Command { id: string; // Unique command ID name: string; // Display name description?: string; // Optional description category?: string; // Category for grouping shortcut?: string; // Keyboard shortcut execute: () => void | Promise<void>; // Function to execute}PluginMigration
Section titled “PluginMigration”interface PluginMigration { version: number; // Unique version number (positive integer) name: string; // Human-readable name up: string; // SQL to execute}Complete Example
Section titled “Complete Example”Here’s a complete example using multiple SDK features:
<script lang="ts"> import { onMount, onDestroy } from "svelte"; import type { PluginSDK } from "@treeline-money/plugin-sdk";
interface Props { sdk: PluginSDK; } let { sdk }: Props = $props();
interface Item { id: string; name: string; amount: number; }
let items = $state<Item[]>([]); let isLoading = $state(true); let unsubscribe: (() => void) | null = null;
const schema = sdk.getSchemaName();
onMount(async () => { // Subscribe to data refresh unsubscribe = sdk.onDataRefresh(() => loadData());
// Load initial data await loadData(); });
onDestroy(() => { if (unsubscribe) unsubscribe(); });
async function loadData() { isLoading = true; try { items = await sdk.sql<Item>( `SELECT * FROM ${schema}.items ORDER BY name` ); sdk.updateBadge(items.length); } catch (e) { sdk.toast.error("Failed to load", e instanceof Error ? e.message : String(e)); } finally { isLoading = false; } }
async function addItem() { try { await sdk.execute( `INSERT INTO ${schema}.items (id, name, amount) VALUES (?, ?, ?)`, [crypto.randomUUID(), "New Item", 0] ); sdk.toast.success("Added!"); sdk.emitDataRefresh(); await loadData(); } catch (e) { sdk.toast.error("Failed to add", e instanceof Error ? e.message : String(e)); } }
async function deleteItem(id: string) { try { await sdk.execute( `DELETE FROM ${schema}.items WHERE id = ?`, [id] ); sdk.emitDataRefresh(); await loadData(); } catch (e) { sdk.toast.error("Failed to delete", e instanceof Error ? e.message : String(e)); } }</script>
<div class="tl-view"> <div class="tl-header"> <div class="tl-header-left"> <h1 class="tl-title">My Items</h1> <p class="tl-subtitle">Press {sdk.formatShortcut("mod+n")} to add</p> </div> <div class="tl-header-right"> <button class="tl-btn tl-btn-primary" onclick={addItem}>Add Item</button> </div> </div> <div class="tl-content"> {#if isLoading} <div class="tl-loading"><div class="tl-spinner"></div></div> {:else if items.length === 0} <div class="tl-empty"> <p class="tl-empty-title">No items yet</p> <p class="tl-empty-message">Click "Add Item" to get started</p> </div> {:else} <div class="tl-table-container"> <table class="tl-table"> <thead> <tr> <th>Name</th> <th style:text-align="right">Amount</th> <th></th> </tr> </thead> <tbody> {#each items as item} <tr> <td>{item.name}</td> <td class="tl-cell-number">{sdk.currency.format(item.amount)}</td> <td class="tl-cell-actions"> <button class="tl-btn tl-btn-danger" onclick={() => deleteItem(item.id)}>Delete</button> </td> </tr> {/each} </tbody> </table> </div> {/if} </div></div>