/
Documentation

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:

Settings.tsx
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