stack/docs-mintlify/guides/getting-started/setup.mdx
2026-05-22 20:18:05 -07:00

2120 lines
133 KiB
Plaintext

---
title: Setup
description: Install and configure Stack Auth for your project
sidebarTitle: Setup
---
{/* This file is auto-generated by scripts/generate-setup-prompt-docs.ts. Do not edit it manually; edit packages/stack-shared/src/ai/prompts.ts instead. */}
export const generatedSetupPromptText = "# Setting up Stack Auth\n\nThis prompt explains how to set up Stack Auth in your project.\n\nTo use it, you can use the sections below to set up Stack Auth in the project. For example, if you are setting up a Svelte project, you would follow the SDK setup instructions for a frontend JS project.\n\n## SDK Setup Instructions\n\nFollow these instructions in order to set up and get started with the Stack Auth SDK in various languages.\n\nNot all steps are applicable to every type of application; for example, React apps have some extra steps that are not needed with other frameworks.\n\nThe frameworks and languages with explicit SDK support are:\n\n- Next.js\n- React\n- TanStack Start\n- Other JS & TS (both frontend and backend)\n\n<Steps titleSize=\"h3\">\n <Step title=\"Install dependencies\">\n Stack Auth has SDKs for various languages, frameworks, and libraries. Use the most specific package each, so, for example, even though a Next.js project uses both Next.js and React, use the Next.js package. If a programming language is not supported entirely, you may have to use the REST API to interface with Stack Auth.\n \n #### JavaScript & TypeScript\n \n For JS & TS, the following packages are available:\n \n - Next.js: `@stackframe/stack`\n - React: `@stackframe/react`\n - TanStack Start: `@stackframe/tanstack-start`\n - Other & vanilla JS: `@stackframe/js`\n \n You can install the correct JavaScript Stack Auth SDK into your project by running the following command:\n\n ```sh\n npm i <the-sdk-from-above>\n # or: pnpm i <the-sdk-from-above>\n # or: yarn add <the-sdk-from-above>\n # or: bun add <the-sdk-from-above>\n ```\n </Step>\n \n <Step title=\"Initializing the Stack App\">\n Next, let us create the Stack App object for your project. This is the most important object in a Stack Auth project.\n\n In a frontend where you cannot keep a secret key safe, you would use the `StackClientApp` constructor:\n \n ```ts src/stack/client.ts\n import { StackClientApp } from \"<the-sdk-from-above>\";\n \n export const stackClientApp = new StackClientApp({\n tokenStore: \"cookie\", // \"nextjs-cookie\" for Next.js, \"cookie\" for other web frontends, null for backend environments\n urls: {\n default: {\n type: \"hosted\",\n }\n },\n });\n ```\n\n In a backend where you can keep a secret key safe, you can use the `StackServerApp`, which provides access to more sensitive APIs compared to `StackClientApp`:\n \n ```ts src/stack/server.ts\n import { StackServerApp } from \"<the-sdk-from-above>\";\n \n export const stackServerApp = new StackServerApp({\n tokenStore: null,\n urls: {\n default: {\n type: \"hosted\",\n }\n },\n });\n ```\n \n In frameworks that are both front- and backend, like Next.js, you can also create a `StackServerApp` from a `StackClientApp` object:\n \n ```ts src/stack/server.ts\n import { StackServerApp } from \"<the-sdk-from-above>\";\n import { stackClientApp } from \"./client\";\n \n export const stackServerApp = new StackServerApp({\n inheritsFrom: stackClientApp,\n });\n ```\n \n Note that the secret server key should **never** be exposed to the client, as it can be used to read and write everything in your Stack Auth project. In web frontends or bundled applications, you should therefore always only ever create a `StackClientApp` object.\n </Step>\n\n <Step title=\"Setting up the project\">\n It's now time to connect your code to a Stack Auth project.\n\n You can either run Stack Auth's dev environment locally, or connect to a production project hosted in the cloud.\n\n If you already use Stack Auth for your product, we recommend you re-use the same project to share your configuration between the two.\n\n <AccordionGroup>\n <Accordion title=\"Option 1: Running Stack Auth's dev environment (recommended)\" defaultOpen>\n First, create a `stack.config.ts` configuration file in the root directory of the workspace (or anywhere else):\n\n ```ts stack.config.ts\n import type { StackConfig } from \"<the-sdk-from-above>\";\n\n // default: show-onboarding, which shows the onboarding flow for this project when Stack Auth starts\n export const config: StackConfig = \"show-onboarding\";\n ```\n\n To run your application with Stack Auth, you can then start the dev environment and set environment variables expected by your application. Stack Auth's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json:\n\n ```sh\n npm i -D @stackframe/stack-cli\n # or: pnpm i -D @stackframe/stack-cli\n # or: yarn add -D @stackframe/stack-cli\n # or: bun add --dev @stackframe/stack-cli\n ```\n\n ```json package.json\n {\n // ...\n \"scripts\": {\n // ...\n \"dev\": \"stack dev --config-file ./stack.config.ts -- npm run dev:without-stack-auth\",\n \"dev:without-stack-auth\": \"<your-existing-dev-script>\"\n }\n }\n ```\n </Accordion>\n\n <Accordion title=\"Option 2: Connecting to a production project hosted in the cloud\">\n Note: If you're an AI agent, and you don't already have the information you need from the Cloud project, you may have to ask the user for help on this step. You can either ask them to provide the environment variables, or just leave them empty for now and ask the user to complete them at the end.\n\n If you'd rather run your development environment on our infrastructure, or you already have an existing product, you can also connect a cloud project.\n\n This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not).\n\n #### Frontend\n\n Go to your project's dashboard on [app.stack-auth.com](https://app.stack-auth.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored):\n\n Some projects have the `requirePublishableClientKey` config option enabled. In that case, a publishable client key will also be necessary. However, this is extremely uncommon; for most projects this is not true, so don't ask the user for one unless you have confirmation that the publishable client key is required. If it's not required, the project ID is the only environment variable required to use Stack Auth on a client.\n \n ```.env .env.local\n STACK_PROJECT_ID=<your-project-id>\n ```\n\n Alternatively, you can also just set the project ID in the `stack/client.ts` file:\n\n ```ts src/stack/client.ts\n export const stackClientApp = new StackClientApp({\n // ...\n projectId: \"your-project-id\",\n });\n ```\n\n\n #### Backend (or both frontend and backend)\n\n First, navigate to the [Project Keys](https://app.stack-auth.com/projects/-selector-/project-keys) page in the Stack Auth dashboard and generate a new set of keys.\n\n Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored):\n\n If the `requirePublishableClientKey` config option is enabled as described above, a publishable client key will also be necessary. Otherwise, these two are the only environment variables required to use Stack Auth on a server.\n \n ```.env .env.local\n STACK_PROJECT_ID=<your-project-id>\n STACK_SECRET_SERVER_KEY=<your-secret-server-key>\n ```\n\n They'll automatically be picked up by the `StackServerApp` constructor.\n </Accordion>\n </AccordionGroup>\n </Step>\n\n <Step title=\"React: Creating a <StackProvider /> and <StackTheme />\">\n In React frameworks, Stack Auth provides `StackProvider` and `StackTheme` components that should wrap your entire app at the root level.\n \n For example, if you have an `App.tsx` file, update it as follows:\n \n ```tsx src/App.tsx\n import { StackProvider, StackTheme } from \"<the-sdk-from-above>\";\n import { stackClientApp } from \"./stack/client\";\n \n export default function App() {\n return (\n <StackProvider app={stackClientApp}>\n <StackTheme>\n {/* your app content */}\n </StackTheme>\n </StackProvider>\n );\n }\n ```\n \n For Next.js specifically: You can do this in the `layout.tsx` file in the `app` directory:\n \n ```tsx src/app/layout.tsx\n import { Suspense } from \"react\";\n import { StackProvider, StackTheme } from \"<the-sdk-from-above>\";\n import { stackServerApp } from \"@/stack/server\";\n \n export default function RootLayout({ children }: { children: React.ReactNode }) {\n return (\n <StackProvider app={stackServerApp}>\n <StackTheme>\n {children}\n </StackTheme>\n </StackProvider>\n );\n }\n ```\n \n For TanStack Start specifically: TanStack Start uses file-based routes. The provider goes inside the root route's `component` (the inner React tree), while the document shell stays in `shellComponent`. Update `src/routes/__root.tsx`:\n \n ```tsx src/routes/__root.tsx\n import { StackProvider, StackTheme } from \"@stackframe/tanstack-start\";\n import { createRootRoute, HeadContent, Outlet, Scripts } from \"@tanstack/react-router\";\n import type { ReactNode } from \"react\";\n import { stackClientApp } from \"../stack/client\";\n \n export const Route = createRootRoute({\n shellComponent: RootDocument,\n component: RootComponent,\n });\n \n function RootDocument({ children }: { children: ReactNode }) {\n return (\n <html lang=\"en\" suppressHydrationWarning>\n <head>\n <HeadContent />\n </head>\n <body>\n {children}\n <Scripts />\n </body>\n </html>\n );\n }\n \n function RootComponent() {\n return (\n <StackProvider app={stackClientApp}>\n <StackTheme>\n <Outlet />\n </StackTheme>\n </StackProvider>\n );\n }\n ```\n \n Do not edit `src/routeTree.gen.ts` — it is regenerated automatically by the TanStack Start router from the files under `src/routes/`.\n </Step>\n \n <Step title=\"React: Add Suspense boundary\">\n Stack Auth also provides additional `useXyz` React hooks for `getXyz`/`listXyz` functions. For example, `useUser` is like `getUser`, but as a suspending React hook.\n \n To support the suspension, you need to add a suspense boundary around your app.\n \n The easiest way to do this is to just wrap your entire app in a `Suspense` component:\n \n ```tsx src/App.tsx\n import { Suspense } from \"react\";\n import { StackProvider, StackTheme } from \"<the-sdk-from-above>\";\n import { stackClientApp } from \"./stack/client\";\n \n export default function App() {\n return (\n <Suspense fallback={<div>Loading...</div>}>\n <StackProvider app={stackClientApp}>\n <StackTheme>\n {/* your app content */}\n </StackTheme>\n </StackProvider>\n </Suspense>\n );\n }\n ```\n \n In Next.js, this can be easily done by adding a `loading.tsx` file in the `app` directory:\n \n ```tsx src/app/loading.tsx\n export default function Loading() {\n return <div>Loading...</div>;\n }\n ```\n \n In TanStack Start: wrap the `<Outlet />` in your root route with a `Suspense` boundary so the document shell can stream while child routes wait on Stack Auth. Update `RootComponent` in `src/routes/__root.tsx`:\n \n ```tsx src/routes/__root.tsx\n import { Suspense } from \"react\";\n // ...other imports...\n \n function RootComponent() {\n return (\n <StackProvider app={stackClientApp}>\n <StackTheme>\n <Suspense fallback={<div>Loading...</div>}>\n <Outlet />\n </Suspense>\n </StackTheme>\n </StackProvider>\n );\n }\n ```\n \n Note: If you are an AI agent, make sure to make the loading indicator visually appealing and match the design of your app, instead of the example that just says \"Loading...\".\n </Step>\n \n <Step title=\"TanStack Start: Add the Stack handler route\">\n Stack Auth's auth flows (sign-in, sign-up, OAuth callbacks, password reset, etc.) are rendered by a single `StackHandler` component mounted at `/handler/*`. In TanStack Start, expose it as a splat file route at `src/routes/handler/$.tsx`:\n \n ```tsx src/routes/handler/$.tsx\n import { StackHandler } from \"@stackframe/tanstack-start\";\n import { createFileRoute, useLocation } from \"@tanstack/react-router\";\n \n export const Route = createFileRoute(\"/handler/$\")({\n ssr: false,\n component: HandlerPage,\n });\n \n function HandlerPage() {\n const { pathname } = useLocation();\n return <StackHandler fullPage location={pathname} />;\n }\n ```\n \n Two TanStack-specific notes:\n \n - The route is opted out of SSR with `ssr: false`. The handler runs browser-only auth flows (cookies, redirects, popups), so rendering it on the server provides no benefit and can fight with hydration. Other routes can opt into or out of SSR per-route the same way.\n - Stack Auth resolves the current user during SSR by reading TanStack Start's request cookies through `@stackframe/tanstack-start`'s server context. No extra wiring is required — `useUser()` \"just works\" on both server and client routes as long as `tokenStore: \"cookie\"` is set on `StackClientApp`.\n </Step>\n\n <Step title=\"Backend: Update callers with header & get user\">\n You are now ready to use the Stack Auth SDK. If you have any frontends calling your backend endpoints, you may want to pass along the Stack Auth tokens in a header such that you can access the same user object on your backend.\n \n The most ergonomic way to do this is to pass the result of `stackClientApp.getAuthorizationHeader()` as the `Authorization` header into your backend endpoints when the user is signed in:\n \n ```ts\n // NOTE: This is your frontend's code\n const authorizationHeader = await stackClientApp.getAuthorizationHeader();\n const response = await fetch(\"/my-backend-endpoint\", {\n headers: {\n ...(authorizationHeader ? { Authorization: authorizationHeader } : {}),\n },\n });\n // ...\n ```\n \n In most backend frameworks you can then access the user object by passing the request object as a `tokenStore` of the functions that access the user object:\n \n ```ts\n // NOTE: This is your backend's code\n const user = await stackServerApp.getUser({ tokenStore: request });\n return new Response(\"Hello, \" + user.displayName, { headers: { \"Cache-Control\": \"private, no-store\" } });\n ```\n \n This will work as long as `request` is an object that follows the shape `{ headers: Record<string, string | null> | { get: (name: string) => string | null } }`.\n \n <Note>\n Make sure that HTTP caching is disabled with `Cache-Control: private, no-store` for authenticated backend endpoints.\n </Note>\n \n If you cannot use `getAuthorizationHeader()`, for example because you are using a protocol other than HTTP, you can use `getAuthJson()` instead:\n \n ```ts\n // Frontend:\n await rpcCall(\"my-rpc-endpoint\", {\n data: {\n auth: await stackClientApp.getAuthJson(),\n },\n });\n \n // Backend:\n const user = await stackServerApp.getUser({ tokenStore: data.auth });\n return new RpcResponse(\"Hello, \" + user.displayName);\n ```\n </Step>\n\n <Step title=\"Done!\" />\n</Steps>\n\n## Convex Setup\n\nFollow these instructions to integrate Stack Auth with Convex.\n\n<Steps titleSize=\"h3\">\n <Step title=\"Create or identify the Convex app\">\n If the project does not already use Convex, initialize a Convex + Next.js app:\n\n ```sh\n npm create convex@latest\n ```\n\n When prompted, choose **Next.js** and **No auth**. Stack Auth will provide auth.\n\n During development, run the Convex backend and the app dev server:\n\n ```sh\n npx convex dev\n npm run dev\n ```\n </Step>\n\n <Step title=\"Install and configure Stack Auth\">\n Install Stack Auth in the app. If you have not already completed the SDK setup steps above, run the setup wizard:\n\n ```sh\n npx @stackframe/stack-cli@latest init\n ```\n\n Create or select a Stack Auth project in the dashboard. Copy the Stack Auth environment variables into the app's `.env.local` file.\n\n Also add the same Stack Auth environment variables to the Convex deployment environment in the Convex dashboard.\n </Step>\n\n <Step title=\"Configure Convex auth providers\">\n Create or update `convex/auth.config.ts`:\n\n ```ts convex/auth.config.ts\n import { getConvexProvidersConfig } from \"@stackframe/js\";\n // or: import { getConvexProvidersConfig } from \"@stackframe/react\";\n // or: import { getConvexProvidersConfig } from \"@stackframe/stack\";\n\n export default {\n providers: getConvexProvidersConfig({\n projectId: process.env.STACK_PROJECT_ID, // or process.env.NEXT_PUBLIC_STACK_PROJECT_ID\n }),\n };\n ```\n </Step>\n\n <Step title=\"Connect Convex clients to Stack Auth\">\n Update the Convex client setup so Convex receives Stack Auth tokens.\n\n In browser JavaScript:\n\n ```ts\n convexClient.setAuth(stackClientApp.getConvexClientAuth({}));\n ```\n\n In React:\n\n ```ts\n convexReactClient.setAuth(stackClientApp.getConvexClientAuth({}));\n ```\n\n For Convex HTTP clients on the server, pass a request-like token store:\n\n ```ts\n convexHttpClient.setAuth(stackClientApp.getConvexHttpClientAuth({ tokenStore: requestObject }));\n ```\n </Step>\n\n <Step title=\"Use Stack Auth user data in Convex functions\">\n In Convex queries and mutations, use Stack Auth's Convex integration to read the current user.\n\n ```ts convex/myFunctions.ts\n import { query } from \"./_generated/server\";\n import { stackServerApp } from \"../src/stack/server\";\n\n export const myQuery = query({\n handler: async (ctx, args) => {\n const user = await stackServerApp.getPartialUser({ from: \"convex\", ctx });\n return user;\n },\n });\n ```\n </Step>\n\n <Step title=\"Done!\" />\n</Steps>\n\n## Supabase Setup\n\n<Note>\n This setup covers Supabase Row Level Security (RLS) with Stack Auth JWTs. It does not sync user data between Supabase and Stack Auth. Use Stack Auth webhooks if you need data sync.\n</Note>\n\n<Steps titleSize=\"h3\">\n <Step title=\"Create Supabase RLS policies\">\n In the Supabase SQL editor, enable Row Level Security for your tables and write policies based on Supabase JWT claims.\n\n For example, this sample table demonstrates public rows, authenticated rows, and user-owned rows:\n\n ```sql\n CREATE TABLE data (\n id bigint PRIMARY KEY,\n text text NOT NULL,\n user_id UUID\n );\n\n INSERT INTO data (id, text, user_id) VALUES\n (1, 'Everyone can see this', NULL),\n (2, 'Only authenticated users can see this', NULL),\n (3, 'Only user with specific id can see this', NULL);\n\n ALTER TABLE data ENABLE ROW LEVEL SECURITY;\n\n CREATE POLICY \"Public read\" ON \"public\".\"data\" TO public\n USING (id = 1);\n\n CREATE POLICY \"Authenticated access\" ON \"public\".\"data\" TO authenticated\n USING (id = 2);\n\n CREATE POLICY \"User access\" ON \"public\".\"data\" TO authenticated\n USING (id = 3 AND auth.uid() = user_id);\n ```\n </Step>\n\n <Step title=\"Install Stack Auth and Supabase dependencies\">\n If you are starting from scratch with Next.js, you can use Supabase's template and then initialize Stack Auth:\n\n ```sh\n npx create-next-app@latest -e with-supabase stack-supabase\n cd stack-supabase\n npx @stackframe/stack-cli@latest init\n ```\n\n Add the Supabase environment variables to `.env.local`:\n\n ```.env .env.local\n NEXT_PUBLIC_SUPABASE_URL=<your-supabase-url>\n NEXT_PUBLIC_SUPABASE_ANON_KEY=<your-supabase-anon-key>\n SUPABASE_JWT_SECRET=<your-supabase-jwt-secret>\n ```\n\n Also add the Stack Auth environment variables:\n\n ```.env .env.local\n # The project ID is the only client-exposed Stack Auth variable; in Next.js it must\n # be prefixed with NEXT_PUBLIC_. STACK_SECRET_SERVER_KEY is server-only and must\n # NEVER be prefixed or exposed to the client.\n NEXT_PUBLIC_STACK_PROJECT_ID=<your-stack-project-id>\n STACK_SECRET_SERVER_KEY=<your-secret-server-key>\n ```\n </Step>\n\n <Step title=\"Mint Supabase JWTs from Stack Auth users\">\n Create a server action that signs a Supabase JWT using the current Stack Auth user ID:\n\n ```tsx utils/actions.ts\n 'use server';\n\n import { stackServerApp } from \"@/stack/server\";\n import * as jose from \"jose\";\n\n export const getSupabaseJwt = async () => {\n const user = await stackServerApp.getUser();\n\n if (!user) {\n return null;\n }\n\n const token = await new jose.SignJWT({\n sub: user.id,\n role: \"authenticated\",\n })\n .setProtectedHeader({ alg: \"HS256\" })\n .setIssuedAt()\n .setExpirationTime(\"1h\")\n .sign(new TextEncoder().encode(process.env.SUPABASE_JWT_SECRET));\n\n return token;\n };\n ```\n </Step>\n\n <Step title=\"Create a Supabase client that uses the Stack Auth JWT\">\n Create a helper that passes the server-generated JWT to Supabase:\n\n ```tsx utils/supabase-client.ts\n import { createBrowserClient } from \"@supabase/ssr\";\n import { getSupabaseJwt } from \"./actions\";\n\n export const createSupabaseClient = () => {\n return createBrowserClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,\n { accessToken: async () => await getSupabaseJwt() || \"\" },\n );\n };\n ```\n </Step>\n\n <Step title=\"Fetch Supabase data\">\n Use the Supabase client from your UI. The RLS policies will decide which rows the user can read based on the Stack Auth user ID embedded in the Supabase JWT.\n\n ```tsx app/page.tsx\n 'use client';\n\n import { createSupabaseClient } from \"@/utils/supabase-client\";\n import { useStackApp, useUser } from \"@stackframe/stack\";\n import Link from \"next/link\";\n import { useEffect, useState } from \"react\";\n\n export default function Page() {\n const app = useStackApp();\n const user = useUser();\n const supabase = createSupabaseClient();\n const [data, setData] = useState<null | any[]>(null);\n\n useEffect(() => {\n supabase.from(\"data\").select().then(({ data }) => setData(data ?? []));\n }, []);\n\n const listContent = data === null\n ? <p>Loading...</p>\n : data.length === 0\n ? <p>No notes found</p>\n : data.map((note) => <li key={note.id}>{note.text}</li>);\n\n return (\n <div>\n {user ? (\n <>\n <p>You are signed in</p>\n <p>User ID: {user.id}</p>\n <Link href={app.urls.signOut}>Sign Out</Link>\n </>\n ) : (\n <Link href={app.urls.signIn}>Sign In</Link>\n )}\n <h3>Supabase data</h3>\n <ul>{listContent}</ul>\n </div>\n );\n }\n ```\n </Step>\n\n <Step title=\"Done!\" />\n</Steps>\n\n## CLI Setup\n\nFollow these instructions to authenticate users in a command line application with Stack Auth.\n\n<Steps titleSize=\"h3\">\n <Step title=\"Add the CLI auth template\">\n Download the Stack Auth CLI authentication template and place it in your project. For Python apps, copy it as `stack_auth_cli_template.py`.\n\n Example project layout:\n\n ```text\n my-python-app/\n ├─ main.py\n └─ stack_auth_cli_template.py\n ```\n </Step>\n\n <Step title=\"Prompt the user to log in\">\n Import and call `prompt_cli_login`. It opens the browser, lets the user authenticate, and returns a refresh token.\n\n ```py main.py\n from stack_auth_cli_template import prompt_cli_login\n\n refresh_token = prompt_cli_login(\n app_url=\"https://your-app-url.example.com\",\n project_id=\"your-project-id-here\",\n publishable_client_key=\"your-publishable-client-key-here\",\n )\n\n if refresh_token is None:\n print(\"User cancelled the login process. Exiting\")\n exit(1)\n ```\n\n You can store the refresh token in a local file or keychain and only prompt the user again when no saved refresh token exists.\n </Step>\n\n <Step title=\"Exchange the refresh token for an access token\">\n Use the refresh token with Stack Auth's REST API to get an access token.\n\n ```py\n def get_access_token(refresh_token):\n access_token_response = stack_auth_request(\n \"post\",\n \"/api/v1/auth/sessions/current/refresh\",\n headers={\n \"x-stack-refresh-token\": refresh_token,\n },\n )\n\n return access_token_response[\"access_token\"]\n ```\n </Step>\n\n <Step title=\"Fetch the current user\">\n Use the access token to call the Stack Auth REST API as the logged-in user.\n\n ```py\n def get_user_object(access_token):\n return stack_auth_request(\n \"get\",\n \"/api/v1/users/me\",\n headers={\n \"x-stack-access-token\": access_token,\n },\n )\n\n user = get_user_object(get_access_token(refresh_token))\n print(\"The user is logged in as\", user[\"display_name\"] or user[\"primary_email\"])\n ```\n </Step>\n\n <Step title=\"Done!\" />\n</Steps>";
export const setupToolIds = ["nextjs","react","js","tanstack-start","tanstack-query","nodejs","bun","convex","supabase","cli"];
export const setupTabMetadata = [{"toolId":"nextjs","title":"Next.js"},{"toolId":"react","title":"React"},{"toolId":"js","title":"JS/TS"},{"toolId":"tanstack-start","title":"Tanstack Start"},{"toolId":"nodejs","title":"Node.js"},{"toolId":"bun","title":"Bun"},{"toolId":"convex","title":"Convex"},{"toolId":"supabase","title":"Supabase"},{"toolId":"cli","title":"CLI"}];
export const unifiedAiPromptTabTitle = "Unified AI Prompt";
export const copyGeneratedSetupPrompt = async (event) => {
const button = event.currentTarget;
try {
await navigator.clipboard.writeText(generatedSetupPromptText);
button.textContent = "Copied";
} catch {
button.textContent = "Copy failed";
}
window.setTimeout(() => {
button.textContent = "Copy prompt";
}, 1300);
};
export const getSelectedSetupToolIdsFromUrl = () => {
if (typeof window === "undefined") {
return [];
}
const selectedToolIds = new Set(setupToolIds);
return (new URLSearchParams(window.location.search).get("tools") ?? "")
.split(",")
.map((toolId) => toolId.trim())
.filter((toolId) => selectedToolIds.has(toolId));
};
export const writeSelectedSetupToolIdsToUrl = (selectedToolIds) => {
if (typeof window === "undefined") {
return;
}
const url = new URL(window.location.href);
const orderedSelectedToolIds = setupToolIds.filter((toolId) => selectedToolIds.has(toolId));
if (orderedSelectedToolIds.length === 0) {
url.searchParams.delete("tools");
} else {
url.searchParams.set("tools", orderedSelectedToolIds.join(","));
}
window.history.replaceState(null, "", url.pathname + url.search + url.hash);
};
export const updateSetupBuilder = (root, syncUrl = true) => {
const selectedToolIds = new Set(
Array.from(root.querySelectorAll("[data-setup-tool-card='true'][aria-pressed='true']"))
.map((card) => card.getAttribute("data-tool-id"))
.filter((toolId) => toolId != null)
);
if (syncUrl) {
writeSelectedSetupToolIdsToUrl(selectedToolIds);
}
const visibleTabTitles = new Set(setupTabMetadata
.filter((tab) => selectedToolIds.has(tab.toolId))
.map((tab) => tab.title)
);
if (visibleTabTitles.size > 0) {
visibleTabTitles.add(unifiedAiPromptTabTitle);
}
const tabsRoot = root.querySelector("[data-setup-tabs-root='true']");
const emptyState = root.querySelector("[data-setup-tabs-empty='true']");
if (emptyState != null) {
emptyState.hidden = visibleTabTitles.size > 0;
emptyState.style.display = visibleTabTitles.size > 0 ? "none" : "";
}
if (tabsRoot == null) {
return;
}
tabsRoot.hidden = visibleTabTitles.size === 0;
tabsRoot.style.display = visibleTabTitles.size === 0 ? "none" : "";
const tabButtons = Array.from(tabsRoot.querySelectorAll("[role='tab']"));
let firstVisibleTabButton = null;
let selectedVisibleTabButton = null;
for (const tabButton of tabButtons) {
const title = tabButton.textContent?.trim() ?? "";
const shouldShow = visibleTabTitles.has(title);
tabButton.hidden = !shouldShow;
tabButton.style.display = shouldShow ? "" : "none";
if (shouldShow && firstVisibleTabButton == null) {
firstVisibleTabButton = tabButton;
}
if (shouldShow && tabButton.getAttribute("aria-selected") === "true") {
selectedVisibleTabButton = tabButton;
}
}
if (visibleTabTitles.size > 0 && selectedVisibleTabButton == null) {
firstVisibleTabButton?.click();
}
};
export const initializeSetupBuilder = (node) => {
if (node == null || node.dataset.setupBuilderInitialized === "true") {
return;
}
node.dataset.setupBuilderInitialized = "true";
const selectedToolIds = new Set(getSelectedSetupToolIdsFromUrl());
for (const toolCard of node.querySelectorAll("[data-setup-tool-card='true']")) {
toolCard.setAttribute("aria-pressed", selectedToolIds.has(toolCard.getAttribute("data-tool-id")) ? "true" : "false");
}
updateSetupBuilder(node, false);
};
export const onSetupToolClick = (event) => {
const button = event.currentTarget;
const root = button.closest("[data-setup-builder='true']");
if (root == null) {
return;
}
button.setAttribute("aria-pressed", button.getAttribute("aria-pressed") === "true" ? "false" : "true");
updateSetupBuilder(root);
};
<Note>
<p className="font-semibold">Setting up with AI? Use this single prompt:</p>
<div className="not-prose relative mt-3">
<pre className="max-h-40 overflow-auto whitespace-pre-wrap rounded-2xl border border-[#cdd7f4] bg-white/75 px-4 py-3 pr-32 font-mono text-xs leading-6 text-zinc-700 backdrop-blur-sm sm:text-sm dark:border-[#33476d] dark:bg-black/20 dark:text-zinc-200"><code>{generatedSetupPromptText}</code></pre>
<button
type="button"
onClick={copyGeneratedSetupPrompt}
className="absolute right-2 top-2 inline-flex items-center justify-center rounded-lg border border-[#9fb5e4] bg-[#eaf1ff] px-3 py-1.5 text-xs font-semibold text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 focus-visible:ring-offset-[#f3f6ff] dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51] dark:focus-visible:ring-offset-[#0f1a2e]"
>
Copy prompt
</button>
</div>
</Note>
<div ref={initializeSetupBuilder} data-setup-builder="true" className="mt-10">
<div>
<h2 className="text-3xl font-bold tracking-tight text-slate-900 dark:text-white">Choose your tech stack</h2>
<p className="mt-2 text-sm font-medium text-slate-500 dark:text-slate-400">Choose all that apply.</p>
</div>
<div className="not-prose mt-5 space-y-4 rounded-2xl border border-[#d6e4ff] bg-gradient-to-b from-[#f7faff] to-[#eaf2ff] p-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.9),0_10px_30px_-24px_rgba(47,79,140,0.35)] dark:border-[#1f2d45] dark:from-[#11203a] dark:to-[#070f1f] dark:shadow-[inset_0_1px_0_rgba(112,152,224,0.18),0_16px_34px_-24px_rgba(2,8,20,0.85)] sm:p-4">
<section className="grid gap-3 sm:grid-cols-[6rem_1fr] sm:items-start">
<h3 className="pt-1 text-sm font-semibold text-[#2e446f] dark:text-[#d8e7ff]">Frontend</h3>
<div className="grid grid-cols-3 gap-x-2 gap-y-3 sm:grid-cols-4 sm:gap-x-3 sm:gap-y-3 lg:grid-cols-6">
<button
type="button"
aria-pressed="false"
data-setup-tool-card="true"
data-tool-id="nextjs"
data-tool-label="Next.js"
data-tool-has-tabs="true"
data-tool-extra-features=""
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="Next.js"
>
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<span
aria-hidden="true"
className="h-8 w-8 bg-black opacity-80 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-90 dark:bg-white dark:opacity-95"
style={{
WebkitMask: "url(/images/setup-tools/nextjs.svg) center / contain no-repeat",
mask: "url(/images/setup-tools/nextjs.svg) center / contain no-repeat",
}}
/>
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
</div>
<span
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title="Next.js"
>
Next.js
</span>
</button>
<button
type="button"
aria-pressed="false"
data-setup-tool-card="true"
data-tool-id="react"
data-tool-label="React"
data-tool-has-tabs="true"
data-tool-extra-features=""
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="React"
>
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<img
src="/images/setup-tools/react.svg"
alt=""
aria-hidden="true"
className="h-8 w-8 object-contain opacity-90 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-100"
/>
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
</div>
<span
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title="React"
>
React
</span>
</button>
<button
type="button"
aria-pressed="false"
data-setup-tool-card="true"
data-tool-id="js"
data-tool-label="Other JS/TS"
data-tool-has-tabs="true"
data-tool-extra-features=""
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="Other JS/TS"
>
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<img
src="/images/setup-tools/javascript.svg"
alt=""
aria-hidden="true"
className="h-8 w-8 object-contain opacity-90 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-100"
/>
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
</div>
<span
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title="Other JS/TS"
>
Other JS/TS
</span>
</button>
<button
type="button"
aria-pressed="false"
data-setup-tool-card="true"
data-tool-id="tanstack-start"
data-tool-label="Tanstack Start"
data-tool-has-tabs="true"
data-tool-extra-features="tanstack-query"
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="Tanstack Start"
>
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<span
aria-hidden="true"
className="h-8 w-8 bg-black opacity-80 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-90 dark:bg-white dark:opacity-95"
style={{
WebkitMask: "url(/images/setup-tools/tanstack.svg) center / contain no-repeat",
mask: "url(/images/setup-tools/tanstack.svg) center / contain no-repeat",
}}
/>
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
</div>
<span
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title="Tanstack Start"
>
Tanstack Start
</span>
</button>
<button
type="button"
aria-pressed="false"
data-setup-tool-card="true"
data-tool-id="tanstack-query"
data-tool-label="Tanstack Query"
data-tool-has-tabs="false"
data-tool-extra-features="tanstack-query"
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="Tanstack Query"
>
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<span
aria-hidden="true"
className="h-8 w-8 bg-black opacity-80 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-90 dark:bg-white dark:opacity-95"
style={{
WebkitMask: "url(/images/setup-tools/tanstack.svg) center / contain no-repeat",
mask: "url(/images/setup-tools/tanstack.svg) center / contain no-repeat",
}}
/>
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
</div>
<span
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title="Tanstack Query"
>
Tanstack Query
</span>
</button>
</div>
</section>
<section className="grid gap-3 sm:grid-cols-[6rem_1fr] sm:items-start">
<h3 className="pt-1 text-sm font-semibold text-[#2e446f] dark:text-[#d8e7ff]">Backend</h3>
<div className="grid grid-cols-3 gap-x-2 gap-y-3 sm:grid-cols-4 sm:gap-x-3 sm:gap-y-3 lg:grid-cols-6">
<button
type="button"
aria-pressed="false"
data-setup-tool-card="true"
data-tool-id="nextjs"
data-tool-label="Next.js"
data-tool-has-tabs="true"
data-tool-extra-features=""
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="Next.js"
>
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<span
aria-hidden="true"
className="h-8 w-8 bg-black opacity-80 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-90 dark:bg-white dark:opacity-95"
style={{
WebkitMask: "url(/images/setup-tools/nextjs.svg) center / contain no-repeat",
mask: "url(/images/setup-tools/nextjs.svg) center / contain no-repeat",
}}
/>
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
</div>
<span
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title="Next.js"
>
Next.js
</span>
</button>
<button
type="button"
aria-pressed="false"
data-setup-tool-card="true"
data-tool-id="nodejs"
data-tool-label="Node.js"
data-tool-has-tabs="true"
data-tool-extra-features=""
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="Node.js"
>
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<img
src="/images/setup-tools/nodejs.svg"
alt=""
aria-hidden="true"
className="h-8 w-8 object-contain opacity-90 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-100"
/>
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
</div>
<span
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title="Node.js"
>
Node.js
</span>
</button>
<button
type="button"
aria-pressed="false"
data-setup-tool-card="true"
data-tool-id="bun"
data-tool-label="Bun"
data-tool-has-tabs="true"
data-tool-extra-features=""
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="Bun"
>
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<img
src="/images/setup-tools/bun.svg"
alt=""
aria-hidden="true"
className="h-8 w-8 object-contain opacity-90 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-100"
/>
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
</div>
<span
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title="Bun"
>
Bun
</span>
</button>
<button
type="button"
aria-pressed="false"
data-setup-tool-card="true"
data-tool-id="convex"
data-tool-label="Convex"
data-tool-has-tabs="true"
data-tool-extra-features=""
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="Convex"
>
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<img
src="/images/setup-tools/convex.svg"
alt=""
aria-hidden="true"
className="h-8 w-8 object-contain opacity-90 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-100"
/>
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
</div>
<span
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title="Convex"
>
Convex
</span>
</button>
</div>
</section>
<section className="grid gap-3 sm:grid-cols-[6rem_1fr] sm:items-start">
<h3 className="pt-1 text-sm font-semibold text-[#2e446f] dark:text-[#d8e7ff]">Database</h3>
<div className="grid grid-cols-3 gap-x-2 gap-y-3 sm:grid-cols-4 sm:gap-x-3 sm:gap-y-3 lg:grid-cols-6">
<button
type="button"
aria-pressed="false"
data-setup-tool-card="true"
data-tool-id="convex"
data-tool-label="Convex"
data-tool-has-tabs="true"
data-tool-extra-features=""
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="Convex"
>
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<img
src="/images/setup-tools/convex.svg"
alt=""
aria-hidden="true"
className="h-8 w-8 object-contain opacity-90 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-100"
/>
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
</div>
<span
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title="Convex"
>
Convex
</span>
</button>
<button
type="button"
aria-pressed="false"
data-setup-tool-card="true"
data-tool-id="supabase"
data-tool-label="Supabase"
data-tool-has-tabs="true"
data-tool-extra-features=""
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="Supabase"
>
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<img
src="/images/setup-tools/supabase.svg"
alt=""
aria-hidden="true"
className="h-8 w-8 object-contain opacity-90 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-100"
/>
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
</div>
<span
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title="Supabase"
>
Supabase
</span>
</button>
</div>
</section>
<section className="grid gap-3 sm:grid-cols-[6rem_1fr] sm:items-start">
<h3 className="pt-1 text-sm font-semibold text-[#2e446f] dark:text-[#d8e7ff]">Other</h3>
<div className="grid grid-cols-3 gap-x-2 gap-y-3 sm:grid-cols-4 sm:gap-x-3 sm:gap-y-3 lg:grid-cols-6">
<button
type="button"
aria-pressed="false"
data-setup-tool-card="true"
data-tool-id="cli"
data-tool-label="CLI"
data-tool-has-tabs="true"
data-tool-extra-features=""
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="CLI"
>
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
<span
aria-hidden="true"
className="h-8 w-8 bg-black opacity-80 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-90 dark:bg-white dark:opacity-95"
style={{
WebkitMask: "url(/images/setup-tools/cli.svg) center / contain no-repeat",
mask: "url(/images/setup-tools/cli.svg) center / contain no-repeat",
}}
/>
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
</div>
<span
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
title="CLI"
>
CLI
</span>
</button>
</div>
</section>
</div>
<div className="mt-8">
<p data-setup-tabs-empty="true" className="not-prose rounded-2xl border border-[#c5d7f6] bg-white/65 p-5 text-sm text-[#4a5f89] dark:border-[#2c4c7d] dark:bg-[#0c1627]/45 dark:text-[#8fa4cc]">
Select a tool to show setup instructions.
</p>
<div data-setup-tabs-root="true" hidden style={{ display: "none" }}>
<Tabs>
<Tab title="Unified AI Prompt">
Setting up with AI? Use this single prompt in your coding agent to set up Stack Auth for your selected stack.
<div className="not-prose relative mt-3">
<pre className="max-h-40 overflow-auto whitespace-pre-wrap rounded-2xl border border-[#cdd7f4] bg-white/75 px-4 py-3 pr-32 font-mono text-xs leading-6 text-zinc-700 backdrop-blur-sm sm:text-sm dark:border-[#33476d] dark:bg-black/20 dark:text-zinc-200"><code>{generatedSetupPromptText}</code></pre>
<button
type="button"
onClick={copyGeneratedSetupPrompt}
className="absolute right-2 top-2 inline-flex items-center justify-center rounded-lg border border-[#9fb5e4] bg-[#eaf1ff] px-3 py-1.5 text-xs font-semibold text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
>
Copy prompt
</button>
</div>
</Tab>
<Tab title="Next.js">
<div className="not-prose mb-1 flex justify-end">
<button
type="button"
onClick={copyGeneratedSetupPrompt}
className="inline-flex items-center justify-center rounded-md border border-[#9fb5e4] bg-[#eaf1ff] px-2 py-1 text-[11px] font-semibold leading-none text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
>
Copy prompt
</button>
</div>
## Next.js SDK Setup Instructions
Follow these instructions in order to set up and get started with the Stack Auth SDK for Next.js .
<Steps titleSize="h3">
<Step title="Install dependencies">
First, install the `@stackframe/stack` npm package with your preferred package manager:
```sh
npm i @stackframe/stack
# or: pnpm i @stackframe/stack
# or: yarn add @stackframe/stack
# or: bun add @stackframe/stack
```
</Step>
<Step title="Initializing the Stack App">
Next, let us create the Stack App object for your project. This is the most important object in a Stack Auth project.
In a frontend where you cannot keep a secret key safe, you would use the `StackClientApp` constructor:
```ts src/stack/client.ts
import { StackClientApp } from "@stackframe/stack";
export const stackClientApp = new StackClientApp({
tokenStore: "cookie", // "nextjs-cookie" for Next.js, "cookie" for other web frontends, null for backend environments
urls: {
default: {
type: "hosted",
}
},
});
```
In a backend where you can keep a secret key safe, you can use the `StackServerApp`, which provides access to more sensitive APIs compared to `StackClientApp`:
```ts src/stack/server.ts
import { StackServerApp } from "@stackframe/stack";
import { stackClientApp } from "./client";
export const stackServerApp = new StackServerApp({
inheritsFrom: stackClientApp,
});
```
</Step>
<Step title="Setting up the project">
It's now time to connect your code to a Stack Auth project.
You can either run Stack Auth's dev environment locally, or connect to a production project hosted in the cloud.
<AccordionGroup>
<Accordion title="Option 1: Running Stack Auth's dev environment (recommended)" defaultOpen>
First, create a `stack.config.ts` configuration file in the root directory of the workspace (or anywhere else):
```ts stack.config.ts
import type { StackConfig } from "@stackframe/stack";
// default: show-onboarding, which shows the onboarding flow for this project when Stack Auth starts
export const config: StackConfig = "show-onboarding";
```
To run your application with Stack Auth, you can then start the dev environment and set environment variables expected by your application. Stack Auth's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json:
```sh
npm i -D @stackframe/stack-cli
# or: pnpm i -D @stackframe/stack-cli
# or: yarn add -D @stackframe/stack-cli
# or: bun add --dev @stackframe/stack-cli
```
```json package.json
{
// ...
"scripts": {
// ...
"dev": "stack dev --config-file ./stack.config.ts -- npm run dev:without-stack-auth",
"dev:without-stack-auth": "<your-existing-dev-script>"
}
}
```
</Accordion>
<Accordion title="Option 2: Connecting to a production project hosted in the cloud">
If you'd rather run your development environment on our infrastructure, or you already have an existing product, you can also connect a cloud project.
This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not).
#### Frontend
Go to your project's dashboard on [app.stack-auth.com](https://app.stack-auth.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored):
```.env .env.local
STACK_PROJECT_ID=<your-project-id>
```
Alternatively, you can also just set the project ID in the `stack/client.ts` file:
```ts src/stack/client.ts
export const stackClientApp = new StackClientApp({
// ...
projectId: "your-project-id",
});
```
#### Backend (or both frontend and backend)
First, navigate to the [Project Keys](https://app.stack-auth.com/projects/-selector-/project-keys) page in the Stack Auth dashboard and generate a new set of keys.
Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored):
```.env .env.local
STACK_PROJECT_ID=<your-project-id>
STACK_SECRET_SERVER_KEY=<your-secret-server-key>
```
They'll automatically be picked up by the `StackServerApp` constructor.
</Accordion>
</AccordionGroup>
</Step>
<Step title="Creating a <StackProvider /> and <StackTheme />">
In React frameworks, Stack Auth provides `StackProvider` and `StackTheme` components that should wrap your entire app at the root level.
You can do this in the `layout.tsx` file in the `app` directory:
```tsx src/app/layout.tsx
import { Suspense } from "react";
import { StackProvider, StackTheme } from "@stackframe/stack";
import { stackServerApp } from "@/stack/server";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<StackProvider app={stackServerApp}>
<StackTheme>
{children}
</StackTheme>
</StackProvider>
);
}
```
</Step>
<Step title="Add Suspense boundary">
Stack Auth also provides additional `useXyz` React hooks for `getXyz`/`listXyz` functions. For example, `useUser` is like `getUser`, but as a suspending React hook.
To support the suspension, you need to add a suspense boundary around your app.
In Next.js, this can be easily done by adding a `loading.tsx` file in the `app` directory:
```tsx src/app/loading.tsx
export default function Loading() {
return <div>Loading...</div>;
}
```
</Step>
<Step title="Done!" />
</Steps>
</Tab>
<Tab title="React">
<div className="not-prose mb-1 flex justify-end">
<button
type="button"
onClick={copyGeneratedSetupPrompt}
className="inline-flex items-center justify-center rounded-md border border-[#9fb5e4] bg-[#eaf1ff] px-2 py-1 text-[11px] font-semibold leading-none text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
>
Copy prompt
</button>
</div>
## React SDK Setup Instructions
Follow these instructions in order to set up and get started with the Stack Auth SDK for React .
<Steps titleSize="h3">
<Step title="Install dependencies">
First, install the `@stackframe/react` npm package with your preferred package manager:
```sh
npm i @stackframe/react
# or: pnpm i @stackframe/react
# or: yarn add @stackframe/react
# or: bun add @stackframe/react
```
</Step>
<Step title="Initializing the Stack App">
Next, let us create the Stack App object for your project. This is the most important object in a Stack Auth project.
In a frontend where you cannot keep a secret key safe, you would use the `StackClientApp` constructor:
```ts src/stack/client.ts
import { StackClientApp } from "@stackframe/react";
export const stackClientApp = new StackClientApp({
tokenStore: "cookie", // "nextjs-cookie" for Next.js, "cookie" for other web frontends, null for backend environments
urls: {
default: {
type: "hosted",
}
},
});
```
</Step>
<Step title="Setting up the project">
It's now time to connect your code to a Stack Auth project.
You can either run Stack Auth's dev environment locally, or connect to a production project hosted in the cloud.
<AccordionGroup>
<Accordion title="Option 1: Running Stack Auth's dev environment (recommended)" defaultOpen>
First, create a `stack.config.ts` configuration file in the root directory of the workspace (or anywhere else):
```ts stack.config.ts
import type { StackConfig } from "@stackframe/react";
// default: show-onboarding, which shows the onboarding flow for this project when Stack Auth starts
export const config: StackConfig = "show-onboarding";
```
To run your application with Stack Auth, you can then start the dev environment and set environment variables expected by your application. Stack Auth's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json:
```sh
npm i -D @stackframe/stack-cli
# or: pnpm i -D @stackframe/stack-cli
# or: yarn add -D @stackframe/stack-cli
# or: bun add --dev @stackframe/stack-cli
```
```json package.json
{
// ...
"scripts": {
// ...
"dev": "stack dev --config-file ./stack.config.ts -- npm run dev:without-stack-auth",
"dev:without-stack-auth": "<your-existing-dev-script>"
}
}
```
</Accordion>
<Accordion title="Option 2: Connecting to a production project hosted in the cloud">
If you'd rather run your development environment on our infrastructure, or you already have an existing product, you can also connect a cloud project.
This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not).
#### Frontend
Go to your project's dashboard on [app.stack-auth.com](https://app.stack-auth.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored):
```.env .env.local
STACK_PROJECT_ID=<your-project-id>
```
Alternatively, you can also just set the project ID in the `stack/client.ts` file:
```ts src/stack/client.ts
export const stackClientApp = new StackClientApp({
// ...
projectId: "your-project-id",
});
```
#### Backend (or both frontend and backend)
First, navigate to the [Project Keys](https://app.stack-auth.com/projects/-selector-/project-keys) page in the Stack Auth dashboard and generate a new set of keys.
Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored):
```.env .env.local
STACK_PROJECT_ID=<your-project-id>
STACK_SECRET_SERVER_KEY=<your-secret-server-key>
```
They'll automatically be picked up by the `StackServerApp` constructor.
</Accordion>
</AccordionGroup>
</Step>
<Step title="Creating a <StackProvider /> and <StackTheme />">
In React frameworks, Stack Auth provides `StackProvider` and `StackTheme` components that should wrap your entire app at the root level.
For example, if you have an `App.tsx` file, update it as follows:
```tsx src/App.tsx
import { StackProvider, StackTheme } from "@stackframe/react";
import { stackClientApp } from "./stack/client";
export default function App() {
return (
<StackProvider app={stackClientApp}>
<StackTheme>
{/* your app content */}
</StackTheme>
</StackProvider>
);
}
```
</Step>
<Step title="Add Suspense boundary">
Stack Auth also provides additional `useXyz` React hooks for `getXyz`/`listXyz` functions. For example, `useUser` is like `getUser`, but as a suspending React hook.
To support the suspension, you need to add a suspense boundary around your app.
The easiest way to do this is to just wrap your entire app in a `Suspense` component:
```tsx src/App.tsx
import { Suspense } from "react";
import { StackProvider, StackTheme } from "@stackframe/react";
import { stackClientApp } from "./stack/client";
export default function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<StackProvider app={stackClientApp}>
<StackTheme>
{/* your app content */}
</StackTheme>
</StackProvider>
</Suspense>
);
}
```
</Step>
<Step title="Done!" />
</Steps>
</Tab>
<Tab title="JS/TS">
<div className="not-prose mb-1 flex justify-end">
<button
type="button"
onClick={copyGeneratedSetupPrompt}
className="inline-flex items-center justify-center rounded-md border border-[#9fb5e4] bg-[#eaf1ff] px-2 py-1 text-[11px] font-semibold leading-none text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
>
Copy prompt
</button>
</div>
## Other JS/TS SDK Setup Instructions
Follow these instructions in order to set up and get started with the Stack Auth SDK for Other JS/TS .
<Steps titleSize="h3">
<Step title="Install dependencies">
First, install the `@stackframe/js` npm package with your preferred package manager:
```sh
npm i @stackframe/js
# or: pnpm i @stackframe/js
# or: yarn add @stackframe/js
# or: bun add @stackframe/js
```
</Step>
<Step title="Initializing the Stack App">
Next, let us create the Stack App object for your project. This is the most important object in a Stack Auth project.
In a frontend where you cannot keep a secret key safe, you would use the `StackClientApp` constructor:
```ts src/stack/client.ts
import { StackClientApp } from "@stackframe/js";
export const stackClientApp = new StackClientApp({
tokenStore: "cookie", // "nextjs-cookie" for Next.js, "cookie" for other web frontends, null for backend environments
urls: {
default: {
type: "hosted",
}
},
});
```
In a backend where you can keep a secret key safe, you can use the `StackServerApp`, which provides access to more sensitive APIs compared to `StackClientApp`:
```ts src/stack/server.ts
import { StackServerApp } from "@stackframe/js";
export const stackServerApp = new StackServerApp({
tokenStore: null,
urls: {
default: {
type: "hosted",
}
},
});
```
In frameworks that are both front- and backend, like Next.js, you can also create a `StackServerApp` from a `StackClientApp` object:
```ts src/stack/server.ts
import { StackServerApp } from "@stackframe/js";
import { stackClientApp } from "./client";
export const stackServerApp = new StackServerApp({
inheritsFrom: stackClientApp,
});
```
</Step>
<Step title="Setting up the project">
It's now time to connect your code to a Stack Auth project.
You can either run Stack Auth's dev environment locally, or connect to a production project hosted in the cloud.
<AccordionGroup>
<Accordion title="Option 1: Running Stack Auth's dev environment (recommended)" defaultOpen>
First, create a `stack.config.ts` configuration file in the root directory of the workspace (or anywhere else):
```ts stack.config.ts
import type { StackConfig } from "@stackframe/js";
// default: show-onboarding, which shows the onboarding flow for this project when Stack Auth starts
export const config: StackConfig = "show-onboarding";
```
To run your application with Stack Auth, you can then start the dev environment and set environment variables expected by your application. Stack Auth's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json:
```sh
npm i -D @stackframe/stack-cli
# or: pnpm i -D @stackframe/stack-cli
# or: yarn add -D @stackframe/stack-cli
# or: bun add --dev @stackframe/stack-cli
```
```json package.json
{
// ...
"scripts": {
// ...
"dev": "stack dev --config-file ./stack.config.ts -- npm run dev:without-stack-auth",
"dev:without-stack-auth": "<your-existing-dev-script>"
}
}
```
</Accordion>
<Accordion title="Option 2: Connecting to a production project hosted in the cloud">
If you'd rather run your development environment on our infrastructure, or you already have an existing product, you can also connect a cloud project.
This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not).
#### Frontend
Go to your project's dashboard on [app.stack-auth.com](https://app.stack-auth.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored):
```.env .env.local
STACK_PROJECT_ID=<your-project-id>
```
Alternatively, you can also just set the project ID in the `stack/client.ts` file:
```ts src/stack/client.ts
export const stackClientApp = new StackClientApp({
// ...
projectId: "your-project-id",
});
```
#### Backend (or both frontend and backend)
First, navigate to the [Project Keys](https://app.stack-auth.com/projects/-selector-/project-keys) page in the Stack Auth dashboard and generate a new set of keys.
Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored):
```.env .env.local
STACK_PROJECT_ID=<your-project-id>
STACK_SECRET_SERVER_KEY=<your-secret-server-key>
```
They'll automatically be picked up by the `StackServerApp` constructor.
</Accordion>
</AccordionGroup>
</Step>
<Step title="Backend: Update callers with header & get user">
You are now ready to use the Stack Auth SDK. If you have any frontends calling your backend endpoints, you may want to pass along the Stack Auth tokens in a header such that you can access the same user object on your backend.
The most ergonomic way to do this is to pass the result of `stackClientApp.getAuthorizationHeader()` as the `Authorization` header into your backend endpoints when the user is signed in:
```ts
// NOTE: This is your frontend's code
const authorizationHeader = await stackClientApp.getAuthorizationHeader();
const response = await fetch("/my-backend-endpoint", {
headers: {
...(authorizationHeader ? { Authorization: authorizationHeader } : {}),
},
});
// ...
```
In most backend frameworks you can then access the user object by passing the request object as a `tokenStore` of the functions that access the user object:
```ts
// NOTE: This is your backend's code
const user = await stackServerApp.getUser({ tokenStore: request });
return new Response("Hello, " + user.displayName, { headers: { "Cache-Control": "private, no-store" } });
```
This will work as long as `request` is an object that follows the shape `{ headers: Record<string, string | null> | { get: (name: string) => string | null } }`.
> Make sure that HTTP caching is disabled with `Cache-Control: private, no-store` for authenticated backend endpoints.
If you cannot use `getAuthorizationHeader()`, for example because you are using a protocol other than HTTP, you can use `getAuthJson()` instead:
```ts
// Frontend:
await rpcCall("my-rpc-endpoint", {
data: {
auth: await stackClientApp.getAuthJson(),
},
});
// Backend:
const user = await stackServerApp.getUser({ tokenStore: data.auth });
return new RpcResponse("Hello, " + user.displayName);
```
</Step>
<Step title="Done!" />
</Steps>
</Tab>
<Tab title="Tanstack Start">
<div className="not-prose mb-1 flex justify-end">
<button
type="button"
onClick={copyGeneratedSetupPrompt}
className="inline-flex items-center justify-center rounded-md border border-[#9fb5e4] bg-[#eaf1ff] px-2 py-1 text-[11px] font-semibold leading-none text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
>
Copy prompt
</button>
</div>
## Tanstack Start SDK Setup Instructions
Follow these instructions in order to set up and get started with the Stack Auth SDK for Tanstack Start .
<Steps titleSize="h3">
<Step title="Install dependencies">
First, install the `@stackframe/tanstack-start` npm package with your preferred package manager:
```sh
npm i @stackframe/tanstack-start
# or: pnpm i @stackframe/tanstack-start
# or: yarn add @stackframe/tanstack-start
# or: bun add @stackframe/tanstack-start
```
</Step>
<Step title="Initializing the Stack App">
Next, let us create the Stack App object for your project. This is the most important object in a Stack Auth project.
In a frontend where you cannot keep a secret key safe, you would use the `StackClientApp` constructor:
```ts src/stack/client.ts
import { StackClientApp } from "@stackframe/tanstack-start";
export const stackClientApp = new StackClientApp({
tokenStore: "cookie", // "nextjs-cookie" for Next.js, "cookie" for other web frontends, null for backend environments
urls: {
default: {
type: "hosted",
}
},
});
```
</Step>
<Step title="Setting up the project">
It's now time to connect your code to a Stack Auth project.
You can either run Stack Auth's dev environment locally, or connect to a production project hosted in the cloud.
<AccordionGroup>
<Accordion title="Option 1: Running Stack Auth's dev environment (recommended)" defaultOpen>
First, create a `stack.config.ts` configuration file in the root directory of the workspace (or anywhere else):
```ts stack.config.ts
import type { StackConfig } from "@stackframe/tanstack-start";
// default: show-onboarding, which shows the onboarding flow for this project when Stack Auth starts
export const config: StackConfig = "show-onboarding";
```
To run your application with Stack Auth, you can then start the dev environment and set environment variables expected by your application. Stack Auth's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json:
```sh
npm i -D @stackframe/stack-cli
# or: pnpm i -D @stackframe/stack-cli
# or: yarn add -D @stackframe/stack-cli
# or: bun add --dev @stackframe/stack-cli
```
```json package.json
{
// ...
"scripts": {
// ...
"dev": "stack dev --config-file ./stack.config.ts -- npm run dev:without-stack-auth",
"dev:without-stack-auth": "<your-existing-dev-script>"
}
}
```
</Accordion>
<Accordion title="Option 2: Connecting to a production project hosted in the cloud">
If you'd rather run your development environment on our infrastructure, or you already have an existing product, you can also connect a cloud project.
This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not).
#### Frontend
Go to your project's dashboard on [app.stack-auth.com](https://app.stack-auth.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored):
```.env .env.local
STACK_PROJECT_ID=<your-project-id>
```
Alternatively, you can also just set the project ID in the `stack/client.ts` file:
```ts src/stack/client.ts
export const stackClientApp = new StackClientApp({
// ...
projectId: "your-project-id",
});
```
#### Backend (or both frontend and backend)
First, navigate to the [Project Keys](https://app.stack-auth.com/projects/-selector-/project-keys) page in the Stack Auth dashboard and generate a new set of keys.
Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored):
```.env .env.local
STACK_PROJECT_ID=<your-project-id>
STACK_SECRET_SERVER_KEY=<your-secret-server-key>
```
They'll automatically be picked up by the `StackServerApp` constructor.
</Accordion>
</AccordionGroup>
</Step>
<Step title="Creating a <StackProvider /> and <StackTheme />">
In React frameworks, Stack Auth provides `StackProvider` and `StackTheme` components that should wrap your entire app at the root level.
TanStack Start uses file-based routes. The provider goes inside the root route's `component` (the inner React tree), while the document shell stays in `shellComponent`. Update `src/routes/__root.tsx`:
```tsx src/routes/__root.tsx
import { StackProvider, StackTheme } from "@stackframe/tanstack-start";
import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router";
import type { ReactNode } from "react";
import { stackClientApp } from "../stack/client";
export const Route = createRootRoute({
shellComponent: RootDocument,
component: RootComponent,
});
function RootDocument({ children }: { children: ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
);
}
function RootComponent() {
return (
<StackProvider app={stackClientApp}>
<StackTheme>
<Outlet />
</StackTheme>
</StackProvider>
);
}
```
Do not edit `src/routeTree.gen.ts` — it is regenerated automatically by the TanStack Start router from the files under `src/routes/`.
</Step>
<Step title="Add Suspense boundary">
Stack Auth also provides additional `useXyz` React hooks for `getXyz`/`listXyz` functions. For example, `useUser` is like `getUser`, but as a suspending React hook.
To support the suspension, you need to add a suspense boundary around your app.
wrap the `<Outlet />` in your root route with a `Suspense` boundary so the document shell can stream while child routes wait on Stack Auth. Update `RootComponent` in `src/routes/__root.tsx`:
```tsx src/routes/__root.tsx
import { Suspense } from "react";
// ...other imports...
function RootComponent() {
return (
<StackProvider app={stackClientApp}>
<StackTheme>
<Suspense fallback={<div>Loading...</div>}>
<Outlet />
</Suspense>
</StackTheme>
</StackProvider>
);
}
```
</Step>
<Step title="Add the Stack handler route">
Stack Auth's auth flows (sign-in, sign-up, OAuth callbacks, password reset, etc.) are rendered by a single `StackHandler` component mounted at `/handler/*`. In TanStack Start, expose it as a splat file route at `src/routes/handler/$.tsx`:
```tsx src/routes/handler/$.tsx
import { StackHandler } from "@stackframe/tanstack-start";
import { createFileRoute, useLocation } from "@tanstack/react-router";
export const Route = createFileRoute("/handler/$")({
ssr: false,
component: HandlerPage,
});
function HandlerPage() {
const { pathname } = useLocation();
return <StackHandler fullPage location={pathname} />;
}
```
Two TanStack-specific notes:
- The route is opted out of SSR with `ssr: false`. The handler runs browser-only auth flows (cookies, redirects, popups), so rendering it on the server provides no benefit and can fight with hydration. Other routes can opt into or out of SSR per-route the same way.
- Stack Auth resolves the current user during SSR by reading TanStack Start's request cookies through `@stackframe/tanstack-start`'s server context. No extra wiring is required — `useUser()` "just works" on both server and client routes as long as `tokenStore: "cookie"` is set on `StackClientApp`.
</Step>
<Step title="Done!" />
</Steps>
</Tab>
<Tab title="Node.js">
<div className="not-prose mb-1 flex justify-end">
<button
type="button"
onClick={copyGeneratedSetupPrompt}
className="inline-flex items-center justify-center rounded-md border border-[#9fb5e4] bg-[#eaf1ff] px-2 py-1 text-[11px] font-semibold leading-none text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
>
Copy prompt
</button>
</div>
## Node.js SDK Setup Instructions
Follow these instructions in order to set up and get started with the Stack Auth SDK for Node.js .
<Steps titleSize="h3">
<Step title="Install dependencies">
First, install the `@stackframe/js` npm package with your preferred package manager:
```sh
npm i @stackframe/js
# or: pnpm i @stackframe/js
# or: yarn add @stackframe/js
# or: bun add @stackframe/js
```
</Step>
<Step title="Initializing the Stack App">
Next, let us create the Stack App object for your project. This is the most important object in a Stack Auth project.
In a backend where you can keep a secret key safe, you can use the `StackServerApp`, which provides access to more sensitive APIs compared to `StackClientApp`:
```ts src/stack/server.ts
import { StackServerApp } from "@stackframe/js";
export const stackServerApp = new StackServerApp({
tokenStore: null,
urls: {
default: {
type: "hosted",
}
},
});
```
</Step>
<Step title="Setting up the project">
It's now time to connect your code to a Stack Auth project.
You can either run Stack Auth's dev environment locally, or connect to a production project hosted in the cloud.
<AccordionGroup>
<Accordion title="Option 1: Running Stack Auth's dev environment (recommended)" defaultOpen>
First, create a `stack.config.ts` configuration file in the root directory of the workspace (or anywhere else):
```ts stack.config.ts
import type { StackConfig } from "@stackframe/js";
// default: show-onboarding, which shows the onboarding flow for this project when Stack Auth starts
export const config: StackConfig = "show-onboarding";
```
To run your application with Stack Auth, you can then start the dev environment and set environment variables expected by your application. Stack Auth's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json:
```sh
npm i -D @stackframe/stack-cli
# or: pnpm i -D @stackframe/stack-cli
# or: yarn add -D @stackframe/stack-cli
# or: bun add --dev @stackframe/stack-cli
```
```json package.json
{
// ...
"scripts": {
// ...
"dev": "stack dev --config-file ./stack.config.ts -- npm run dev:without-stack-auth",
"dev:without-stack-auth": "<your-existing-dev-script>"
}
}
```
</Accordion>
<Accordion title="Option 2: Connecting to a production project hosted in the cloud">
If you'd rather run your development environment on our infrastructure, or you already have an existing product, you can also connect a cloud project.
This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not).
#### Frontend
Go to your project's dashboard on [app.stack-auth.com](https://app.stack-auth.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored):
```.env .env.local
STACK_PROJECT_ID=<your-project-id>
```
Alternatively, you can also just set the project ID in the `stack/client.ts` file:
```ts src/stack/client.ts
export const stackClientApp = new StackClientApp({
// ...
projectId: "your-project-id",
});
```
#### Backend (or both frontend and backend)
First, navigate to the [Project Keys](https://app.stack-auth.com/projects/-selector-/project-keys) page in the Stack Auth dashboard and generate a new set of keys.
Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored):
```.env .env.local
STACK_PROJECT_ID=<your-project-id>
STACK_SECRET_SERVER_KEY=<your-secret-server-key>
```
They'll automatically be picked up by the `StackServerApp` constructor.
</Accordion>
</AccordionGroup>
</Step>
<Step title="Update callers with header & get user">
You are now ready to use the Stack Auth SDK. If you have any frontends calling your backend endpoints, you may want to pass along the Stack Auth tokens in a header such that you can access the same user object on your backend.
The most ergonomic way to do this is to pass the result of `stackClientApp.getAuthorizationHeader()` as the `Authorization` header into your backend endpoints when the user is signed in:
```ts
// NOTE: This is your frontend's code
const authorizationHeader = await stackClientApp.getAuthorizationHeader();
const response = await fetch("/my-backend-endpoint", {
headers: {
...(authorizationHeader ? { Authorization: authorizationHeader } : {}),
},
});
// ...
```
In most backend frameworks you can then access the user object by passing the request object as a `tokenStore` of the functions that access the user object:
```ts
// NOTE: This is your backend's code
const user = await stackServerApp.getUser({ tokenStore: request });
return new Response("Hello, " + user.displayName, { headers: { "Cache-Control": "private, no-store" } });
```
This will work as long as `request` is an object that follows the shape `{ headers: Record<string, string | null> | { get: (name: string) => string | null } }`.
> Make sure that HTTP caching is disabled with `Cache-Control: private, no-store` for authenticated backend endpoints.
If you cannot use `getAuthorizationHeader()`, for example because you are using a protocol other than HTTP, you can use `getAuthJson()` instead:
```ts
// Frontend:
await rpcCall("my-rpc-endpoint", {
data: {
auth: await stackClientApp.getAuthJson(),
},
});
// Backend:
const user = await stackServerApp.getUser({ tokenStore: data.auth });
return new RpcResponse("Hello, " + user.displayName);
```
</Step>
<Step title="Done!" />
</Steps>
</Tab>
<Tab title="Bun">
<div className="not-prose mb-1 flex justify-end">
<button
type="button"
onClick={copyGeneratedSetupPrompt}
className="inline-flex items-center justify-center rounded-md border border-[#9fb5e4] bg-[#eaf1ff] px-2 py-1 text-[11px] font-semibold leading-none text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
>
Copy prompt
</button>
</div>
## Bun SDK Setup Instructions
Follow these instructions in order to set up and get started with the Stack Auth SDK for Bun .
<Steps titleSize="h3">
<Step title="Install dependencies">
First, install the `@stackframe/js` npm package with your preferred package manager:
```sh
npm i @stackframe/js
# or: pnpm i @stackframe/js
# or: yarn add @stackframe/js
# or: bun add @stackframe/js
```
</Step>
<Step title="Initializing the Stack App">
Next, let us create the Stack App object for your project. This is the most important object in a Stack Auth project.
In a backend where you can keep a secret key safe, you can use the `StackServerApp`, which provides access to more sensitive APIs compared to `StackClientApp`:
```ts src/stack/server.ts
import { StackServerApp } from "@stackframe/js";
export const stackServerApp = new StackServerApp({
tokenStore: null,
urls: {
default: {
type: "hosted",
}
},
});
```
</Step>
<Step title="Setting up the project">
It's now time to connect your code to a Stack Auth project.
You can either run Stack Auth's dev environment locally, or connect to a production project hosted in the cloud.
<AccordionGroup>
<Accordion title="Option 1: Running Stack Auth's dev environment (recommended)" defaultOpen>
First, create a `stack.config.ts` configuration file in the root directory of the workspace (or anywhere else):
```ts stack.config.ts
import type { StackConfig } from "@stackframe/js";
// default: show-onboarding, which shows the onboarding flow for this project when Stack Auth starts
export const config: StackConfig = "show-onboarding";
```
To run your application with Stack Auth, you can then start the dev environment and set environment variables expected by your application. Stack Auth's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json:
```sh
npm i -D @stackframe/stack-cli
# or: pnpm i -D @stackframe/stack-cli
# or: yarn add -D @stackframe/stack-cli
# or: bun add --dev @stackframe/stack-cli
```
```json package.json
{
// ...
"scripts": {
// ...
"dev": "stack dev --config-file ./stack.config.ts -- npm run dev:without-stack-auth",
"dev:without-stack-auth": "<your-existing-dev-script>"
}
}
```
</Accordion>
<Accordion title="Option 2: Connecting to a production project hosted in the cloud">
If you'd rather run your development environment on our infrastructure, or you already have an existing product, you can also connect a cloud project.
This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not).
#### Frontend
Go to your project's dashboard on [app.stack-auth.com](https://app.stack-auth.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored):
```.env .env.local
STACK_PROJECT_ID=<your-project-id>
```
Alternatively, you can also just set the project ID in the `stack/client.ts` file:
```ts src/stack/client.ts
export const stackClientApp = new StackClientApp({
// ...
projectId: "your-project-id",
});
```
#### Backend (or both frontend and backend)
First, navigate to the [Project Keys](https://app.stack-auth.com/projects/-selector-/project-keys) page in the Stack Auth dashboard and generate a new set of keys.
Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored):
```.env .env.local
STACK_PROJECT_ID=<your-project-id>
STACK_SECRET_SERVER_KEY=<your-secret-server-key>
```
They'll automatically be picked up by the `StackServerApp` constructor.
</Accordion>
</AccordionGroup>
</Step>
<Step title="Update callers with header & get user">
You are now ready to use the Stack Auth SDK. If you have any frontends calling your backend endpoints, you may want to pass along the Stack Auth tokens in a header such that you can access the same user object on your backend.
The most ergonomic way to do this is to pass the result of `stackClientApp.getAuthorizationHeader()` as the `Authorization` header into your backend endpoints when the user is signed in:
```ts
// NOTE: This is your frontend's code
const authorizationHeader = await stackClientApp.getAuthorizationHeader();
const response = await fetch("/my-backend-endpoint", {
headers: {
...(authorizationHeader ? { Authorization: authorizationHeader } : {}),
},
});
// ...
```
In most backend frameworks you can then access the user object by passing the request object as a `tokenStore` of the functions that access the user object:
```ts
// NOTE: This is your backend's code
const user = await stackServerApp.getUser({ tokenStore: request });
return new Response("Hello, " + user.displayName, { headers: { "Cache-Control": "private, no-store" } });
```
This will work as long as `request` is an object that follows the shape `{ headers: Record<string, string | null> | { get: (name: string) => string | null } }`.
> Make sure that HTTP caching is disabled with `Cache-Control: private, no-store` for authenticated backend endpoints.
If you cannot use `getAuthorizationHeader()`, for example because you are using a protocol other than HTTP, you can use `getAuthJson()` instead:
```ts
// Frontend:
await rpcCall("my-rpc-endpoint", {
data: {
auth: await stackClientApp.getAuthJson(),
},
});
// Backend:
const user = await stackServerApp.getUser({ tokenStore: data.auth });
return new RpcResponse("Hello, " + user.displayName);
```
</Step>
<Step title="Done!" />
</Steps>
</Tab>
<Tab title="Convex">
<div className="not-prose mb-1 flex justify-end">
<button
type="button"
onClick={copyGeneratedSetupPrompt}
className="inline-flex items-center justify-center rounded-md border border-[#9fb5e4] bg-[#eaf1ff] px-2 py-1 text-[11px] font-semibold leading-none text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
>
Copy prompt
</button>
</div>
## Convex Setup
Follow these instructions to integrate Stack Auth with Convex.
<Steps titleSize="h3">
<Step title="Create or identify the Convex app">
If the project does not already use Convex, initialize a Convex + Next.js app:
```sh
npm create convex@latest
```
When prompted, choose **Next.js** and **No auth**. Stack Auth will provide auth.
During development, run the Convex backend and the app dev server:
```sh
npx convex dev
npm run dev
```
</Step>
<Step title="Install and configure Stack Auth">
Install Stack Auth in the app. If you have not already completed the SDK setup steps above, run the setup wizard:
```sh
npx @stackframe/stack-cli@latest init
```
Create or select a Stack Auth project in the dashboard. Copy the Stack Auth environment variables into the app's `.env.local` file.
Also add the same Stack Auth environment variables to the Convex deployment environment in the Convex dashboard.
</Step>
<Step title="Configure Convex auth providers">
Create or update `convex/auth.config.ts`:
```ts convex/auth.config.ts
import { getConvexProvidersConfig } from "@stackframe/js";
// or: import { getConvexProvidersConfig } from "@stackframe/react";
// or: import { getConvexProvidersConfig } from "@stackframe/stack";
export default {
providers: getConvexProvidersConfig({
projectId: process.env.STACK_PROJECT_ID, // or process.env.NEXT_PUBLIC_STACK_PROJECT_ID
}),
};
```
</Step>
<Step title="Connect Convex clients to Stack Auth">
Update the Convex client setup so Convex receives Stack Auth tokens.
In browser JavaScript:
```ts
convexClient.setAuth(stackClientApp.getConvexClientAuth({}));
```
In React:
```ts
convexReactClient.setAuth(stackClientApp.getConvexClientAuth({}));
```
For Convex HTTP clients on the server, pass a request-like token store:
```ts
convexHttpClient.setAuth(stackClientApp.getConvexHttpClientAuth({ tokenStore: requestObject }));
```
</Step>
<Step title="Use Stack Auth user data in Convex functions">
In Convex queries and mutations, use Stack Auth's Convex integration to read the current user.
```ts convex/myFunctions.ts
import { query } from "./_generated/server";
import { stackServerApp } from "../src/stack/server";
export const myQuery = query({
handler: async (ctx, args) => {
const user = await stackServerApp.getPartialUser({ from: "convex", ctx });
return user;
},
});
```
</Step>
<Step title="Done!" />
</Steps>
</Tab>
<Tab title="Supabase">
<div className="not-prose mb-1 flex justify-end">
<button
type="button"
onClick={copyGeneratedSetupPrompt}
className="inline-flex items-center justify-center rounded-md border border-[#9fb5e4] bg-[#eaf1ff] px-2 py-1 text-[11px] font-semibold leading-none text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
>
Copy prompt
</button>
</div>
## Supabase Setup
> This setup covers Supabase Row Level Security (RLS) with Stack Auth JWTs. It does not sync user data between Supabase and Stack Auth. Use Stack Auth webhooks if you need data sync.
<Steps titleSize="h3">
<Step title="Create Supabase RLS policies">
In the Supabase SQL editor, enable Row Level Security for your tables and write policies based on Supabase JWT claims.
For example, this sample table demonstrates public rows, authenticated rows, and user-owned rows:
```sql
CREATE TABLE data (
id bigint PRIMARY KEY,
text text NOT NULL,
user_id UUID
);
INSERT INTO data (id, text, user_id) VALUES
(1, 'Everyone can see this', NULL),
(2, 'Only authenticated users can see this', NULL),
(3, 'Only user with specific id can see this', NULL);
ALTER TABLE data ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public read" ON "public"."data" TO public
USING (id = 1);
CREATE POLICY "Authenticated access" ON "public"."data" TO authenticated
USING (id = 2);
CREATE POLICY "User access" ON "public"."data" TO authenticated
USING (id = 3 AND auth.uid() = user_id);
```
</Step>
<Step title="Install Stack Auth and Supabase dependencies">
If you are starting from scratch with Next.js, you can use Supabase's template and then initialize Stack Auth:
```sh
npx create-next-app@latest -e with-supabase stack-supabase
cd stack-supabase
npx @stackframe/stack-cli@latest init
```
Add the Supabase environment variables to `.env.local`:
```.env .env.local
NEXT_PUBLIC_SUPABASE_URL=<your-supabase-url>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<your-supabase-anon-key>
SUPABASE_JWT_SECRET=<your-supabase-jwt-secret>
```
Also add the Stack Auth environment variables:
```.env .env.local
# The project ID is the only client-exposed Stack Auth variable; in Next.js it must
# be prefixed with NEXT_PUBLIC_. STACK_SECRET_SERVER_KEY is server-only and must
# NEVER be prefixed or exposed to the client.
NEXT_PUBLIC_STACK_PROJECT_ID=<your-stack-project-id>
STACK_SECRET_SERVER_KEY=<your-secret-server-key>
```
</Step>
<Step title="Mint Supabase JWTs from Stack Auth users">
Create a server action that signs a Supabase JWT using the current Stack Auth user ID:
```tsx utils/actions.ts
'use server';
import { stackServerApp } from "@/stack/server";
import * as jose from "jose";
export const getSupabaseJwt = async () => {
const user = await stackServerApp.getUser();
if (!user) {
return null;
}
const token = await new jose.SignJWT({
sub: user.id,
role: "authenticated",
})
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("1h")
.sign(new TextEncoder().encode(process.env.SUPABASE_JWT_SECRET));
return token;
};
```
</Step>
<Step title="Create a Supabase client that uses the Stack Auth JWT">
Create a helper that passes the server-generated JWT to Supabase:
```tsx utils/supabase-client.ts
import { createBrowserClient } from "@supabase/ssr";
import { getSupabaseJwt } from "./actions";
export const createSupabaseClient = () => {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ accessToken: async () => await getSupabaseJwt() || "" },
);
};
```
</Step>
<Step title="Fetch Supabase data">
Use the Supabase client from your UI. The RLS policies will decide which rows the user can read based on the Stack Auth user ID embedded in the Supabase JWT.
```tsx app/page.tsx
'use client';
import { createSupabaseClient } from "@/utils/supabase-client";
import { useStackApp, useUser } from "@stackframe/stack";
import Link from "next/link";
import { useEffect, useState } from "react";
export default function Page() {
const app = useStackApp();
const user = useUser();
const supabase = createSupabaseClient();
const [data, setData] = useState<null | any[]>(null);
useEffect(() => {
supabase.from("data").select().then(({ data }) => setData(data ?? []));
}, []);
const listContent = data === null
? <p>Loading...</p>
: data.length === 0
? <p>No notes found</p>
: data.map((note) => <li key={note.id}>{note.text}</li>);
return (
<div>
{user ? (
<>
<p>You are signed in</p>
<p>User ID: {user.id}</p>
<Link href={app.urls.signOut}>Sign Out</Link>
</>
) : (
<Link href={app.urls.signIn}>Sign In</Link>
)}
<h3>Supabase data</h3>
<ul>{listContent}</ul>
</div>
);
}
```
</Step>
<Step title="Done!" />
</Steps>
</Tab>
<Tab title="CLI">
<div className="not-prose mb-1 flex justify-end">
<button
type="button"
onClick={copyGeneratedSetupPrompt}
className="inline-flex items-center justify-center rounded-md border border-[#9fb5e4] bg-[#eaf1ff] px-2 py-1 text-[11px] font-semibold leading-none text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
>
Copy prompt
</button>
</div>
## CLI Setup
Follow these instructions to authenticate users in a command line application with Stack Auth.
<Steps titleSize="h3">
<Step title="Add the CLI auth template">
Download the Stack Auth CLI authentication template and place it in your project. For Python apps, copy it as `stack_auth_cli_template.py`.
Example project layout:
```text
my-python-app/
├─ main.py
└─ stack_auth_cli_template.py
```
</Step>
<Step title="Prompt the user to log in">
Import and call `prompt_cli_login`. It opens the browser, lets the user authenticate, and returns a refresh token.
```py main.py
from stack_auth_cli_template import prompt_cli_login
refresh_token = prompt_cli_login(
app_url="https://your-app-url.example.com",
project_id="your-project-id-here",
publishable_client_key="your-publishable-client-key-here",
)
if refresh_token is None:
print("User cancelled the login process. Exiting")
exit(1)
```
You can store the refresh token in a local file or keychain and only prompt the user again when no saved refresh token exists.
</Step>
<Step title="Exchange the refresh token for an access token">
Use the refresh token with Stack Auth's REST API to get an access token.
```py
def get_access_token(refresh_token):
access_token_response = stack_auth_request(
"post",
"/api/v1/auth/sessions/current/refresh",
headers={
"x-stack-refresh-token": refresh_token,
},
)
return access_token_response["access_token"]
```
</Step>
<Step title="Fetch the current user">
Use the access token to call the Stack Auth REST API as the logged-in user.
```py
def get_user_object(access_token):
return stack_auth_request(
"get",
"/api/v1/users/me",
headers={
"x-stack-access-token": access_token,
},
)
user = get_user_object(get_access_token(refresh_token))
print("The user is logged in as", user["display_name"] or user["primary_email"])
```
</Step>
<Step title="Done!" />
</Steps>
</Tab>
</Tabs>
</div>
</div>
</div>