stack/docs-mintlify/guides/getting-started/user-fundamentals.mdx
2026-06-16 10:37:58 -07:00

331 lines
11 KiB
Plaintext

---
title: User Fundamentals
description: Access and manage user information within custom components
sidebarTitle: User Fundamentals
---
import { UserFieldsTable } from "/snippets/user-fields-table.jsx";
You've set up Hexclave. Now let's understand the most important object in your application: the **User**.
The user object represents whoever is currently interacting with your app — their identity, profile, and metadata. Almost everything you build will revolve around it: retrieving the current user, protecting pages from unauthorized access, updating profile information, signing out, and more.
## Getting the current user
On both client and server, you can use the `getUser()` function to get the current user (or `null` if not signed in). In React apps, there is also a `useUser()` hook which automatically updates when the value changes.
<CodeGroup dropdown>
```ts my-app.ts
import { hexclaveClientApp } from "../src/stack/client";
const user = await hexclaveClientApp.getUser();
if (user) {
console.log("Signed in: " + (user.displayName ?? user.primaryEmail ?? "<Unnamed>"));
} else {
console.log("Not signed in");
}
```
```tsx my-react-component.tsx
"use client";
import { hexclaveClientApp } from "../src/stack/client";
// Like most `getXyz()` or `listXyz()` functions, Hexclave provides a `useUser()` hook equivalent to `getUser()`.
// It behaves the same, but returns the user directly instead of a Promise, and updates when the user object changes.
// Note: In Server Components, you can still use `await hexclaveServerApp.getUser()`.
export default async function MyReactComponent() {
const user = hexclaveClientApp.useUser();
if (user) {
return <div>Hello, {user.displayName ?? user.primaryEmail ?? "anon"}</div>;
} else {
return <div>You are not logged in</div>;
}
}
```
</CodeGroup>
### Protecting a page & requiring a signed-in user
Sometimes, you want to retrieve the user only if they're signed in, and redirect to the sign-in page otherwise. In this case, simply pass `{ or: "redirect" }`, and the function will never return `null`.
You can also use `{ or: "throw" }` to throw an error instead — useful in API routes and server actions where a redirect doesn't make sense.
In both cases, the return type is non-nullable, so you don't need to handle `null`.
<CodeGroup dropdown>
```ts my-app.ts
import { hexclaveClientApp } from "../src/stack/client";
const user = await hexclaveClientApp.getUser({ or: "redirect" });
// user is guaranteed to be non-null here
console.log("Signed in: " + (user.displayName ?? user.primaryEmail ?? "<Unnamed>"));
```
```tsx my-react-component.tsx
"use client";
import { hexclaveClientApp } from "../src/stack/client";
export default function MyReactComponent() {
const user = hexclaveClientApp.useUser({ or: "redirect" });
// user is guaranteed to be non-null here
return <div>{`Hello, ${user.displayName ?? user.primaryEmail ?? "anon"}`}</div>;
}
```
</CodeGroup>
## User data
### What's on a user object?
Before diving into updates, here's an overview of the most important fields available on every user:
<UserFieldsTable />
For the full list of fields and methods, see the [User SDK reference](/sdk/types/user).
### Updating a user
You can update attributes on a user object with the `user.update()` function.
<CodeGroup dropdown>
```ts my-app.ts
import { hexclaveClientApp } from "../src/stack/client";
const user = await hexclaveClientApp.getUser();
await user.update({ displayName: "New Name" });
```
```tsx my-client-component.tsx
'use client';
import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package
export default function MyClientComponent() {
const user = useUser();
return <button onClick={async () => await user.update({ displayName: "New Name" })}>
Change Name
</button>;
}
```
</CodeGroup>
### Custom metadata
Beyond built-in fields like `displayName` and `primaryEmail`, you can store your own JSON-like data on a user. Hexclave provides three metadata fields, each with different read and write permissions:
| Field | Client access | Server access | Use for |
| --- | --- | --- | --- |
| `clientMetadata` | Read and write | Read and write | Non-sensitive user preferences, such as theme, locale, onboarding form drafts, or UI state. |
| `serverMetadata` | No access | Read and write | Sensitive or internal data that users should not be able to see or modify. |
| `clientReadOnlyMetadata` | Read only | Read and write | Server-authoritative data that the client should display, such as subscription plans, role labels, or validated onboarding state. |
Use `clientMetadata` for information that is safe for the browser to read and change. For example, a user can store their own theme preference:
<CodeGroup dropdown>
```ts my-app.ts
import { hexclaveClientApp } from "../src/stack/client";
const user = await hexclaveClientApp.getUser();
await user.update({
clientMetadata: {
theme: "dark",
},
});
```
```tsx my-client-component.tsx
'use client';
import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package
export default function ThemeToggle() {
const user = useUser({ or: "redirect" });
const currentTheme = user.clientMetadata?.theme ?? "light";
return (
<button onClick={async () => {
await user.update({
clientMetadata: {
...user.clientMetadata,
theme: currentTheme === "light" ? "dark" : "light",
},
});
}}>
Switch to {currentTheme === "light" ? "dark" : "light"} mode
</button>
);
}
```
</CodeGroup>
Use `serverMetadata` for sensitive or internal data. It is only available through [`HexclaveServerApp`](/sdk/objects/hexclave-app#hexclaveserverapp):
<CodeGroup dropdown>
```ts my-server-file.ts
import { hexclaveServerApp } from "../src/stack/server";
const user = await hexclaveServerApp.getUser();
await user.update({
serverMetadata: {
internalFlag: true,
},
});
console.log(user.serverMetadata);
```
</CodeGroup>
Use `clientReadOnlyMetadata` when the client needs to read a value but only your server should decide it. A common example is storing a validated onboarding or subscription state:
<CodeGroup dropdown>
```ts my-server-file.ts
import { hexclaveServerApp } from "../src/stack/server";
const user = await hexclaveServerApp.getUser({ or: "throw" });
await user.update({
clientReadOnlyMetadata: {
subscriptionPlan: "premium",
onboarded: true,
},
});
```
```tsx my-client-component.tsx
'use client';
import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package
export default function SubscriptionBadge() {
const user = useUser({ or: "redirect" });
const plan = user.clientReadOnlyMetadata?.subscriptionPlan ?? "free";
return <div>Current plan: {plan}</div>;
}
```
</CodeGroup>
<Warning>
Do not put secrets or trusted authorization decisions in `clientMetadata`. Users can read and modify it from client-side code. If the value affects access control, validate it on your server and store it in `serverMetadata` or `clientReadOnlyMetadata`.
</Warning>
## Signing out
You can sign out the user by calling `user.signOut()`. They will be redirected to the URL [configured as `afterSignOut` in the Hexclave App](/sdk/objects/hexclave-app).
<CodeGroup dropdown>
```ts my-app.ts
import { hexclaveClientApp } from "../src/stack/client";
const user = await hexclaveClientApp.getUser();
await user.signOut();
```
```tsx my-client-component.tsx
'use client';
import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package
export default function MyClientComponent() {
const user = useUser();
return <button onClick={() => user.signOut()}>Sign Out</button>;
}
```
</CodeGroup>
## Example: Custom profile page
Hexclave automatically creates a user profile on sign-up. Let's build a page that displays this information. In `app/profile/page.tsx`:
<CodeGroup dropdown>
```tsx my-app.tsx
"use client";
import { useUser, useHexclaveApp, UserButton } from "../src/stack/client";
export default function PageClient() {
const user = useUser();
const app = useHexclaveApp();
return (
<div>
{user ? (
<div>
<UserButton />
<p>Welcome, {user.displayName ?? "unnamed user"}</p>
<p>Your e-mail: {user.primaryEmail}</p>
<button onClick={() => user.signOut()}>Sign Out</button>
</div>
) : (
<div>
<p>You are not logged in</p>
<button onClick={() => app.redirectToSignIn()}>Sign in</button>
<button onClick={() => app.redirectToSignUp()}>Sign up</button>
</div>
)}
</div>
);
}
```
```tsx my-client-component.tsx
"use client";
import { hexclaveClientApp, UserButton } from "../src/stack/client";
export default function MyClientComponent() {
const user = hexclaveClientApp.useUser();
const app = hexclaveClientApp.useHexclaveApp();
return (
<div>
{user ? (
<div>
<UserButton />
<p>Welcome, {user.displayName ?? "unnamed user"}</p>
<p>Your e-mail: {user.primaryEmail}</p>
<p><button onClick={() => user.signOut()}>Sign Out</button></p>
</div>
) : (
<div>
<p>You are not logged in</p>
<p><button onClick={() => app.redirectToSignIn()}>Sign in</button></p>
<p><button onClick={() => app.redirectToSignUp()}>Sign up</button></p>
</div>
)}
</div>
);
}
```
</CodeGroup>
After saving your code, you can see the profile page on [http://localhost:3000/profile](http://localhost:3000/profile).
For more examples on how to use the `User` object, check the [the SDK documentation](/sdk/types/user).
## Anonymous users
Hexclave supports anonymous users - users who can interact with your app without signing up. This is useful for features like guest checkouts, try-before-you-sign-up flows, or collecting analytics before a user creates an account. If you have the analytics app enabled, anonymous users will automatically be created and visible in the Users table.
In most ways, anonymous users are just like any other, however, there are some key differences:
- Unless opted in, anonymous users cannot access any backend functions that require a signed-in user.
- By default, anonymous users are not returned by `getUser()`, `useUser()`.
- Anonymous users have a different set of [JWT keys](/guides/apps/authentication/jwts), which means they cannot access protected routes or API endpoints.
- Anonymous users are always [restricted](/guides/apps/authentication/restricted-users).
To get an anonymous user, pass `{ or: "anonymous" }` to `useUser()` or `getUser()`. This will create an anonymous user if the user isn't signed in.
<CodeGroup dropdown>
```ts my-app.ts
import { hexclaveClientApp } from "../src/stack/client";
const user = await hexclaveClientApp.getUser({ or: "anonymous" });
// user is guaranteed to be non-null here
console.log("Signed in: " + (user.displayName ?? user.primaryEmail ?? "<Unnamed>"));
```
```tsx my-client-component.tsx
"use client";
import { hexclaveClientApp } from "../src/stack/client";
export default function MyComponent() {
const user = useUser({ or: "anonymous" });
// user is always non-null — either a real user or an anonymous one
return <div>Your user ID: {user.id}</div>;
}
```
</CodeGroup>
Anonymous users have an `isAnonymous` property set to `true`.