React hooks
Fetch and mutate standards data from React with @stndrds/react.
@stndrds/react wraps the REST API in TanStack Query hooks. Every query hook returns a standard UseQueryResult (data, isLoading, error, refetch, …). Every mutation hook returns a UseMutationResult (mutate, mutateAsync, isPending, …). Everything TanStack Query offers works here.
pnpm add @stndrds/react @tanstack/react-queryProvider setup
StandardsAppProvider from @stndrds/ui already wires up SchemaClientProvider for you — see Client setup. The config below is for headless integrations.
Wrap the app in SchemaClientProvider. Auth is injected through headers or a custom fetch — there is no apiKey config field.
import { SchemaClientProvider } from "@stndrds/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<SchemaClientProvider
queryClient={queryClient} // share the app's QueryClient
config={{
baseUrl: "https://api.standards.new",
apiPrefix: "/v1",
headers: { Authorization: `Bearer ${accessToken}` },
}}
>
{children}
</SchemaClientProvider>
</QueryClientProvider>
);
}SchemaClientConfig options:
| Option | Effect |
|---|---|
baseUrl | API origin (required). |
apiPrefix | Path prefix, e.g. "/v1". |
headers | Default headers on every request — put your Authorization here. |
fetch | Custom fetch, for token refresh or logging. |
tenantId / tenantIdHeader | Workspace scoping — a workspace is a tenant in the SDK (header defaults to x-tenant-id). |
timeoutMs | Request timeout (default 30 000). |
onUnauthorized | 401 handler — return true to retry the request once. |
getAccessToken | Token supplier for SSE streams only. |
The provider also accepts renderFileViewer (renders file previews), hooks (client record lifecycle hooks built with recordHooks()), and translations (a French bundle ships in @stndrds/react/locales/fr).
For imperative access, useSchemaClient() returns the underlying SchemaClient.
Fetching records
Hooks are generic over the record's values shape — pair them with ExtractRecord for end-to-end typing.
import { useRecord, useRecords } from "@stndrds/react";
function ContactList() {
const { data, isLoading } = useRecords<Contact>("contacts", {
limit: 50,
sorts: [{ attribute: "lastName", direction: "asc" }],
filters: {
combinator: "and",
rules: [{ attribute: "status", operator: "is", value: "active" }],
},
});
if (isLoading) return <Spinner />;
return data?.data.map((record) => <li key={record.id}>{record.label}</li>);
}
function ContactDetail({ id }: { id: string }) {
const { data: contact } = useRecord<Contact>("contacts", id);
// ...
}| Hook | Purpose |
|---|---|
useRecords(objectName, options?) | List records. Options extend ListParams (below) plus enabled, keepPreviousData. Returns { data, page } where page = { limit, hasMore, total, countMode }. |
useRecord(objectName, recordId, options?) | One record. Options: enabled, includeDeleted. |
useArchivedRecords(objectName, options?) | Trashed records. |
useSearchRecords(objectName, query, options?) | Full-text search within an object. Options: minQueryLength, staleTime, select, plus list params. |
useInfiniteSearchRecords(objectName, query, options?) | Infinite-scroll search (pageSize default 20, sorts, filters). |
useGlobalSearch(query, options?) | Cross-object search — see Search. useGlobalSearchGrouped returns one group per object, and useGlobalArchivedSearch searches the trash. |
Records come back as ObjectRecord<TValues>:
{
id: string;
objectId: string;
label: string; // rendered labelExpression
values: TValues; // your attributes
createdAt: Date;
updatedAt: Date;
}createdAt and updatedAt are typed as Date but arrive as ISO strings over JSON — parse them before calling Date methods.
There is no draft/complete field on the record. CreateRecordOptions.allowDraft lets a create skip required-field validation, and isRecordComplete() checks completeness on demand — draft state is never stored on the record itself.
Filtering, sorting, and pagination
All list hooks accept the same ListParams:
{
limit?: number;
offset?: number;
countMode?: "none" | "estimated" | "exact";
sorts?: { attribute: string; direction: "asc" | "desc" }[];
filters?: FilterState;
fields?: string[]; // reference attributes to hydrate
}FilterState combines rules with and/or:
{
combinator: "and" | "or";
rules: {
attribute: string;
operator: FilterOperator;
value: FilterValue; // null for is_empty / is_not_empty
property?: string; // filter on a qualified relation property
quantifier?: "any" | "none";
}[];
}The valid operators depend on the attribute's type — see the operator table. A rule's value can also be resolved at query time rather than fixed: see dynamic values.
Filtering on dates
ListParams carries no timezone, so the hooks never send one and @today resolves in UTC. If your users filter on today's date, set the header once on the provider — headers: { "x-timezone": "Europe/Paris" } — so the day boundary lands where they expect.
Mutating records
Each mutation hook takes (objectName, options?) where options are { onSuccess, onError, onSettled }. All mutations update the cache optimistically.
import { useCreateRecord, useUpdateRecord, useDeleteRecord } from "@stndrds/react";
const { mutate: createContact, isPending } = useCreateRecord<ContactInput>("contacts", {
onSuccess: (record) => toast.success(`Created ${record.label}`),
});
createContact({ data: { firstName: "Ada", lastName: "Lovelace" } });
const { mutate: updateContact } = useUpdateRecord<Contact>("contacts");
updateContact({ recordId: id, data: { email: "[email protected]" } });
const { mutate: deleteContact } = useDeleteRecord("contacts");
deleteContact(id); // soft delete — restorable from trash| Hook | Variables | Notes |
|---|---|---|
useCreateRecord | { data, options?: { allowDraft? } } | |
useUpdateRecord | { recordId, data } | Partial update. |
useDeleteRecord | recordId | Soft delete (trash). |
useBulkDeleteRecords | recordId[] | Returns { deleted, errors }. |
useRestoreRecord | recordId | Un-trash. |
usePurgeRecord | recordId | Permanent delete — requires the manage permission. |
useSetRecordVisibility | { recordId, visibility } | Move a record between workspace and private; membership caches update optimistically. |
Schema hooks
Read (and administer) the schema itself:
| Hook | Purpose |
|---|---|
useObjects(options?) | All object definitions (systemOnly / customOnly filters). |
useObject(objectId) | One definition — by id, not name. |
useObjectsByNames(names) | Definitions by name. |
useAttributes(objectId) / useAttribute(objectId, attrId) | Attribute definitions. |
useViews / useViewsForObject(objectName) / useDefaultView(objectName) | View definitions. |
useRelationOptions(attributeId, params) / useResolveRelations(attributeId, ids) | Options and label resolution for relation pickers. |
Admin mutations exist for all of these (useCreateObject, useUpdateObject, useDeleteObject, useAddAttribute, useUpdateAttribute, useDeleteAttribute, useCreateView, useUpdateView, useSetDefaultView, …) with the same { onSuccess, onError, onSettled } options.
Documents and files
| Hook | Purpose |
|---|---|
useRecordDocuments({ recordId, objectName }) | Documents attached to a record, grouped by attribute. |
useDocument(id) / useDocumentFiles(id) | One document and its files (pack model — position order, no slot names). |
useCreateDocument() / useUpdateDocument() / useDeleteDocument() | Document CRUD. |
useAttachFile() / useDetachFile() / useUploadAndAttachFile() | Add or remove a file from a document's pack. |
useUploadFiles() | Upload raw files ({ files, options?: { folderPath? } }). |
useFile(id) / useFileUrl(id) | File metadata and a signed URL. |
Folder navigation hooks (useFolderChildren, useFolderAncestors, useCreateFolder, useMoveDocument, …) cover the drive UI.
Users, permissions, and the rest
- Current user —
useMyProfile(),useUpdateMyProfile(),useMyPermissions(). - Access control —
useCanAccess(objectName, action)returnsboolean | undefined;useObjectPermissions,useRolesand role mutations for admin. - Feature flags —
useFeatureFlag(name),useFeatureFlagValue(name, default),useTier(flagName, defaultTier), and the<Feature>gate component. - Realtime —
useLiveSubscriptionfor live record updates, notifications viauseNotifications/useNotificationCounts. Headless apps wrap withRealtimeProviderfirst; the app shell already includes it. - Forms —
useForms,useFormByName,useCreateFormSubmission,useSaveFormStep,useAdvanceFormStep. - Agents —
useAgentDefinitions,useAgentSessions,useRunAgent,useAgentChatSession; create and update mutations acceptvisibility.
Agent chat sessions
useAgentChatSession manages an interactive agent conversation with message history and real-time streaming. Its error field surfaces failures across the full session lifecycle:
- Message send errors — when
sendMessagefails to submit new input. - Session creation errors — when creating a new session fails.
- Session cancellation errors — when stopping an in-progress session fails.
- Message load errors — when fetching the session's existing conversation history fails on mount. This distinguishes a session whose history could not be loaded from one that genuinely has no messages yet — both leave the messages list empty, but only a load failure sets
error.
error resolves in a fixed order, not by recency: send failure, then first-message failure, then session creation, then cancellation, and finally the load failure. The interactive failures come first because they answer a gesture the user just made; a load failure is ambient, so it only shows when nothing more immediate is pending.
Cache invalidation
The package exports query-key factories so you can invalidate exactly the queries you touched when mutating outside the hooks:
import { documentsKeys, recordsKeys } from "@stndrds/react";
queryClient.invalidateQueries({
queryKey: documentsKeys.recordDocuments(objectName, recordId),
});Available factories: recordsKeys, objectsKeys, attributesKeys, viewsKeys, documentsKeys, filesKeys, usersKeys, permissionsKeys, notificationKeys, formsKeys, agentKeys, and more.
Error handling
API errors are instances of SchemaApiError, which exposes .status and .details — every hook surfaces them through its error field.