In order to use the React hooks, make sure you wrap your <App /> inside <ClientProvider />. This provides the bknd API instance to all hooks in your component tree:
import { ClientProvider } from "bknd/client";export default function App() { return <ClientProvider>{/* your app */}</ClientProvider>;}
import { ClientProvider } from "bknd/client";export default function App() { return ( <ClientProvider baseUrl="https://your-bknd-instance.com"> {/* your app */} </ClientProvider> );}
Using with an embedded bknd instance (same origin):
import { ClientProvider } from "bknd/client";export default function App() { // no baseUrl needed - will use window.location.origin return <ClientProvider>{/* your app */}</ClientProvider>;}
Using with custom authentication:
import { ClientProvider } from "bknd/client";export default function App() { return ( <ClientProvider baseUrl="https://your-bknd-instance.com" token="your-auth-token" onAuthStateChange={(state) => { console.log("Auth state changed:", state); }} > {/* your app */} </ClientProvider> );}
For all examples below, we'll assume that your app is wrapped inside the ClientProvider.
Returns the Api instance from the ClientProvider context. This gives you direct access to all API methods for data, auth, media, and system operations.
import { useApi } from "bknd/client";export default async function App() { const api = useApi(); // access data API const posts = await api.data.readMany("posts"); // access auth API const user = await api.auth.me(); // access media API const files = await api.media.listFiles(); // ...}
Props:
Prop
Type
Default
host?
string
-
See the SDK documentation for all available API methods and options.
Provides authentication state and helper functions for login, register, logout, and token management. This hook automatically tracks the authentication state from the ClientProvider context.
Use this pattern when your frontend and backend are deployed on the same domain or when using a framework that serves both. Authentication is handled via HTTP-only cookies.
import { ClientProvider, useAuth } from "bknd/client";import { useEffect } from "react";// setup ClientProvider with credentials includedexport default function App() { return ( <ClientProvider baseUrl="https://your-app.com" credentials="include" > <InnerApp /> </ClientProvider> );}function InnerApp() { const auth = useAuth(); // important: verify auth on mount since cookies aren't readable from client-side JavaScript // cookies are included automatically in requests useEffect(() => { auth.verify(); }, []); if (auth.user) { return ( <div> <p>Logged in as {auth.user.email}</p> {/* logout by navigating to the logout endpoint */} <a href="/api/auth/logout">Logout</a> </div> ); } // option 1: programmatic login return ( <button onClick={async () => { await auth.login({ email: "user@example.com", password: "password" }); }} > Login </button> ); // option 2: form-based login (traditional) return ( <form method="post" action="/api/auth/password/login"> <input type="email" name="email" placeholder="Email" /> <input type="password" name="password" placeholder="Password" /> <button type="submit">Login</button> </form> );}
Notes:
With credentials: "include", cookies are automatically sent with every request
The logout endpoint (/api/auth/logout) clears the cookie and redirects back to the referrer
You can use either programmatic login with auth.login() or traditional form submission
Use this pattern when bknd is embedded in your framework (e.g., Next.js, Astro, React Router). The backend and frontend run in the same process.
// this example is not specific to any framework, but you can use it with any framework that supports server-side renderingimport { ClientProvider, useAuth } from "bknd/client";import { useEffect } from "react";// setup: extract user from server-side// in your server-side code (e.g., Next.js loader, Astro endpoint):export async function loader({ request }) { // create API instance from your app (may be available in context) const api = app.getApi({ request }); // extracts credentials from request // or: const api = app.getApi({ headers: request.headers }); const user = api.getUser(); // optionally: await api.verifyAuth(); return { user };}// in your component:export default function App({ user }) { return ( <ClientProvider user={user}> <InnerApp /> </ClientProvider> );}function InnerApp() { const auth = useAuth(); // optionally verify if not already verified useEffect(() => { if (!auth.verified) { auth.verify(); } }, []); if (auth.user) { return ( <div> <p>Logged in as {auth.user.email}</p> {/* logout by navigating to the logout endpoint */} <a href="/api/auth/logout">Logout</a> </div> ); } // use form-based authentication for full-stack apps return ( <form method="post" action="/api/auth/password/login"> <input type="email" name="email" placeholder="Email" /> <input type="password" name="password" placeholder="Password" /> <button type="submit">Login</button> </form> );}
Notes:
No baseUrl needed in ClientProvider - it automatically uses the same origin
Pass the user prop from server-side to avoid an initial unauthenticated state
Use app.getApi({ request }) or app.getApi({ headers }) on the server to extract credentials
The logout endpoint (/api/auth/logout) clears the session and redirects back
Authentication persists via cookies automatically handled by the framework
To query and mutate data using this hook, you can leverage the parameters returned. In the
following example we'll also use a refine function as well as revalidateOnFocus (option from
SWRConfiguration) so that our data keeps updating on window focus change.
import { useEffect, useState } from "react";import { useApiQuery } from "bknd/client";export default function App() { const [text, setText] = useState(""); const { data, api, mutate, ...q } = useApiQuery( (api) => api.data.readOne("comments", 1), { // filter to a subset of the response refine: (data) => data.data, revalidateOnFocus: true, }, ); const comment = data ? data : null; useEffect(() => { setText(comment?.content ?? ""); }, [comment]); if (q.error) return <div>Error</div>; if (q.isLoading) return <div>Loading...</div>; return ( <form onSubmit={async (e) => { e.preventDefault(); if (!comment) return; // this will automatically revalidate the query await mutate(async () => { const res = await api.data.updateOne("comments", comment.id, { content: text, }); return res.data; }); return false; }} > <input type="text" value={text} onChange={(e) => setText(e.target.value)} /> <button type="submit">Update</button> </form> );}
This hook wraps the actions from useEntity around SWR for automatic data fetching, caching, and revalidation. It combines the power of SWR with CRUD operations for your entities.
import { useEntityQuery } from "bknd/client";export default function App() { const { data } = useEntityQuery("comments", 1); return <pre>{JSON.stringify(data, null, 2)}</pre>;}
Important: The returned CRUD actions are typed differently based on whether you provide an id:
With id (single item mode): update and _delete don't require an id parameter since the item is already specified
Without id (list mode): update and _delete require an id parameter to specify which item to modify
// single item mode - id is already specifiedconst { data, update, _delete } = useEntityQuery("comments", 1);await update({ content: "new text" }); // no id neededawait _delete(); // no id needed// list mode - must specify which item to update/deleteconst { data, update, _delete } = useEntityQuery("comments");await update({ content: "new text" }, 1); // id requiredawait _delete(1); // id required
Auto-revalidation: By default, all mutations (create, update, _delete) automatically revalidate all queries for that entity. This ensures your UI stays in sync.
Optimistic updates: For more advanced scenarios, you can use mutateRaw to implement optimistic updates manually.
Disable auto-revalidation: If you need more control, set revalidateOnMutate: false: