The app handle
Every custom component receives app: AppHandle next to its props. It is the app runtime narrowed to what a screen may do, and every call it makes goes through the app's data API, so row-level security decides what is allowed.
On this page
Import only the type in a component: import type { AppHandle } from "@app/kit/primitives". The page that places the component builds the handle; a component never picks its own identity and never sees a token.
The handle groups its members by job. Identity, routing and capability checks are on this page; data, realtime, AI, server functions, payments, files and sign-in each have their own page.
Identity and routing
app.userproperty
The signed-in user: signedIn, name, email, role (the persona id, or null signed out) and refs, the id of this user's own row in each persona table.
app.user: AppUser- Returns
- AppUser. Read synchronously; it changes when the user signs in or out.
- Errors
- Does not throw.
- Permissions
- Identity is injected server-side from the session. The component cannot change it, and role reflects the account's live role (re-read every 15 seconds on the server).
export default function Greeting({ app }: { app?: AppHandle }) {
if (!app?.user.signedIn) return <p>Welcome, guest</p>;
return <p>Hello {app.user.name} ({app.user.role})</p>;
}Source: packages/appkit/src/kits/primitives/app-handle.ts:158
app.paramsproperty
The current page's route params. On a /tickets/:id page, params.id is the ticket id.
app.params: Record<string, string>- Returns
- Record<string, string>; empty on pages without params.
- Errors
- Does not throw.
- Permissions
- Plain values from the URL. Treat them as untrusted input: app.get still applies row-level security.
const ticket = await app.get("tickets", app.params.id);Source: packages/appkit/src/kits/primitives/app-handle.ts:162
app.memethod
My own row id in a persona entity. Shorthand for app.user.refs[entity].
app.me(entity: string): string | undefinedParameters
| Name | Type | Description |
|---|---|---|
entityrequired | string | A persona table, e.g. "member" or "customer". |
- Returns
- The row id, or undefined when the user has no row there (signed out, or another persona).
- Errors
- Does not throw.
- Permissions
- Read from the session's refs, set server-side at sign-in.
const memberId = app.me("member");
const mine = await app.query("bookings", { where: { member: memberId ?? "" } });Source: packages/appkit/src/kits/primitives/app-handle.ts:164
app.canmethod
Whether the current role has a capability on an entity, answered from the same access matrix the server's row-level security enforces. Use it to hide buttons, never as the only check.
app.can(cap: "create" | "edit" | "delete", entity?: string): booleanParameters
| Name | Type | Description |
|---|---|---|
caprequired | "create" | "edit" | "delete" | "create", "edit" or "delete". |
entityoptional | string | The table. Omitted, the answer is true (page-level chrome). |
- Returns
- true or false. An "own rows" scope answers true; the server then decides per row.
- Errors
- Does not throw.
- Permissions
- A UI hint. The server enforces the same rule on every write whatever this returns.
{app.can("delete", "tickets") && (
<button onClick={() => app.remove("tickets", id)}>Delete</button>
)}Source: packages/appkit/src/kits/primitives/app-handle.ts:189
Building a handle
useAppHandlefunction
Build the handle for a page in app code you own (an exported or self-hosted app). Compiled pages call it for you.
useAppHandle(user: { name?: string; email?: string; role?: string; refs?: Record<string, string> } | undefined, params: Record<string, string>): AppHandleParameters
| Name | Type | Description |
|---|---|---|
userrequired | { name?: string; email?: string; role?: string; refs?: Record<string, string> } | undefined | The chassis user: { name, email, role, refs }, or undefined signed out. |
paramsrequired | Record<string, string> | The page's route params. |
- Returns
- An AppHandle wired to the KitRuntime in context, or a stub handle when no runtime is mounted.
- Errors
- Does not throw.
- Permissions
- The handle only reaches the data API the runtime is configured for; identity still comes from the session.
function TicketPage({ user, id }: { user: SessionUser; id: string }) {
const app = useAppHandle(user, { id });
return <TicketDetail app={app} />;
}Source: packages/appkit/src/kits/primitives/app-handle.ts:344
stubAppHandlefunction
A handle that answers with empty data and refuses writes. The SSR gate renders components with it, and a page gets it when no runtime is mounted. Use it in tests and stories.
stubAppHandle(params?: Record<string, string>, auth?: AppAuth): AppHandleParameters
| Name | Type | Description |
|---|---|---|
paramsoptional | Record<string, string> | Route params to expose (default {}). |
authoptional | AppAuth | An AppAuth to expose (default: the sign-in verbs reject). |
- Returns
- AppHandle: user is a signed-out Guest, reads return [] / null, can() is false.
- Errors
- create, update, remove, transition and upload reject with "no app runtime here".
- Permissions
- No network access at all.
render(<TicketList app={stubAppHandle()} />);Source: packages/appkit/src/kits/primitives/app-handle.ts:317
Types
type AppUser = {
signedIn: boolean;
name: string;
email?: string;
role: string | null;
refs: Record<string, string>;
}Source: packages/appkit/src/kits/primitives/app-handle.ts:25