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
| Hook | Returns | Scope Required |
|---|---|---|
| useCustomers(params?) | Paginated customer list | customers.read |
| useCustomer(id) | Single customer | customers.read |
| useInvoices(params?) | Paginated invoice list | invoices.read |
| useInvoice(id) | Single invoice | invoices.read |
| useProducts(params?) | Paginated product list | products.read |
| useProduct(id) | Single product | products.read |
| useExpenses(params?) | Paginated expense list | expenses.read |
| usePayments(params?) | Paginated payment list | payments.read |
| useVendors(params?) | Paginated vendor list | vendors.read |
| useProjects(params?) | Paginated project list | projects.read |
| useProject(id) | Single project | projects.read |
| useEmployees(params?) | Paginated employee list | employees.read |
| useAccounts(params?) | Chart of accounts | accounts.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
- UI Components — display data with pre-built components
- Events & Webhooks — react to data changes in real-time