stack/docs-mintlify/sdk/objects/stack-app.mdx
BilalG1 57ff5d3ce9
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
feat(hexclave): PR 2 — visible rebrand (Hexclave brand goes public) (#1481)
## Summary

**Stacked on [#1475](https://github.com/hexclave/stack-auth/pull/1475)**
(`cl/hexclave-pr1`, the invisible compatibility layer). Diff vs that
base = the actual PR 2 code.

This is **PR 2 of the Stack Auth → Hexclave rebrand: the visible flip**.
Old wire identifiers (cookies, request/response headers, Bearer prefix,
JWT issuers, MCP tool name) keep working indefinitely via PR 1's
dual-accept. This PR flips every user-visible surface — package names
taught in docs, SDK class names in code examples, dashboard setup
snippets, page titles, error messages, email content, CLI binary,
default base URLs, GitHub repo slug, contributor guidance — to the
Hexclave brand.

See [`RENAME-TO-HEXCLAVE.md`](./RENAME-TO-HEXCLAVE.md) → *"PR 2: Rebrand
to Hexclave (visible)"* for the full per-work-area spec.

## What's implemented (per the plan's PR 2 scope)

- **SDK base URLs** flipped: `defaultBaseUrl` and
`defaultAnalyticsBaseUrl` in
[common.ts](packages/template/src/lib/stack-app/apps/implementations/common.ts:127)
→ `https://api.hexclave.com` / `https://r.hexclave.com`. PR 1's
[`getHardcodedFallbackUrls`](packages/stack-shared/src/utils/urls.tsx:199)
table now keys on the Hexclave domain.

- **Domain inventory sweep** (16 subdomains from the plan): every
`api/app/docs/discord/demo/mcp/skill/feedback/test/preview/r/api2/api.staging/idp-jwk-audience/built-with.stack-auth.com`
reference in production code, docs-mintlify, examples, READMEs, and
contributor guidance flipped to `*.hexclave.com`. Carve-outs: PR 1's
intentional JWT issuer dual-accept table in
[tokens.tsx](apps/backend/src/lib/tokens.tsx), the legacy `./docs/`
folder, the `unified-docs-widget` allowlist (deliberately accepts both
during DNS transition), and `url-targets.ts` hosted-component default
(baked into existing customer deploys).

- **`@deprecated` JSDoc** on every `Stack*` public export
([packages/template/src/lib/stack-app/index.ts](packages/template/src/lib/stack-app/index.ts)
+ [packages/template/src/index.ts](packages/template/src/index.ts)) —
`StackClientApp`, `StackServerApp`, `StackAdminApp` + every
constructor/options/JSON type, `StackHandler`, `StackProvider`,
`StackTheme`, `useStackApp`, `defineStackConfig`, `StackConfig`.
Hexclave\* aliases are now canonical.

- **Runtime `console.warn`**
([packages/template/src/internal/deprecation-warning.ts](packages/template/src/internal/deprecation-warning.ts))
— once-per-process when the SDK is loaded from a `@stackframe/*`
artifact. Detection uses the existing
`STACK_COMPILE_TIME_CLIENT_PACKAGE_VERSION_SENTINEL` (rewritten at build
time to e.g. `js @stackframe/stack@2.8.92` or `js
@hexclave/next@1.0.0`); `@hexclave/*` mirror artifacts short-circuit the
warning.

- **Tier 3 data migration**: new idempotent SQL migration
[`20260523000000_rename_internal_project_to_hexclave`](apps/backend/prisma/migrations/20260523000000_rename_internal_project_to_hexclave/migration.sql)
— updates the internal Project `displayName` 'Stack Dashboard' →
'Hexclave Dashboard' and `description` only if both still hold the
pre-rebrand defaults. Operator-renamed projects untouched, missing row
no-ops, re-runs are no-ops. [`seed.ts`](apps/backend/prisma/seed.ts:87)
default flipped. `getSharedEmailConfig("Stack Auth")` → `("Hexclave")`.

- **Tier 4 brand strings** (mechanical sweep, ~340 files):
- Page + OpenAPI titles (Hexclave API / Dashboard / REST API / Webhooks
API / Documentation). OpenAPI `info.description` documents
`X-Hexclave-*` headers as canonical with compat note on `X-Stack-*`.
- `HexclaveAssertionError` message text
([errors.tsx:71](packages/stack-shared/src/utils/errors.tsx:71)) — "an
error in Stack." → "an error in Hexclave."
- Known-error message templates
([known-errors.tsx](packages/stack-shared/src/known-errors.tsx)) flipped
to lead with `x-hexclave-*` + the new `docs.hexclave.com` URL; legacy
`x-stack-*` mentioned as compat aliases. **25 e2e test files updated in
lockstep**.
- Email content: failed-emails-digest body, sendTestEmail recipient (now
`sent-with-hexclave.com`), test-email-recipient default.
  - `CHANGELOG.md` title → "Hexclave Changelog".
- `AGENTS.md` env var convention: new vars prefix `HEXCLAVE_` /
`NEXT_PUBLIC_HEXCLAVE_` for Category A/B; legacy `STACK_*` explicitly
noted as accepted via PR 1's dual-read.

- **CLI / init wizard**:
- Every dashboard setup snippet, init-stack template, and docs-mintlify
page teaches `npx @hexclave/cli@latest init` (was
`@stackframe/stack-cli`).
[setup-page.tsx](apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx)
+
[link-existing-onboarding](apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/link-existing-onboarding.tsx).
- [init-stack](packages/init-stack/src/index.ts:634)
`STACK_*_INSTALL_PACKAGE_NAME_OVERRIDE` defaults flipped to
`@hexclave/*`.
- Generated `stack/client.ts` / `stack/server.ts` import from
`@hexclave/next` and reference `HexclaveClientApp` /
`HexclaveServerApp`.
- Internal `StackAuthKeys` dashboard component renamed to
`HexclaveKeys`.

- **docs-mintlify rewrite** (legacy `./docs/` intentionally untouched
per scoping decision):
- **78 MDX files swept**.
`@stackframe/{react,stack,js,tanstack-start,...}` →
`@hexclave/{react,stack,js,...}` in install snippets and code blocks;
`Stack*` SDK class names → `Hexclave*` in all code examples; 'Stack
Auth' brand phrase → 'Hexclave'.
- `openapi/{server,admin,client,webhooks}.json` titles → 'Hexclave REST
API' / 'Hexclave Webhooks API'.

- **Generators flipped before regeneration**:
-
[`packages/stack-shared/src/helpers/init-prompt.ts`](packages/stack-shared/src/helpers/init-prompt.ts),
[`/ai/prompts.ts`](packages/stack-shared/src/ai/prompts.ts),
[`apps/backend/src/lib/ai/prompts.ts`](apps/backend/src/lib/ai/prompts.ts),
[`apps/backend/src/lib/ai/tools/create-email-{template,draft}.ts`](apps/backend/src/lib/ai/tools/create-email-template.ts),
[`apps/skills/src/app/route.ts`](apps/skills/src/app/route.ts) (taught
MCP tool → `ask_hexclave` with compat note; CLI binary teach →
`hexclave`),
[`docs-mintlify/snippets/home-prompt-island.jsx`](docs-mintlify/snippets/home-prompt-island.jsx),
[`packages/template/README.md`](packages/template/README.md) +
integrations/convex/component/README.md.
  - `generate-sdks` propagated changes to `packages/{react,stack,js}`.

- **OpenAPI dual-documentation**:
[`apps/backend/src/app/api/latest/route.ts`](apps/backend/src/app/api/latest/route.ts)
now lists `X-Hexclave-*` headers as primary documented schemas with
`X-Stack-*` duplicates marked `.optional()` (both accepted at runtime by
PR 1's normalize-at-proxy shim).

- **`@stackframe/emails` virtual module**: dual-aliased to
`@hexclave/emails` at the bundler boundary
([email-rendering.tsx:89](apps/backend/src/lib/email-rendering.tsx:89)).
Stored email templates continue to import from either name; new
AI-generated templates and the system prompt teach `@hexclave/emails`.

- **Tier 2 mirror-publish wiring** (new this PR, lays the groundwork for
`@hexclave/*` first publish):
-
[`scripts/rewrite-packages-to-hexclave.ts`](scripts/rewrite-packages-to-hexclave.ts)
— rewrites 9 publishable `@stackframe/*` → `@hexclave/*` `package.json`
files (reads `HEXCLAVE_VERSION` env or `--version=` flag), pins
cross-deps to the shared `@hexclave` version, registers `hexclave` bin
alongside `stack` for `@hexclave/cli`.
-
[`.github/workflows/npm-publish.yaml`](.github/workflows/npm-publish.yaml)
appended with rewrite-then-republish step. `pnpm publish` skips
already-on-npm versions so reruns are safe.

- **Sender email domain**: `noreply@stackframe.co` →
`noreply@sent-with-hexclave.com` (the dedicated transactional-sender
domain split per the plan, to isolate bulk deliverability from
`hexclave.com` reputation); `security@` / `team@stack-auth.com` inbound
mailboxes → `@hexclave.com`.

- **Self-host docs**: docker network / container names in the bash
examples flipped from `stack-auth` to `hexclave` (`hexclave-postgres`,
`hexclave-clickhouse`, `hexclave.env`). The docker image tag
`stackauth/server:latest` stays per the plan's locked decision.

- **GitHub repo slug**: `hexclave/stack-auth` → `hexclave/hexclave` in
every `package.json` `repository` field, README link, CHANGELOG
raw-asset URL.

## Carve-outs (deliberately untouched)

-
**[`apps/backend/src/lib/tokens.tsx`](apps/backend/src/lib/tokens.tsx)**
JWT issuer dual-accept table — PR 1 intentional infrastructure, kept
indefinitely.
- **Legacy `./docs/` folder** — per scoping decision (only
`docs-mintlify/` rewritten).
- **`unified-docs-widget` hostname allowlist** — accepts both
`.hexclave.com` (canonical) and `.stack-auth.com` (transition window)
for DNS rollout.
- **`url-targets.ts`** hosted-domain default
`.built-with-stack-auth.com` — wire identifier baked into existing
customer deploys; indefinite read-fallback.
- **Binary visual assets** (logos, favicons, OG images, README
screenshots) — out of scope for this PR. Need design work; tracked
separately.

## Verification

- **`pnpm typecheck`** on
`packages/{template,stack-shared,react,stack,js}` + `apps/dashboard`:
**all green**. The remaining backend / e-commerce-demo typecheck errors
are pre-existing (Prisma codegen output +
`./generated/api-versions.json` not present in fresh worktrees without
`pnpm run codegen-prisma` + a live DB) and unrelated to this diff.
- **`pnpm lint`** on the same 6 packages: all green.
- **Final grep** for residual `Stack Auth` / `stack-auth.com` /
`@stackframe/stack-cli@latest` references: zero outside the intentional
carve-outs above.
- **25 e2e test files updated in lockstep** with the known-error message
changes (asserted strings flipped to match the new x-hexclave-* +
compat-note messages).

## Deploy blockers (ops sequencing before this rebrand goes live)

This PR is code-complete, but the rebrand's visible surfaces (SDK
default URLs, dashboard links, npm READMEs, REST error messages, runtime
deprecation warning) all point at `*.hexclave.com` / `@hexclave/*`
resources that don't exist yet. None of these are fixable from a PR —
they're ops/registrar/npm work that has to be sequenced before merging
this to a release tag.

Suggested ordering, hardest blockers first:

### Tier 1 — required before customer-facing deploy (everything below
this line *will visibly break customers on day 1* if skipped)

1. **DNS + TLS for `api.hexclave.com` + `api1./api2.hexclave.com`** →
must point at the same backend that serves `api.stack-auth.com` (or a
backend that mirrors PR 1's dual-accept). The SDK's new `defaultBaseUrl`
is `https://api.hexclave.com`; every customer that relied on the old
default and upgrades to a post-PR2 SDK build sends API requests here.
Until this resolves, every default-config customer's API call NXDOMAINs.
2. **DNS for `app.hexclave.com`** → the dashboard. Referenced in the
SDK's default-error messages ("Please create a project on the Hexclave
dashboard at https://app.hexclave.com"), the init-stack flow's
`wizard-congrats` redirect, and the OAuth dashboard handoff.
3. **DNS for `docs.hexclave.com`** + Mintlify deploy → the SDK runtime
deprecation warning (`https://docs.hexclave.com/migration`), every
README, every "Learn more" link in the dashboard, and every REST API
error body (`/api/overview#authentication`) points here. The MDX is in
this PR; the docs build target needs DNS.
4. **DNS for `mcp.hexclave.com`** → the MCP server endpoint that every
taught agent integration (`claude mcp add ...`, `cursor`, `codex`,
`vscode`) registers. Until this resolves, every `npx
@hexclave/cli@latest init` MCP-registration step fails.
5. **Reserve the `@hexclave` npm scope + set repo variable
`HEXCLAVE_VERSION`** → the mirror-publish step in
`.github/workflows/npm-publish.yaml` is gated on this variable. Without
it, the entire taught onboarding command `npx @hexclave/cli@latest init`
404s from the npm registry, *and* every README that says "install
`@hexclave/next`" leads to install failure. Pick the initial version
intentionally (`1.0.0` or aligned to `@stackframe/stack`); don't accept
a silent default.

### Tier 2 — required before announcing the rebrand publicly (lookalike
or low-traffic surfaces, but visibly broken)

6. **DNS for `r.hexclave.com`** → the analytics beacon
`defaultAnalyticsBaseUrl`. Silent failure if missing (analytics drops),
but should land alongside Tier 1.
7. **Register `sent-with-hexclave.com` + full email auth (SPF / DKIM /
DMARC)** → the new default sender domain for shared-sender transactional
emails. Without it the dashboard "send test email" path emits bounces,
and shared-sender flows (`getSharedEmailConfig("Hexclave")`) deliver to
spam at best.
8. **MX + SPF / DMARC for `hexclave.com`** → `team@hexclave.com` and
`security@hexclave.com` mailboxes. The security disclosure mailbox is
referenced in [`.github/SECURITY.md`](.github/SECURITY.md);
`team@hexclave.com` is the actual recipient of internal feedback emails
sent at runtime by
[`apps/backend/src/lib/internal-feedback-emails.tsx`](apps/backend/src/lib/internal-feedback-emails.tsx).
Today, every runtime feedback email bounces.
9. **DNS for `skill.hexclave.com`** → the canonical AI-agent skill fetch
URL (the agent bootstrap pivot). Without it, the entire "agent downloads
`SKILL.md` from a known URL" flow taught in
[`packages/stack-shared/src/helpers/init-prompt.ts`](packages/stack-shared/src/helpers/init-prompt.ts)
fails.
10. **Create `github.com/hexclave/hexclave` as a public repo** (even as
a redirect to `hexclave/stack-auth`) **OR** rewrite every `package.json`
`"repository"` field + dashboard footer "view on GitHub" link to point
at `hexclave/stack-auth` (which already exists). Currently every npm
package page's "Repository" link is dead, and the dashboard's GitHub
button + dev-tool repo link are dead.

### Tier 3 — broken but low-visibility / low-traffic

11. **DNS for `discord.hexclave.com`** → Discord invite redirect, used
in every README's chip and the dashboard footer.
12. **DNS for `demo.hexclave.com`** → " Demo" badge in every npm
package README. Broken-image badge on the package page.
13. **DNS + TLS for `built-with-hexclave.com`** → optional
hosted-handler domain (the default reverted to
`.built-with-stack-auth.com` in this PR's carve-outs, so this only
matters for projects that manually flip).

## Other follow-ups (not deploy-blocking)

- **E2E snapshot regen across the full suite** for the dual-emitted
`x-hexclave-*` response headers (PR 1 follow-up; `vitest -u` in CI
absorbs).
- **Binary visual assets** — logos, favicons, OG images, README
screenshots; need design pass.
- **Backend OpenAPI fumadocs regen** in CI flow — the JSON files in
`docs-mintlify/openapi/` are committed but regen runs in CI. Verify the
workflow that does this still works against the post-PR2 source.
- **Backend typecheck infra debt** — needs `codegen-prisma` +
`codegen-route-info` to clear; pre-existing, unaffected by this PR.

## Test plan

- [ ] CI runs full e2e suite (with `vitest -u` to absorb residual
snapshot deltas, then committed back).
- [ ] Spot-check: new `@hexclave/cli init` (once published) generates
`hexclave.config.ts` and works against a fresh project.
- [ ] Spot-check: existing customer with `@stackframe/stack` import sees
the once-per-process `console.warn` recommending `@hexclave/next` on SDK
init.
- [ ] Manual: dashboard setup page renders the `npx @hexclave/cli@latest
init` snippet and the `x-hexclave-publishable-client-key` API header in
the curl example.
- [ ] Manual: a fresh `pnpm run prisma migrate` against a clean DB sets
the internal project displayName to 'Hexclave Dashboard'.

---------

Co-authored-by: Konstantin Wohlwend <n2d4xc@gmail.com>
2026-05-26 19:18:20 -07:00

1132 lines
30 KiB
Plaintext

---
title: "StackApp"
description: "Reference documentation for HexclaveClientApp and HexclaveServerApp objects."
sidebarTitle: "StackApp"
mode: "wide"
---
import {
AsideSection,
ContentSection,
MethodAside,
MethodContent,
MethodLayout,
MethodReturns,
} from "/snippets/sdk-type-components.jsx";
This is a detailed reference for the `StackApp` object. If you're looking for a more high-level overview, please read the [respective page in the Concepts section](/guides/going-further/stack-app).
## Overview
- [HexclaveClientApp](#stackclientapp) - Client-level permissions for frontend code
- [HexclaveServerApp](#stackserverapp) - Server-level permissions with full access
---
# HexclaveClientApp
A `StackApp` with client-level permissions. It contains most of the useful methods and hooks for your client-side code.
Most commonly you get an instance of `HexclaveClientApp` by calling [`useHexclaveApp()`](/sdk/hooks/use-stack-app) in a Client Component.
## Table of Contents
```typescript
type HexclaveClientApp = {
new(options): HexclaveClientApp;
getUser([options]): Promise<User>;
useUser([options]): User;
getProject(): Promise<Project>;
useProject(): Project;
signInWithOAuth(provider): void;
signInWithCredential([options]): Promise<...>;
signUpWithCredential([options]): Promise<...>;
sendForgotPasswordEmail(email): Promise<...>;
sendMagicLinkEmail(email): Promise<...>;
};
```
## Constructor
Creates a new `HexclaveClientApp` instance.
Because each app creates a new connection to Hexclave's backend, you should re-use existing instances wherever possible.
<Info>
This object is not usually constructed directly. More commonly, you would construct a [`HexclaveServerApp`](#stackserverapp) instead, pass it into your app setup (see the [setup guide](/guides/getting-started/setup)), and then use the `useHexclaveApp()` hook to obtain a `HexclaveClientApp`.
The [setup wizard](/guides/getting-started/setup) does these steps for you, so you don't need to worry about it unless you are manually setting up Hexclave.
If you're building a client-only app and don't have a `SECRET_SERVER_KEY`, you can construct a `HexclaveClientApp` directly.
</Info>
<MethodLayout>
<MethodContent>
<ContentSection title="Parameters">
<ParamField body="tokenStore" type='"nextjs-cookie" | "cookie" | { accessToken, refreshToken } | Request' required>
Token storage configuration.
</ParamField>
<ParamField body="baseUrl" type="string">
Base URL for the Hexclave API.
</ParamField>
<ParamField body="projectId" type="string">
Project ID. Defaults to the `NEXT_PUBLIC_STACK_PROJECT_ID` environment variable.
</ParamField>
<ParamField body="publishableClientKey" type="string">
Publishable client key. Defaults to the `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY` environment variable.
</ParamField>
<ParamField body="urls" type="object">
Redirect URL configuration.
</ParamField>
<ParamField body="noAutomaticPrefetch" type="boolean">
Disable automatic prefetching.
</ParamField>
</ContentSection>
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare class HexclaveClientApp {
constructor(options: {
tokenStore: "nextjs-cookie" | "cookie" | {
accessToken: string;
refreshToken: string;
} | Request;
baseUrl?: string;
projectId?: string;
publishableClientKey?: string;
urls?: object;
noAutomaticPrefetch?: boolean;
});
}
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const stackClientApp = new HexclaveClientApp({
tokenStore: "nextjs-cookie",
baseUrl: "https://api.hexclave.com",
projectId: "123",
publishableClientKey: "123",
urls: {
home: "/",
},
});
```
```typescript
"use client";
function MyReactComponent() {
const stackClientApp = useHexclaveApp();
}
```
</AsideSection>
</MethodAside>
</MethodLayout>
## User Management
### `stackClientApp.getUser([options])`
<MethodLayout>
<MethodContent>
Gets the current user.
<ContentSection title="Parameters">
<ParamField body="options.or" type='"return-null" | "redirect" | "throw"'>
What to do if the user is not found.
</ParamField>
</ContentSection>
<MethodReturns type="Promise<CurrentUser | null>" />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function getUser(options?: {
or?: "return-null" | "redirect" | "throw";
}): Promise<CurrentUser | null>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const userOrNull = await stackClientApp.getUser();
console.log(userOrNull);
const user = await stackClientApp.getUser({ or: "redirect" });
console.log(user);
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackClientApp.useUser([options])`
<MethodLayout>
<MethodContent>
React hook version of `getUser()`. Equivalent to the [`useUser()`](/sdk/hooks/use-user) standalone hook, which is an alias for `useHexclaveApp().useUser()`.
<ContentSection title="Parameters">
<ParamField body="options.or" type='"return-null" | "redirect" | "throw"'>
What to do if the user is not found.
</ParamField>
</ContentSection>
<MethodReturns type="CurrentUser | null" />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function useUser(options?: {
or?: "return-null" | "redirect" | "throw";
}): CurrentUser | null;
```
</AsideSection>
<AsideSection title="Examples">
```jsx
"use client";
function MyReactComponent() {
const user = useUser();
return user ? <div>Hello, {user.name}</div> : <div>Not signed in</div>;
}
```
```tsx
"use client";
function MyProtectedComponent() {
useUser({ or: "redirect" });
return <div>You can only see this if you are authenticated</div>;
}
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackClientApp.getProject()`
<MethodLayout>
<MethodContent>
Gets the current project.
<MethodReturns type="Promise<Project>" />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function getProject(): Promise<Project>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const project = await stackClientApp.getProject();
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackClientApp.useProject()`
<MethodLayout>
<MethodContent>
React hook version of `getProject()`.
<MethodReturns type="Project" />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function useProject(): Project;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
function MyReactComponent() {
const project = useProject();
}
```
</AsideSection>
</MethodAside>
</MethodLayout>
## Authentication
### `stackClientApp.signInWithOAuth(provider)`
<MethodLayout>
<MethodContent>
Initiates the OAuth sign-in process with the specified provider.
<ContentSection title="Parameters">
<ParamField body="provider" type="string">
The OAuth provider type.
</ParamField>
</ContentSection>
<MethodReturns type="Promise<void>" />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function signInWithOAuth(provider: string): Promise<void>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
await stackClientApp.signInWithOAuth("google");
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackClientApp.signInWithCredential([options])`
<MethodLayout>
<MethodContent>
Sign in using email and password credentials.
<ContentSection title="Parameters">
<ParamField body="options.email" type="string">
User's email.
</ParamField>
<ParamField body="options.password" type="string">
User's password.
</ParamField>
<ParamField body="options.noRedirect" type="boolean">
Whether to skip redirect after sign-in.
</ParamField>
</ContentSection>
<MethodReturns type='Promise<Result<undefined, KnownErrors["EmailPasswordMismatch"]>>' />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function signInWithCredential(options?: {
email?: string;
password?: string;
noRedirect?: boolean;
}): Promise<Result<undefined, KnownErrors["EmailPasswordMismatch"]>>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const result = await stackClientApp.signInWithCredential({
email: "test@example.com",
password: "password",
});
if (result.status === "error") {
console.error("Sign in failed", result.error.message);
}
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackClientApp.signUpWithCredential([options])`
<MethodLayout>
<MethodContent>
Sign up using email and password credentials.
<ContentSection title="Parameters">
<ParamField body="options.email" type="string">
User's email.
</ParamField>
<ParamField body="options.password" type="string">
User's password.
</ParamField>
<ParamField body="options.noRedirect" type="boolean">
Whether to skip redirect after sign-up.
</ParamField>
</ContentSection>
<MethodReturns type='Promise<Result<undefined, KnownErrors["UserWithEmailAlreadyExists"] | KnownErrors["PasswordRequirementsNotMet"]>>' />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function signUpWithCredential(options?: {
email?: string;
password?: string;
noRedirect?: boolean;
}): Promise<Result<
undefined,
KnownErrors["UserWithEmailAlreadyExists"] | KnownErrors["PasswordRequirementsNotMet"]
>>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const result = await stackClientApp.signUpWithCredential({
email: "test@example.com",
password: "password",
});
if (result.status === "error") {
console.error("Sign up failed", result.error.message);
}
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackClientApp.sendForgotPasswordEmail(email)`
<MethodLayout>
<MethodContent>
Send a forgot-password email to an email address.
<ContentSection title="Parameters">
<ParamField body="email" type="string">
The email to send the forgot-password email to.
</ParamField>
</ContentSection>
<MethodReturns type='Promise<Result<undefined, KnownErrors["UserNotFound"]>>' />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function sendForgotPasswordEmail(
email: string,
): Promise<Result<undefined, KnownErrors["UserNotFound"]>>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const result = await stackClientApp.sendForgotPasswordEmail("test@example.com");
if (result.status === "success") {
console.log("Forgot password email sent");
} else {
console.error("Failed to send forgot password email", result.error.message);
}
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackClientApp.sendMagicLinkEmail(email)`
<MethodLayout>
<MethodContent>
Send a magic-link or OTP sign-in email to an email address.
<ContentSection title="Parameters">
<ParamField body="email" type="string">
The email to send the magic link to.
</ParamField>
</ContentSection>
<MethodReturns type='Promise<Result<{ nonce: string }, KnownErrors["RedirectUrlNotWhitelisted"]>>' />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function sendMagicLinkEmail(
email: string,
): Promise<Result<{ nonce: string }, KnownErrors["RedirectUrlNotWhitelisted"]>>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const result = await stackClientApp.sendMagicLinkEmail("test@example.com");
```
</AsideSection>
</MethodAside>
</MethodLayout>
---
# HexclaveServerApp
Like `HexclaveClientApp`, but with [server permissions](/guides/going-further/stack-app#client-vs-server). Has full read and write access to all users.
<Warning>
Since this functionality should only be available in environments you trust
(ie. your own server), it requires a `SECRET_SERVER_KEY`. In some cases, you
may want to use a `HexclaveServerApp` on the client; an example for this is an
internal dashboard that only your own employees have access to. We generally
recommend against doing this unless you are aware of and protected against the
(potentially severe) security implications of exposing `SECRET_SERVER_KEY` on
the client.
</Warning>
## Table of Contents
```typescript
type HexclaveServerApp =
// Inherits all functionality from HexclaveClientApp
& HexclaveClientApp
& {
new(options): HexclaveServerApp;
getUser([id][, options]): Promise<ServerUser | null>;
useUser([id][, options]): ServerUser;
listUsers([options]): Promise<ServerUser[]>;
useUsers([options]): ServerUser[];
createUser([options]): Promise<ServerUser>;
sendEmail(options): Promise<Result<void, KnownErrors>>;
getTeam(id): Promise<ServerTeam | null>;
useTeam(id): ServerTeam;
listTeams([options]): Promise<ServerTeam[]>;
useTeams([options]): ServerTeam[];
listTeamsPaginated([options]): Promise<{ items: ServerTeam[]; nextCursor: string | null }>;
useTeamsPaginated([options]): { items: ServerTeam[]; nextCursor: string | null };
createTeam([options]): Promise<ServerTeam>;
}
```
## Constructor
Creates a new `HexclaveServerApp` instance.
<MethodLayout>
<MethodContent>
<ContentSection title="Parameters">
<ParamField body="tokenStore" type='"nextjs-cookie" | "cookie" | { accessToken, refreshToken } | Request' required>
Token storage configuration.
</ParamField>
<ParamField body="baseUrl" type="string">
Base URL for the Hexclave API.
</ParamField>
<ParamField body="projectId" type="string">
Project ID.
</ParamField>
<ParamField body="publishableClientKey" type="string">
Publishable client key.
</ParamField>
<ParamField body="secretServerKey" type="string">
Secret server key. Defaults to the `SECRET_SERVER_KEY` environment variable.
</ParamField>
<ParamField body="urls" type="object">
Redirect URL configuration.
</ParamField>
<ParamField body="noAutomaticPrefetch" type="boolean">
Disable automatic prefetching.
</ParamField>
</ContentSection>
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare class HexclaveServerApp {
constructor(options: {
tokenStore: "nextjs-cookie" | "cookie" | {
accessToken: string;
refreshToken: string;
} | Request;
baseUrl?: string;
projectId?: string;
publishableClientKey?: string;
secretServerKey?: string;
urls?: object;
noAutomaticPrefetch?: boolean;
});
}
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const stackServerApp = new HexclaveServerApp({
tokenStore: "nextjs-cookie",
urls: {
signIn: "/my-custom-sign-in-page",
},
});
```
</AsideSection>
</MethodAside>
</MethodLayout>
## User Operations
### `stackServerApp.getUser([id], [options])`
<MethodLayout>
<MethodContent>
Enhanced version of `HexclaveClientApp.getUser()` with server permissions.
**Overloads:**
1. `getUser(id: string): Promise<ServerUser | null>` to get a user by ID.
2. `getUser(options?: { or?: "return-null" | "redirect" | "throw" }): Promise<CurrentServerUser | null>` to get the current user.
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function getUser(id: string): Promise<ServerUser | null>;
declare function getUser(options?: {
or?: "return-null" | "redirect" | "throw";
}): Promise<CurrentServerUser | null>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const currentUser = await stackServerApp.getUser();
console.log(currentUser);
const serverUser = await stackServerApp.getUser(
"12345678-1234-1234-1234-123456789abc",
);
console.log(serverUser);
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackServerApp.useUser([id], [options])`
<MethodLayout>
<MethodContent>
Functionally equivalent to [`getUser()`](#stackserverappgetuserid-options), but as a React hook.
<Info>
This should be used on the server-side only.
</Info>
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function useUser(
idOrOptions?: string | { or?: "return-null" | "redirect" | "throw" },
options?: { or?: "return-null" | "redirect" | "throw" },
): ServerUser | CurrentServerUser | null;
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackServerApp.listUsers([options])`
<MethodLayout>
<MethodContent>
Lists all users on the project.
<ContentSection title="Parameters">
<ParamField body="options.cursor" type="string">
The cursor to start the result set from.
</ParamField>
<ParamField body="options.limit" type="number">
Maximum number of items to return. If omitted, all users are returned.
</ParamField>
<ParamField body="options.orderBy" type='"signedUpAt"'>
The field to sort results by.
</ParamField>
<ParamField body="options.desc" type="boolean">
Whether to sort in descending order. Defaults to `false`.
</ParamField>
<ParamField body="options.query" type="string">
Free-text search on the user's display name and emails.
</ParamField>
</ContentSection>
<MethodReturns type="Promise<ServerUser[] & { nextCursor: string | null }>" />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function listUsers(options?: {
cursor?: string;
limit?: number;
orderBy?: "signedUpAt";
desc?: boolean;
query?: string;
}): Promise<ServerUser[] & { nextCursor: string | null }>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const users = await stackServerApp.listUsers({ limit: 20 });
console.log(users);
if (users.nextCursor) {
const nextPageUsers = await stackServerApp.listUsers({
cursor: users.nextCursor,
limit: 20,
});
console.log(nextPageUsers);
}
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackServerApp.useUsers([options])`
<MethodLayout>
<MethodContent>
Functionally equivalent to [`listUsers()`](#stackserverapplistusersoptions), but as a React hook.
<Info>This should be used on the server-side only.</Info>
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function useUsers(options?: {
cursor?: string;
limit?: number;
orderBy?: "signedUpAt";
desc?: boolean;
query?: string;
}): ServerUser[];
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackServerApp.createUser([options])`
<MethodLayout>
<MethodContent>
Creates a new user from the server.
<ContentSection title="Parameters">
<ParamField body="options.primaryEmail" type="string">
User's primary email.
</ParamField>
<ParamField body="options.primaryEmailVerified" type="boolean">
Whether the email is verified.
</ParamField>
<ParamField body="options.primaryEmailAuthEnabled" type="boolean">
Whether email auth is enabled.
</ParamField>
<ParamField body="options.password" type="string">
User's password.
</ParamField>
<ParamField body="options.otpAuthEnabled" type="boolean">
Enable OTP or magic-link auth.
</ParamField>
<ParamField body="options.displayName" type="string">
User's display name.
</ParamField>
</ContentSection>
<MethodReturns type="Promise<ServerUser>" />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function createUser(options?: {
primaryEmail?: string;
primaryEmailVerified?: boolean;
primaryEmailAuthEnabled?: boolean;
password?: string;
otpAuthEnabled?: boolean;
displayName?: string;
}): Promise<ServerUser>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const passwordUser = await stackServerApp.createUser({
primaryEmail: "test@example.com",
primaryEmailAuthEnabled: true,
password: "password123",
});
const magicLinkUser = await stackServerApp.createUser({
primaryEmail: "test@example.com",
primaryEmailVerified: true,
primaryEmailAuthEnabled: true,
otpAuthEnabled: true,
});
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackServerApp.sendEmail(options)`
<MethodLayout>
<MethodContent>
Send custom emails to users. You can send either custom HTML emails or use predefined templates with variables.
<ContentSection title="Parameters">
<ParamField body="options" type="SendEmailOptions">
Email configuration and content.
</ParamField>
</ContentSection>
<MethodReturns type="Promise<Result<void, KnownErrors>>">
The method returns a `Result` that can include `RequiresCustomEmailServer`, `SchemaError`, and `UserIdDoesNotExist`.
</MethodReturns>
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function sendEmail(
options: SendEmailOptions,
): Promise<Result<void, KnownErrors>>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const htmlResult = await stackServerApp.sendEmail({
userIds: ["user-1", "user-2"],
subject: "Welcome to our platform!",
html: "<h1>Welcome!</h1><p>Thanks for joining us.</p>",
});
if (htmlResult.status === "error") {
console.error("Failed to send email:", htmlResult.error);
}
```
```typescript
const templateResult = await stackServerApp.sendEmail({
userIds: ["user-1"],
templateId: "welcome-template",
variables: {
userName: "John Doe",
activationUrl: "https://app.com/activate/token123",
},
});
```
</AsideSection>
</MethodAside>
</MethodLayout>
## Team Management
### `stackServerApp.getTeam(id)`
<MethodLayout>
<MethodContent>
Get a team by its ID.
<ContentSection title="Parameters">
<ParamField body="id" type="string">
Team ID.
</ParamField>
</ContentSection>
<MethodReturns type="Promise<ServerTeam | null>" />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function getTeam(id: string): Promise<ServerTeam | null>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const team = await stackServerApp.getTeam("team_id_123");
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackServerApp.useTeam(id)`
<MethodLayout>
<MethodContent>
Functionally equivalent to [`getTeam(id)`](#stackserverappgetteamid), but as a React hook.
<Info>This should be used on the server-side only.</Info>
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function useTeam(id: string): ServerTeam;
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackServerApp.listTeams([options])`
<MethodLayout>
<MethodContent>
Lists all teams on the current project.
For cursor-based pagination over teams, see [`listTeamsPaginated`](#stackserverapplistteamspaginatedoptions).
<ContentSection title="Parameters">
<ParamField body="options.orderBy" type='"createdAt"'>
The field to sort results by.
</ParamField>
<ParamField body="options.desc" type="boolean">
Whether to sort in descending order. Defaults to `false`.
</ParamField>
</ContentSection>
<MethodReturns type="Promise<ServerTeam[]>" />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function listTeams(options?: {
orderBy?: "createdAt";
desc?: boolean;
}): Promise<ServerTeam[]>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const teams = await stackServerApp.listTeams();
console.log(teams);
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackServerApp.useTeams([options])`
<MethodLayout>
<MethodContent>
Functionally equivalent to [`listTeams()`](#stackserverapplistteamsoptions), but as a React hook.
<Info>This should be used on the server-side only.</Info>
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function useTeams(options?: {
orderBy?: "createdAt";
desc?: boolean;
}): ServerTeam[];
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackServerApp.listTeamsPaginated([options])`
<MethodLayout>
<MethodContent>
Lists teams on the current project with cursor-based pagination, optional filtering, and ordering. The returned array carries an extra `nextCursor` property; pass it back as `cursor` to load the next page.
<ContentSection title="Parameters">
<ParamField body="options.cursor" type="string">
Cursor returned as `nextCursor` from a previous response.
</ParamField>
<ParamField body="options.limit" type="number">
Maximum number of items to return. If omitted, all matching teams are returned.
</ParamField>
<ParamField body="options.orderBy" type='"createdAt"'>
The field to sort results by.
</ParamField>
<ParamField body="options.desc" type="boolean">
Whether to sort in descending order. Defaults to `false`.
</ParamField>
<ParamField body="options.query" type="string">
Free-text search on the team's display name (and team ID if the query is a UUID).
</ParamField>
</ContentSection>
<MethodReturns type="Promise<{ items: ServerTeam[]; nextCursor: string | null }>" />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function listTeamsPaginated(options?: {
cursor?: string;
limit?: number;
orderBy?: "createdAt";
desc?: boolean;
query?: string;
}): Promise<{ items: ServerTeam[]; nextCursor: string | null }>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const teams = await stackServerApp.listTeamsPaginated({ limit: 20 });
console.log(teams);
if (teams.nextCursor) {
const nextPageTeams = await stackServerApp.listTeamsPaginated({
cursor: teams.nextCursor,
limit: 20,
});
console.log(nextPageTeams);
}
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackServerApp.useTeamsPaginated([options])`
<MethodLayout>
<MethodContent>
Functionally equivalent to [`listTeamsPaginated()`](#stackserverapplistteamspaginatedoptions), but as a React hook.
<Info>This should be used on the server-side only.</Info>
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function useTeamsPaginated(options?: {
cursor?: string;
limit?: number;
orderBy?: "createdAt";
desc?: boolean;
query?: string;
}): { items: ServerTeam[]; nextCursor: string | null };
```
</AsideSection>
</MethodAside>
</MethodLayout>
### `stackServerApp.createTeam([options])`
<MethodLayout>
<MethodContent>
Creates a team without adding a user to it.
<ContentSection title="Parameters">
<ParamField body="options.displayName" type="string">
Team display name.
</ParamField>
<ParamField body="options.profileImageUrl" type="string | null">
Team profile image URL.
</ParamField>
</ContentSection>
<MethodReturns type="Promise<ServerTeam>" />
</MethodContent>
<MethodAside>
<AsideSection title="Signature">
```typescript
declare function createTeam(options?: {
displayName?: string;
profileImageUrl?: string | null;
}): Promise<ServerTeam>;
```
</AsideSection>
<AsideSection title="Examples">
```typescript
const team = await stackServerApp.createTeam({
displayName: "New Team",
profileImageUrl: "https://example.com/profile.jpg",
});
```
</AsideSection>
</MethodAside>
</MethodLayout>