standards
SDK

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-query

Provider 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:

OptionEffect
baseUrlAPI origin (required).
apiPrefixPath prefix, e.g. "/v1".
headersDefault headers on every request — put your Authorization here.
fetchCustom fetch, for token refresh or logging.
tenantId / tenantIdHeaderWorkspace scoping — a workspace is a tenant in the SDK (header defaults to x-tenant-id).
timeoutMsRequest timeout (default 30 000).
onUnauthorized401 handler — return true to retry the request once.
getAccessTokenToken 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);
  // ...
}
HookPurpose
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
HookVariablesNotes
useCreateRecord{ data, options?: { allowDraft? } }
useUpdateRecord{ recordId, data }Partial update.
useDeleteRecordrecordIdSoft delete (trash).
useBulkDeleteRecordsrecordId[]Returns { deleted, errors }.
useRestoreRecordrecordIdUn-trash.
usePurgeRecordrecordIdPermanent 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:

HookPurpose
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

HookPurpose
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 useruseMyProfile(), useUpdateMyProfile(), useMyPermissions().
  • Access controluseCanAccess(objectName, action) returns boolean | undefined; useObjectPermissions, useRoles and role mutations for admin.
  • Feature flagsuseFeatureFlag(name), useFeatureFlagValue(name, default), useTier(flagName, defaultTier), and the <Feature> gate component.
  • RealtimeuseLiveSubscription for live record updates, notifications via useNotifications / useNotificationCounts. Headless apps wrap with RealtimeProvider first; the app shell already includes it.
  • FormsuseForms, useFormByName, useCreateFormSubmission, useSaveFormStep, useAdvanceFormStep.
  • AgentsuseAgentDefinitions, useAgentSessions, useRunAgent, useAgentChatSession; create and update mutations accept visibility.

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 sendMessage fails 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.