/
Documentation

Navigation & Notifications

Navigate users to SyncBooks pages, show toast notifications, close the add-on, resize the iframe, and send push notifications.

Navigation

Tell SyncBooks to navigate the user to a different page:

example.tsx
import { navigation } from '@syncbooks/addon-sdk'

// Navigate to the invoices list
navigation.open('/invoices')

// Navigate to a specific invoice
navigation.open('/invoices/507f1f77bcf86cd799439011')

// Navigate to customer detail
navigation.open('/customers/507f1f77bcf86cd799439012')

// Navigate to settings
navigation.open('/settings')

// Navigate to the addon marketplace
navigation.open('/addons/marketplace')

// Common pattern: navigate after creating a record
async function handleCreateInvoice(data) {
  const { data: invoice } = await invoices.create(data)
  ui.notify('Invoice created!', 'success')
  navigation.open(`/invoices/${invoice._id}`)
}

Relative paths

Paths are relative to the organization dashboard. So /invoices means /{orgId}/dashboard/{userId}/sales/invoices internally. The SDK handles the translation.

Toast Notifications

Show toast messages inside the SyncBooks host (not inside your iframe):

example.tsx
import { ui } from '@syncbooks/addon-sdk'

// Success (green) — use after successful operations
ui.notify('Invoice sent successfully!', 'success')

// Error (red) — use for failures
ui.notify('Failed to send email. Please try again.', 'error')

// Warning (yellow) — use for non-critical issues
ui.notify('Customer has no email address', 'warning')

// Info (blue, default) — use for neutral information
ui.notify('Processing your request...')
ui.notify('3 invoices are overdue', 'info')

// Common patterns:
async function handleSubmit(data) {
  try {
    await customers.create(data)
    ui.notify('Customer created!', 'success')
  } catch (error) {
    ui.notify(formatError(error), 'error')
  }
}

Toasts appear in SyncBooks, not in your iframe

The ui.notify() function sends a postMessage to the SyncBooks host, which renders the toast in the main app. This means toasts are visible even if the user navigates away from your addon.

Closing the Add-on

Programmatically close the add-on panel (user returns to the previous SyncBooks page):

import { ui } from '@syncbooks/addon-sdk'

// Close after completing an action
async function handleComplete() {
  await invoices.update(invoiceId, { status: 'sent' })
  ui.notify('Invoice marked as sent!', 'success')
  ui.close()  // Closes the addon panel
}

// Close on cancel
function handleCancel() {
  ui.close()
}

Resizing the Iframe

Request that SyncBooks changes the height of your add-on's iframe:

import { ui } from '@syncbooks/addon-sdk'

// Set a specific height
ui.resize(800)

// Dynamically resize based on content
useEffect(() => {
  const height = document.body.scrollHeight
  ui.resize(height + 20)  // Add some padding
}, [data])

// Resize after expanding a section
function handleAccordionToggle() {
  setTimeout(() => {
    ui.resize(document.body.scrollHeight)
  }, 300)  // Wait for animation
}

Push Notifications

Send notifications that appear in the user's notification bell (persisted, not just toasts):

import { notifications } from '@syncbooks/addon-sdk'

// Send to the current user
await notifications.send({
  title: 'Payment Received',
  message: 'Customer "Acme Corp" paid GHS 5,000 for invoice INV-042',
  type: 'success',
})

// Send to a specific user (e.g., the account owner)
await notifications.send({
  title: 'New Order',
  message: 'A new order #1234 was placed for GHS 2,500',
  type: 'info',
  userId: 'specific_user_id',  // optional, defaults to current user
})

Use notifications sparingly

Persistent notifications are more intrusive than toasts. Use them for important events (payments, new orders) not for confirmations (record updated). For confirmations, use ui.notify() instead.

Listening for Host Events

Listen for messages from the SyncBooks host using the useHostEvent hook:

import { useHostEvent } from '@syncbooks/addon-sdk'

function App() {
  // Listen for context updates (e.g., when user switches pages)
  useHostEvent('syncbooks:context', (payload) => {
    console.log('Context updated:', payload)
  })

  // Listen for custom events from the host
  useHostEvent('syncbooks:theme-changed', (payload) => {
    console.log('Theme:', payload.theme)  // 'light' or 'dark'
  })

  return <div>...</div>
}

Complete Example

Here's a component that uses navigation, notifications, and closing together:

InvoiceActions.tsx
import { useSyncBooks, invoices, ui, navigation } from '@syncbooks/addon-sdk'
import { Button, ConfirmDialog } from '@syncbooks/addon-sdk/components'

function InvoiceActions({ invoiceId, customerName }) {
  const [confirming, setConfirming] = useState(false)

  async function handleSend() {
    await invoices.update(invoiceId, { status: 'sent' })
    ui.notify(`Invoice sent to ${customerName}`, 'success')
  }

  async function handleMarkPaid() {
    await invoices.update(invoiceId, { status: 'paid' })
    ui.notify('Invoice marked as paid!', 'success')
    navigation.open('/invoices')  // Go back to invoice list
  }

  async function handleDelete() {
    await invoices.delete(invoiceId)
    ui.notify('Invoice deleted', 'success')
    ui.close()  // Close addon after deletion
  }

  return (
    <div className="flex gap-2">
      <Button onClick={handleSend}>Send to Customer</Button>
      <Button variant="outline" onClick={handleMarkPaid}>Mark as Paid</Button>
      <Button variant="destructive" onClick={() => setConfirming(true)}>Delete</Button>

      <ConfirmDialog
        open={confirming}
        onCancel={() => setConfirming(false)}
        onConfirm={handleDelete}
        title="Delete Invoice"
        description="This will permanently delete the invoice."
        variant="destructive"
      />
    </div>
  )
}

Next Steps