# Build a User Management App with Next.js

Learn how to use Supabase in your Next.js App.

Note: UI components built on shadcn/ui that connect to Supabase via a single command.

This tutorial demonstrates how to build a basic user management app. The app authenticates and identifies the user, stores their profile information in the database, and allows the user to log in, update their profile details, and upload a profile photo. The app uses:

- [Supabase Database](/docs/guides/database/overview) - a Postgres database for storing your user data and [Row Level Security](/docs/guides/auth#row-level-security) so data is protected and users can only access their own information.
- [Supabase Auth](/docs/guides/auth) - allow users to sign up and log in.
- [Supabase Storage](/docs/guides/storage) - allow users to upload a profile photo.

![Supabase User Management example](/docs/img/user-management-demo.png)

Note: If you get stuck while working through this guide, you can find the [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-user-management).

## Project setup

Before you start building you need to set up the Database and API. You can do this by starting a new Project in Supabase and then creating a "schema" inside the database.

### Create a project

1. [Create a new project](/dashboard) in the Supabase Dashboard.
2. Enter your project details.
3. Wait for the new database to launch.

### Set up the database schema

Now set up the database schema. You can use the "User Management Starter" quickstart in the SQL Editor, or you can copy/paste the SQL from below and run it.

**Dashboard**

1. Go to the [SQL Editor](/dashboard/project/_/sql) page in the Dashboard.
2. Click **User Management Starter** under the **Reference > Examples** tab.
3. Click **Run**.

Note: You can pull the database schema down to your local project by running the `db pull` command. Read the [local development docs](/docs/guides/local-development/database-migrations#link-your-project) for detailed instructions.

```bash
supabase link --project-ref <project-id>
# You can get <project-id> from your project's dashboard URL: /dashboard/project/<project-id>
supabase db pull
```

**SQL**

Note: When working locally you can run the following command to create a new migration file:

```bash
supabase migration new user_management_starter
```

```sql
-- Create a table for public profiles
create table profiles (
  id uuid references auth.users not null primary key,
  updated_at timestamp with time zone,
  username text unique,
  full_name text,
  avatar_url text,
  website text,

  constraint username_length check (char_length(username) >= 3)
);

-- Grant the privileges roles need
GRANT SELECT ON public.profiles TO anon;
GRANT SELECT, INSERT, UPDATE ON public.profiles TO authenticated;

-- Set up Row Level Security (RLS)
-- See /docs/guides/database/postgres/row-level-security for more details.
alter table profiles
  enable row level security;

create policy "Public profiles are viewable by everyone." on profiles
  for select using (true);

create policy "Users can insert their own profile." on profiles
  for insert with check ((select auth.uid()) = id);

create policy "Users can update own profile." on profiles
  for update using ((select auth.uid()) = id);

-- This trigger automatically creates a profile entry when a new user signs up via Supabase Auth.
-- See /docs/guides/auth/managing-user-data#using-triggers for more details.
create function public.handle_new_user()
returns trigger
set search_path = ''
as $$
begin
  insert into public.profiles (id, full_name, avatar_url)
  values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url');
  return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();

-- Set up Storage!
insert into storage.buckets (id, name)
  values ('avatars', 'avatars');

-- Set up access controls for storage. Allows downloading object with public key
-- See /docs/guides/storage/security/access-control#policy-examples for more details.
create policy "Avatar images are publicly accessible." on storage.objects
  for select using (bucket_id = 'avatars' and storage.allow_any_operation(array['object.get_authenticated_info', 'object.get_authenticated']));

create policy "Anyone can upload an avatar." on storage.objects
  for insert with check (bucket_id = 'avatars');

create policy "Anyone can update their own avatar." on storage.objects
  for update using ((select auth.uid()) = owner) with check (bucket_id = 'avatars');
```

### Get API details

To interact with data in database tables, you use the client libraries that wrap [the auto-generated Data API endpoints](/docs/guides/api), authenticating using the Project URL and key from [the project **Connect** dialog](/dashboard/project/_?showConnect=true\&connectTab=frameworks\&framework=nextjs).





Note: See [API keys](/docs/guides/getting-started/api-keys) for a full explanation of all key types, their uses, and where to find them.

## Building the app

Start building the Next.js app from scratch.

### Initialize a Next.js app

Use [`create-next-app`](https://nextjs.org/docs/getting-started) to initialize an app called `supabase-nextjs`:

```bash
npx create-next-app@latest --ts --use-npm supabase-nextjs
cd supabase-nextjs
```

Install [supabase-js](https://github.com/supabase/supabase-js):

```bash
npm install @supabase/supabase-js
```

Save the environment variables in a `.env.local` file at the root of the project, and paste the API URL and the key that you copied [earlier](#get-api-details).

The application exposes these variables in the browser, and that's fine as Supabase enables [Row Level Security](/docs/guides/database/postgres/row-level-security) by default on all tables.

```bash .env.local
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=YOUR_SUPABASE_PUBLISHABLE_KEY
```

### App styling (optional)

An optional step is to update the CSS file `app/globals.css` to make the app look better.
You can find the full contents of this file [in the example repository](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/nextjs-user-management/app/globals.css).

### Supabase Server-Side Auth package

Next.js is a versatile framework offering pre-rendering at build time (SSG), server-side rendering at request time (SSR), API routes, and proxy edge-functions.

To better integrate with the framework, we've created the `@supabase/ssr` package for Server-Side Auth. It has all the functionalities to configure your Supabase project to use cookies for storing user sessions. Read the [Next.js Server-Side Auth guide](/docs/guides/auth/server-side/creating-a-client?queryGroups=package-manager\&package-manager=npm\&queryGroups=framework\&framework=nextjs) for more information.

Install the package for Next.js.

```bash
npm install @supabase/ssr
```

### Supabase utilities

There are two different types of clients in Supabase:

1. **Client Component client** - To access Supabase from Client Components, which run in the browser.
2. **Server Component client** - To access Supabase from Server Components, Server Actions, and Route Handlers, which run only on the server.

We recommend creating the following utilities files for creating clients, and organize them within `lib/supabase` at the root of the project.

Create a `client.ts` and a `server.ts` with the following code for client-side Supabase and server-side Supabase, respectively.



### Next.js proxy

Since Server Components can't write cookies, you need [Proxy](https://nextjs.org/docs/app/getting-started/proxy) to refresh expired Auth tokens and store them.

You accomplish this by:

- Refreshing the Auth token with the call to `supabase.auth.getClaims`.
- Passing the refreshed Auth token to Server Components through `request.cookies.set`, so they don't attempt to refresh the same token themselves.
- Passing the refreshed Auth token to the browser, so it replaces the old token. This is done with `response.cookies.set`.

You could also add a matcher, so that the Proxy only runs on routes that access Supabase. For more information, read [the Next.js matcher documentation](https://nextjs.org/docs/app/api-reference/file-conventions/proxy#matcher).

Danger: Be careful when protecting pages. The server gets the user session from the cookies, which anyone can spoof.

The Supabase Auth SDK contains three different functions for authenticating user access to applications:

### Summary of the methods

- Use [`getClaims`](/docs/reference/javascript/auth-getclaims) to protect pages and user data. It reads the access token from storage and verifies it. Locally via the [WebCrypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) and a cached JWKS endpoint when the project uses asymmetric signing keys (the default for new projects), or by calling `getUser` solely to validate when symmetric keys are in use. The returned claims always come from decoding the JWT, not from a user lookup. When the access token is close to expiring, `getClaims` refreshes the session before it verifies, which is how a server-rendered session stays alive.
- [`getUser`](/docs/reference/javascript/auth-getuser) makes a network call to the project's Auth instance to get the user record, which includes the most up-to-date information about the user at the cost of a network call.
- [`getSession`](/docs/reference/javascript/auth-getsession) when you need the raw session (the access token, refresh token, and expiry). For example to forward the access token to another service. The session is loaded directly from local storage and isn't re-validated against the Auth server, so the embedded user object shouldn't be trusted on its own when storage is shared with the client (cookies, request headers). To verify identity, validate the access token with `getClaims`, or call `getUser` for a fresh, server-confirmed user record.

**In summary**: use `getClaims` to verify identity (typically for protecting pages and data), `getUser` when you need an up-to-date user record from the Auth server, and `getSession` when you need the access or refresh token directly, but don't rely on the user object it returns for authorization decisions.

Create a `proxy.ts` file at the project root and another one within the `lib/supabase` folder. The `lib/supabase` file contains the logic for updating the session. The `proxy.ts` file uses this, which is a Next.js convention.



### Set up a sign-in page

#### Sign-in and sign-up form

To add sign-in/sign-up page for your application, create a new folder named `login`, containing a `page.tsx` file with the following code for a sign-in/sign-up form:



Create the sign-in/sign-up actions to hook up the form to the function which does the following:

- Retrieve the user's information.
- Send that information to Supabase as a sign-up request, which in turns sends a confirmation email. It uses [Magic Links](/docs/guides/auth/auth-email-passwordless#with-magic-link), so users can sign in with their email without using passwords.
- Handle any error that arises.

Create the `actions.ts` file in the `app/login` folder, which contains the sign-in and sign-up functions and the `error/page.tsx` file, which displays an error message if the sign-in or sign-up fails.



Caution: The `cookies` method is called before any calls to Supabase, which takes fetch calls out of Next.js's caching. This is important for authenticated data fetches, to ensure that users get access only to their own data.

Read the Next.js docs to learn more about [opting out of data caching](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#opting-out-of-data-caching).

#### Email template

Before proceeding, change the email template to support a server-side authentication flow that sends a token hash:

- Go to the [Auth templates](/dashboard/project/_/auth/templates) page in your dashboard.
- Select the **Confirm signup** template.
- Change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email`.

Note: You can customize other emails sent out to new users, including the email's looks, content, and query parameters from [the **Authentication > Email**](/dashboard/project/_/auth/templates) section of the Dashboard.

#### Confirmation endpoint

As you are working in a server-side rendering (SSR) environment, you need to create a server endpoint responsible for exchanging the `token_hash` for a session.

The code performs the following steps:

- Retrieves the code sent back from the Supabase Auth server using the `token_hash` query parameter.
- Exchanges this code for a session, which you store in your chosen storage mechanism (in this case, cookies).
- Finally, redirects the user to the `account` page.



### Account page

After a user signs in, they need a way to edit their profile details and manage their accounts.

Create a new component for that called `AccountForm` within the `app/account` folder.



Create an account page for the `AccountForm` component you created



### Sign out

Create a route handler to handle the sign-out from the server side, making sure to check if the user is signed in first.



## Profile photos

Next, add a way for users to upload a profile photo. Supabase configures every project with [Storage](/docs/guides/storage) for managing large files like photos and videos.

### Create an upload widget

Start by creating a new component:



### Update the account form

With the Avatar component created, update `app/account/account-form.tsx` to include it:



### Launch

With all the pages, route handlers, and components in place, run the following in a terminal window:

```bash
npm run dev
```

And then open the browser to [localhost:3000/login](http://localhost:3000/login) and you should see the completed app.

When you enter your email and password, you will receive an email with the title **Confirm your email**. Congrats 🎉!!!

At this stage you have a fully functional application!

## See also

- See the complete [example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-user-management) and deploy it to Vercel
- [Build a Twitter Clone with the Next.js App Router and Supabase - free egghead course](https://egghead.io/courses/build-a-twitter-clone-with-the-next-js-app-router-and-supabase-19bebadb)
- Explore the [pre-built Auth components](/library/docs/nextjs/password-based-auth)
- Explore the [Supabase Cache Helpers](https://github.com/psteinroe/supabase-cache-helpers)
- See the [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments) template on GitHub
