Config & Storage
Persist add-on settings per organization, save user preferences locally, and build settings pages.
Per-Organization Config
Every add-on gets a key-value config store that is unique per organization. Use it to store API keys, feature flags, templates, or any settings your add-on needs.
Reading Config
import { config } from '@syncbooks/addon-sdk'
const { data: settings } = await config.get()
// settings = { apiKey: 'sk_123', enableAutoSend: true, template: 'Hello {name}...' }
// Or use the hook for reactive updates
import { useConfig } from '@syncbooks/addon-sdk'
function SettingsPage() {
const { data: settings, loading, refetch } = useConfig()
if (loading) return <Spinner />
return <p>API Key: {settings?.apiKey || 'Not set'}</p>
}Saving Config
import { config } from '@syncbooks/addon-sdk'
// Save entire config (replaces previous)
await config.set({
apiKey: 'sk_new_key_456',
enableAutoSend: true,
sendTime: '09:00',
messageTemplate: 'Hi {name}, your invoice {number} for {amount} is due.',
webhookUrl: 'https://myapp.com/webhook',
})
// To update a single field, read first then merge:
const { data: current } = await config.get()
await config.set({ ...current, apiKey: 'new_value' })config.set() replaces the entire object
The set() method replaces the whole config object. Always read the current config first if you want to update a single field. A future version will add config.patch().
Building a Settings Page
Common pattern for an add-on settings page:
import { useState, useEffect } from 'react'
import { useConfig, config, ui } from '@syncbooks/addon-sdk'
import { Button, Input, Switch, Textarea, PageLayout } from '@syncbooks/addon-sdk/components'
export default function Settings() {
const { data: saved, loading } = useConfig()
const [form, setForm] = useState({ apiKey: '', enableNotifications: false, template: '' })
const [saving, setSaving] = useState(false)
// Load saved values into form
useEffect(() => {
if (saved) {
setForm({
apiKey: saved.apiKey || '',
enableNotifications: saved.enableNotifications || false,
template: saved.template || 'Hi {name}, your invoice {number} is due.',
})
}
}, [saved])
async function handleSave() {
setSaving(true)
await config.set(form)
setSaving(false)
ui.notify('Settings saved!', 'success')
}
if (loading) return <Spinner />
return (
<PageLayout title="Settings" description="Configure your add-on">
<div className="space-y-6 max-w-lg">
<Input
label="API Key"
type="password"
value={form.apiKey}
onChange={(e) => setForm({ ...form, apiKey: e.target.value })}
placeholder="Enter your API key"
/>
<Switch
label="Enable automatic notifications"
checked={form.enableNotifications}
onChange={(v) => setForm({ ...form, enableNotifications: v })}
/>
<Textarea
label="Message Template"
value={form.template}
onChange={(e) => setForm({ ...form, template: e.target.value })}
placeholder="Use {name}, {number}, {amount} as placeholders"
/>
<Button onClick={handleSave} loading={saving}>
Save Settings
</Button>
</div>
</PageLayout>
)
}Local Storage (Client-Side)
For UI preferences that don't need to be shared across users (view mode, collapsed state, theme):
import { useLocalStorage } from '@syncbooks/addon-sdk'
function Dashboard() {
// Persists across page reloads, scoped to this addon + org
const [viewMode, setViewMode] = useLocalStorage('viewMode', 'table')
const [sidebarOpen, setSidebarOpen] = useLocalStorage('sidebar', true)
return (
<div>
<button onClick={() => setViewMode(viewMode === 'table' ? 'grid' : 'table')}>
{viewMode === 'table' ? 'Switch to Grid' : 'Switch to Table'}
</button>
{viewMode === 'table' ? <TableView /> : <GridView />}
</div>
)
}Raw Storage Helpers
import { getStorage, setStorage, removeStorage } from '@syncbooks/addon-sdk'
// These are scoped to your addon automatically
setStorage('lastSync', new Date().toISOString())
const lastSync = getStorage('lastSync', null)
removeStorage('lastSync')Config vs Local Storage
config (server-side): Shared across all users in the org. Use for API keys, feature flags, settings.
localStorage (client-side): Per-user, per-browser. Use for UI preferences, collapsed states, theme.
Notes (Attach Data to Records)
Attach notes/comments to any SyncBooks record from your add-on:
import { notes } from '@syncbooks/addon-sdk'
// Add a note to a customer
await notes.create({
resourceType: 'customer',
resourceId: '507f1f77bcf86cd799439011',
content: 'Called on 2026-08-13. Customer requested invoice by email.',
})
// Get notes for a record
const { data: customerNotes } = await notes.list('customer', '507f1f77bcf86cd799439011')
// customerNotes = [{ content: '...', createdAt: '...', source: 'addon:com.myapp' }]Next Steps
- Authentication — deep dive into tokens and scopes
- Deployment — deploy and go live