Docs / data / useCollection

useCollection

hooksince 0.1.0

Direct MongoDB CRUD for built-app data — the primary, reactive data hook for tables, forms, cards, and persistence.

import { useCollection } from 'flowstack-sdk'

The primary data hook — use it for all tables, forms, cards, and data persistence. The agent is NOT involved in data operations. Collection names are auto-prefixed with the site_id by the backend, so pass SHORT names only (e.g. 'transactions', not 'site_abc__transactions'). Reads are reactive: they auto-fetch on mount and re-fetch after mutations; every useCollection instance for the same collection auto-refreshes after a successful write.

Signature

const { documents, count, total, isLoading, error, refresh, insert, update, remove } = useCollection<T>(name, options?)

Parameters

ParamTypeNotesDescription
namestringrequiredShort collection name (auto-prefixed with site_id by the backend).
options.filterFilter<T>MongoDB query filter.
options.sortRecord<string, 1 | -1>Sort spec, e.g. { date: -1 }.
options.limitnumberdefault 50Max documents to return (max 500).
options.skipnumberdefault 0Pagination offset.
options.layer'user' | 'shared' | 'auto'default 'auto'Namespace: 'user' is a physically isolated per-user database (already scoped — don't filter by a user key; no other user can read it); 'shared' is one pool every signed-in user can read (app can't insert directly unless the collection is agent_writable) — it is NOT access-controlled per user; 'auto' resolves from the collection's data model. See two-layer-data-model.
options.refreshOnAgentCompletebooleansince 0.2.1Auto-refresh after an agent write tool (insert_documents/update_documents/insert_app_data/etc.) completes — the reactive bridge for the Reason→Write→Read pattern.

Returns

FieldTypeDescription
documentsT[]The fetched documents.
countnumberDocuments returned (≤ limit).
totalnumberTotal matching documents (for pagination).
isLoadingbooleanFetch in flight.
errorstring | nullLast error message.
refresh() => Promise<void>Manual re-fetch.
insert(doc: T | T[]) => Promise<{ inserted_ids: string[] }>Insert one or many documents.
update(filter, update, opts?) => Promise<{ modified_count: number }>Update matching documents.
remove(filter) => Promise<{ deleted_count: number }>Delete matching documents.

Examples

Reactive query
function TransactionTable() {
  const { documents, isLoading } = useCollection<Transaction>('transactions', {
    sort: { date: -1 }, limit: 50, refreshOnAgentComplete: true,
  });
  if (isLoading) return <div>Loading...</div>;
  return (
    <table>
      <tbody>
        {documents.map(row => (
          <tr key={row._id}><td>{row.date}</td><td>${row.amount}</td></tr>
        ))}
      </tbody>
    </table>
  );
}
Insert on form submit
const { insert } = useCollection('transactions');
await insert({ ...form, amount: Number(form.amount), created_at: new Date().toISOString() });
// All useCollection('transactions') instances auto-refresh
Update and delete
const { update, remove } = useCollection('transactions');
await update({ _id: docId }, { $set: { category: 'Food' } });
await remove({ _id: docId });
CriticalThe app reads all data with useCollection (tables, forms, cards, CRUD) — never parse the agent's chat text or messages[] for data, and never use useToolInvocation. Writes happen either via deterministic user actions (insert/update/remove) or via the agent's data_access tools followed by a useCollection read (Reason → Write → Read).
Critical`layer: 'shared'` is world-readable. Every signed-in user can read every row; the _flowstack.user_id field is a filter, not an ACL. Put anything one user must not see from another in layer: 'user' (a physically isolated DB). And note you cannot write into another user's per-user partition — there's no cross-user delivery primitive (no private DMs/notifications today). See two-layer-data-model.

See also

usePublicCollection · useAgent · useDatasets · useQuery · two-layer-data-model

Source: README.md#usecollection · Also available in llms-full.txt and registry.json.