/
Documentation

SDK Initialization

Connect your add-on to SyncBooks by wrapping it with the SDK provider. This handles authentication, context, and API setup automatically.

The SyncBooksProvider

Every SyncBooks add-on must be wrapped with SyncBooksProvider. This component:

  • Establishes the postMessage bridge with the SyncBooks host
  • Receives the session context (organization, user, token, scopes)
  • Configures the API client with the session token
  • Makes all hooks and API calls work automatically
src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { SyncBooksProvider } from '@syncbooks/addon-sdk'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <SyncBooksProvider>
      <App />
    </SyncBooksProvider>
  </React.StrictMode>
)

Don't skip the provider

Without SyncBooksProvider, none of the SDK hooks work. Your add-on will show "Loading..." forever and API calls will fail with "No session token."

Using the Context Hook

Access the SyncBooks context from any component using useSyncBooks():

src/App.tsx
import { useSyncBooks } from '@syncbooks/addon-sdk'

export default function App() {
  const {
    ready,            // boolean — true when bridge is connected
    organizationId,   // string — the org that installed your addon
    userId,           // string — the user currently viewing
    userRole,         // string — "admin", "accountant", "member", etc.
    appId,            // string — your addon's unique ID
    scopes,           // string[] — permissions granted to you
    token,            // string — session JWT (used by API client automatically)
    resource,         // { type, id } | null — if opened from a specific record
  } = useSyncBooks()

  // ALWAYS check ready before rendering content
  if (!ready) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <p>Connecting to SyncBooks...</p>
      </div>
    )
  }

  return (
    <div className="p-6">
      <h1>Connected!</h1>
      <p>Organization: {organizationId}</p>
      <p>User: {userId} ({userRole})</p>
      <p>Scopes: {scopes.join(', ')}</p>
    </div>
  )
}

Context Properties

PropertyTypeDescription
readyrequiredbooleanTrue when the bridge is connected and context is received. Always check this before rendering.
organizationIdstring | nullThe ID of the organization that installed your addon.
userIdstring | nullThe ID of the user currently viewing your addon.
userRolestring | nullThe user's role: admin, accountant, sales, hr, cashier, member, etc.
appIdstring | nullYour addon's unique app ID (com.publisher.appname format).
scopesstring[]Array of permission scopes granted to your addon by this org.
tokenstring | nullThe session JWT. You don't need to use this directly — the API client uses it automatically.
resource{ type: string, id: string } | nullIf the user opened your addon from a specific record (e.g., clicked your button on an invoice), this tells you which record.

Resource Context

When your add-on is opened from a specific record (like an invoice action button), the resource object tells you which record triggered it:

src/App.tsx
import { useSyncBooks, invoices } from '@syncbooks/addon-sdk'

function App() {
  const { ready, resource } = useSyncBooks()
  
  if (!ready) return <p>Loading...</p>
  
  // If opened from an invoice action button
  if (resource?.type === 'invoice') {
    return <InvoiceDetail invoiceId={resource.id} />
  }
  
  // If opened from sidebar (no specific resource)
  return <Dashboard />
}

function InvoiceDetail({ invoiceId }: { invoiceId: string }) {
  const [invoice, setInvoice] = useState(null)
  
  useEffect(() => {
    invoices.get(invoiceId).then(res => setInvoice(res.data))
  }, [invoiceId])
  
  if (!invoice) return <p>Loading invoice...</p>
  return <div>Invoice: {invoice.invoiceNumber}</div>
}

Scope Checking

Always verify you have the required scopes before making API calls or showing write UI:

src/App.tsx
import { useSyncBooks, hasScope } from '@syncbooks/addon-sdk'

function App() {
  const { ready, scopes } = useSyncBooks()
  
  if (!ready) return null

  // Check single scope
  const canReadInvoices = scopes.includes('invoices.read') || 
                          scopes.includes('invoices.*') || 
                          scopes.includes('*')

  // Or use the helper function
  const canWrite = hasScope('customers.write')
  const canDelete = hasScope('customers.delete')

  return (
    <div>
      {canReadInvoices && <InvoiceList />}
      {canWrite && <button>Create Customer</button>}
      {canDelete && <button>Delete</button>}
    </div>
  )
}

Use the PermissionGate component

Instead of manual scope checks, use the SDK's PermissionGate component for cleaner code. See UI Components.

Local Development

During development, your add-on runs on localhost but can't receive a real session from SyncBooks. Configure the SDK with test credentials:

src/main.tsx
import { SyncBooksProvider, configureClient } from '@syncbooks/addon-sdk'

// For local development only — remove before deploying!
if (import.meta.env.DEV) {
  configureClient({
    baseUrl: 'http://localhost:5000/api/addon/v1',  // Your local backend
    token: 'paste-your-test-token-here',  // Get from browser DevTools
  })
}

// Provider still needed for hooks to work
ReactDOM.createRoot(document.getElementById('root')!).render(
  <SyncBooksProvider>
    <App />
  </SyncBooksProvider>
)

How to get a test token:

  1. Register your add-on in the marketplace (draft mode)
  2. Install it on your org
  3. Open browser DevTools → Network tab
  4. Click your add-on in SyncBooks
  5. Find the iframe request — the URL contains syncbooks_token=eyJ...
  6. Copy that token value

Test tokens expire

Tokens expire after 1 hour. You'll need to get a new one each development session. For extended testing, see the Testing guide.

Provider Props

PropertyTypeDescription
childrenrequiredReactNodeYour addon's component tree
apiBaseUrlstringOverride the API base URL (for development)
tokenstringManually provide a session token (for development/testing)

Next Steps