/
Documentation

Fetching & Writing Data

Read, create, update, and delete records in SyncBooks using hooks or direct API calls.

Using Data Hooks (Recommended)

The SDK provides pre-built React hooks for common resources. They handle loading, errors, pagination, and auto-refetch.

Basic List Hook

CustomerList.tsx
import { useCustomers } from '@syncbooks/addon-sdk'

function CustomerList() {
  const {
    data,        // Customer[] — current page of results
    loading,     // boolean — true while fetching
    error,       // string | null — error message if failed
    total,       // number — total records across all pages
    page,        // number — current page (1-indexed)
    totalPages,  // number — total pages available
    hasMore,     // boolean — true if more pages exist
    nextPage,    // () => void — go to next page
    prevPage,    // () => void — go to previous page
    setPage,     // (n: number) => void — jump to specific page
    refetch,     // () => void — reload current page
  } = useCustomers({ limit: 20, search: 'Acme' })

  if (loading) return <p>Loading...</p>
  if (error) return <p>Error: {error}</p>

  return (
    <div>
      <p>{total} customers found</p>
      <ul>
        {data.map(customer => (
          <li key={customer._id}>
            <strong>{customer.name}</strong> — {customer.email}
          </li>
        ))}
      </ul>
      
      <div>
        <button onClick={prevPage} disabled={page <= 1}>Previous</button>
        <span>Page {page} of {totalPages}</span>
        <button onClick={nextPage} disabled={!hasMore}>Next</button>
      </div>
    </div>
  )
}

Available Resource Hooks

HookReturnsScope Required
useCustomers(params?)Paginated customer listcustomers.read
useCustomer(id)Single customercustomers.read
useInvoices(params?)Paginated invoice listinvoices.read
useInvoice(id)Single invoiceinvoices.read
useProducts(params?)Paginated product listproducts.read
useProduct(id)Single productproducts.read
useExpenses(params?)Paginated expense listexpenses.read
usePayments(params?)Paginated payment listpayments.read
useVendors(params?)Paginated vendor listvendors.read
useProjects(params?)Paginated project listprojects.read
useProject(id)Single projectprojects.read
useEmployees(params?)Paginated employee listemployees.read
useAccounts(params?)Chart of accountsaccounts.read
useMe()Current org + user info(none)
useConfig()Addon config for this org(none)

Direct API Calls

For more control, use the resource clients directly:

Reading Data

example.ts
import { customers, invoices, products, search } from '@syncbooks/addon-sdk'

// List with pagination + filters
const { data, total, totalPages, hasMore } = await customers.list({
  page: 1,
  limit: 20,
  search: 'John',    // text search
})

// Get a single record
const { data: customer } = await customers.get('507f1f77bcf86cd799439011')

// List invoices with status filter
const { data: overdue } = await invoices.list({ status: 'overdue', limit: 50 })

// Search across multiple resource types
const results = await search('Acme', {
  types: 'customers,invoices,products',
  limit: 10,
})
// results.data = [{ type: 'customer', _id, name }, { type: 'invoice', ... }]

Creating Records

create-examples.ts
import { customers, invoices, expenses } from '@syncbooks/addon-sdk'

// Create a customer
const { data: newCustomer } = await customers.create({
  name: 'Acme Corporation',
  email: 'hello@acme.com',
  phone: '+233201234567',
  company: 'Acme Corp',
  address: {
    street: '123 Main St',
    city: 'Accra',
    country: 'Ghana',
  },
})
console.log('Created:', newCustomer._id)

// Create an invoice
const { data: invoice } = await invoices.create({
  customerId: newCustomer._id,
  invoiceDate: '2026-08-13',
  dueDate: '2026-09-13',
  lineItems: [
    {
      description: 'Web Development (10 hours)',
      quantity: 10,
      rate: 500,
      amount: 5000,
      taxRate: 12.5,
      taxAmount: 625,
    },
    {
      description: 'Hosting (monthly)',
      quantity: 1,
      rate: 200,
      amount: 200,
      taxRate: 0,
      taxAmount: 0,
    },
  ],
  subtotal: 5200,
  taxAmount: 625,
  totalAmount: 5825,
  notes: 'Payment due within 30 days',
  terms: 'Net 30',
})

// Create an expense
const { data: expense } = await expenses.create({
  description: 'Office Supplies',
  amount: 350.50,
  date: '2026-08-13',
  category: 'office',
  notes: 'Printer paper and ink',
})

Updating Records

update-examples.ts
import { customers, invoices } from '@syncbooks/addon-sdk'

// Update a customer
const { data: updated } = await customers.update('customer_id', {
  phone: '+233209876543',
  address: { city: 'Kumasi' },
})

// Update invoice status
const { data: inv } = await invoices.update('invoice_id', {
  status: 'sent',
})

Deleting Records

delete-examples.ts
import { customers } from '@syncbooks/addon-sdk'

// Soft-delete (sets del_flag: true)
await customers.delete('customer_id')
// Returns: { success: true }

Mutations (Create/Update/Delete with Loading State)

The useMutation hook manages loading and error states for write operations:

CreateCustomerForm.tsx
import { useMutation, customers, ui } from '@syncbooks/addon-sdk'

function CreateCustomerForm() {
  const { mutate, loading, error, data, reset } = useMutation(
    (formData) => customers.create(formData)
  )

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    const form = new FormData(e.target as HTMLFormElement)
    
    const result = await mutate({
      name: form.get('name') as string,
      email: form.get('email') as string,
      phone: form.get('phone') as string,
    })

    if (result) {
      ui.notify(`Customer "${result.name}" created!`, 'success')
      reset() // Clear the mutation state
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Customer name" required />
      <input name="email" type="email" placeholder="Email" />
      <input name="phone" placeholder="Phone" />
      
      {error && <p className="text-red-500">{error}</p>}
      
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create Customer'}
      </button>
    </form>
  )
}

Specialized API Clients

Some resources have specialized methods beyond basic CRUD:

Reports

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

const { data: summary } = await reports.summary()
// { totalRevenue, totalExpenses, totalPayments, invoiceCount }

const { data: pnl } = await reports.profitLoss()
// { revenue: [...accounts], expenses: [...accounts], totalRevenue, totalExpenses, netProfit }

const { data: aging } = await reports.arAging()
// { current, days1to30, days31to60, days61to90, over90 }

Bank Transactions

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

// Categorize a transaction
await bankTransactions.categorize('txn_id', {
  accountId: 'account_id',
  category: 'office_expenses',
})

// Mark as reconciled
await bankTransactions.reconcile('txn_id')

Assets

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

// Record depreciation
await assets.depreciate('asset_id', {
  amount: 5000,
  date: '2026-08-13',
})

Subscriptions

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

// Get metrics
const { data: metrics } = await subscriptions.metrics()
// { active, cancelled, total, churnRate }

// Cancel a subscription
await subscriptions.cancel('subscriber_id', 'Customer requested')

Generic Hooks

Build custom hooks using the generic useQuery and usePaginatedQuery:

import { useQuery, usePaginatedQuery, reports, fleet } from '@syncbooks/addon-sdk'

// Custom single-value hook
function useReportSummary() {
  return useQuery(() => reports.summary(), [])
}

// Custom paginated hook
function useVehicles(status?: string) {
  return usePaginatedQuery(
    (params) => fleet.vehicles.list({ ...params, status }),
    { status },
    [status]  // re-fetch when status changes
  )
}

Use refetch for real-time updates

Call refetch() after a mutation to reload the list. Or use usePolling(() => refetch(), 30000) to auto-refresh every 30 seconds.

Error Handling

import { customers, formatError } from '@syncbooks/addon-sdk'

try {
  await customers.create({ name: '' }) // Missing required field
} catch (error: any) {
  console.log(error.status)    // 400
  console.log(error.message)   // "Customer name is required"
  console.log(error.response)  // Full error response from API
  
  // Or use the helper
  const msg = formatError(error) // "Customer name is required"
  ui.notify(msg, 'error')
}

Next Steps