Appbricx
Docs
FeaturesPricingStart free

Get started

OverviewWhat Appbricx isQuickstart with a coding agentQuickstart with the builderCore concepts

Guides

Data and tablesAccess and rolesBusiness rulesSign-inCustom screensRealtime and topicsWorkflows, schedules and webhooksServer functionsAI in your appPaymentsFilesMessagesIntegrationsVerifyPublishing, domains and rollbackOperating your appExport and self-hostSelf-host install

Tutorials

Build a booking app with Claude CodeMulti-user todo listClient portalSlack on new sign-upTemplates

Reference

Reference homeFrontend SDKHTTP APIMCP toolsCLIIntegrations catalogContract formatAI key resolutionRun Appbricx locally

Use your data from React

The frontend SDK, @app/kit, is how your screens talk to your app: list and change rows, follow changes as they happen, upload a photo, ask the app's AI, or check what the signed-in user may do. You never handle tokens or write fetch calls, and you never have to remember a permission check. Every call goes through your app's data API, so the database decides what each user can see and change.

Two ways in

  • The app handle. Every custom component in your contract receives app as a prop. It is the whole SDK narrowed to what a screen may do, and it is what your coding agent uses most.
  • React hooks. In app code you own in full (an exported or self-hosted app, or pages you write by hand), the same calls are available as hooks: useAppHandle, useEntityQuery, useRecord, useAggregate and usePlatformSession.

The app handle

import type { AppHandle } from "@app/kit/primitives";

type Props = { app?: AppHandle };

export default function MyJobs({ app }: Props) {
  if (!app) return null;
  return <p>Hello {app.user.name}, you are signed in as {app.user.role}.</p>;
}
MemberWhat it does
usersignedIn, name, email?, role (or null signed out), refs
me(entity)The id of my own row in a user table, for example my member row
query(entity, q?)A server-side page: where (equality filters), sort, limit, offset. Returns { rows, total }
get(entity, id)One row, or null when it does not exist or the user may not read it
create(entity, values)Creates a row and returns it. Owner columns are stamped by the server, so pass only the row's own fields
update(entity, id, patch), remove(entity, id)Change or delete a row
transition(entity, id, field, to)Move a status field along its lifecycle (a plain write to it is refused)
upload(file)Uploads a file and returns { url, name }. See Files and payments
live(entity)Follow changes to a table as they happen. Returns the unsubscribe function
can(cap, entity?)Can this role create, edit or delete? A hint for hiding buttons; the server enforces it either way
ai.ask, ai.chatAsk the app's AI. See AI in your app
authSign in, sign up and sign out. See Sign-in methods
rows, loading, revision, params, navigateThe newest readable rows held in memory, a counter that moves when a table changes, route params, and navigation

Read and write

const { rows, total } = await app.query("job", {
  where: { status: "open" },
  sort: { field: "due_on", dir: "asc" },
  limit: 20,
});

const job = await app.create("job", { title: "Fix the boiler", due_on: "2026-11-02" });
await app.update("job", job.id as string, { notes: "Bring a ladder" });
await app.transition("job", job.id as string, "status", "in_progress");

If the signed-in user may not do something, the call rejects with a clear message. Catch it and show it; you do not need your own permission logic.

Realtime: a live chat

Call app.live(entity) in an effect and return the unsubscribe it gives you. Every change to the table, from any user or workflow, moves app.revision(entity), so a query that depends on it refetches by itself.

useEffect(() => app.live("message"), [app]);

const [messages, setMessages] = useState<Row[]>([]);
useEffect(() => {
  app.query("message", { where: { room_id: roomId }, sort: { field: "created_at" } })
    .then((r) => setMessages(r.rows));
}, [app, roomId, app.revision("message")]);

Show actions only to people who can use them

{app.can("delete", "job") && (
  <button onClick={() => app.remove("job", job.id as string)}>Delete</button>
)}

Hooks for app code you own

These are for the React app itself, outside contract components (contract components use the app prop above). Each hook returns { data, loading, refreshing, error, retry } and refetches when the table changes.

import { useEntityQuery, useRecord, useAggregate } from "@app/kit/kits/runtime";

// A page of open jobs, plus the total count
const jobs = useEntityQuery("job", {
  filter: [{ field: "status", op: "eq", value: "open" }],
  sort: { field: "due_on", dir: "asc" },
  limit: 25,
});

// One record (data === null means it does not exist)
const job = useRecord("job", params.id);

// A number for a stat: count, sum, avg, min or max
const overdue = useAggregate("job", {
  metric: "count",
  filter: [{ field: "due_on", op: "lt", value: today }],
});

Filters support eq, neq, in, not_in, lt, lte, gt, gte, contains, is_null, is_not_null and range (a named date window). Totals and aggregates are computed on the server over the whole table, so they stay right however large it grows.

Payments, team and search

The runtime behind the hooks also carries the app's payment, team and full-text search features:

import { useKitRuntime } from "@app/kit/kits/runtime";

const rt = useKitRuntime();

// Payments: "offline" always, "card" once Stripe is connected
const { methods } = await rt.pay!.methods();
await rt.pay!.checkout("order", orderId, methods.includes("card") ? "card" : "offline");

// Team: list, invite and change the app's users (admin session required)
const { members, roles } = await rt.team!.list();
await rt.team!.invite({ email: "sam@acme.com", name: "Sam", password: tempPassword, role: "technician" });

// Full-text search across your tables, scoped to what the user may read
const hits = await rt.search!("boiler"); // [{ entity, row }]

Sign-in in your own app shell

usePlatformSession owns the session: sign in, sign up, sign out, password reset and social sign-in. SignInGate shows your app's sign-in screen until there is a session.

import { usePlatformSession, SignInGate } from "@app/kit/kits/platform-auth";

const auth = usePlatformSession({ apiBase: "", projectToken });
const [skipped, setSkipped] = useState(false);

return (
  <SignInGate
    session={auth.session}
    signIn={auth.signIn}
    signUp={auth.signUp}
    requestReset={auth.requestReset}
    startProvider={auth.startProvider}
    discovery={auth.discovery}
    skipped={skipped}
    onSkip={() => setSkipped(true)}
    brandName="FieldOps"
  >
    <App />
  </SignInGate>
);

// A "Continue with Google" button anywhere:
<button onClick={() => auth.startProvider("google")}>Continue with Google</button>

auth.discovery tells you which sign-in providers are on and whether public sign-up is open, so your screen only shows what works.

When something is not available

In an offline preview there is no server, so upload, ai and payments reject with a message, and reads return demo data. Wrap those calls in try/catch and show a fallback.

Related: Your screens and brand · REST API · Live updates · Access per user

Appbricx

The backend, runtime and hosting for apps your coding agent builds. On our cloud, in yours, or on your own servers.

Features

Coding agents and MCPVerify testingBuilder consolePostgres databaseData APIRealtimeRow-level securitySign-in and authPaymentsWorkflows and jobsAI in your appIntegrationsBackups and rollbackHosting and domainsSelf-host and exportAll features

Product

ShowcasePricingFor coding agentsTemplatesPrivate cloudBring your own AI keyFAQ

Compare

Appbricx vs SupabaseLovable alternativeBolt alternativeFor freelancersFor agencies

Resources

DocumentationReferenceBlogSupportContact

© 2026 Appbricx. All rights reserved.

TermsPrivacyCookiesAcceptable Use