Skip to content

<UserProfile />

Profile dialog with avatar, name, email, linked accounts, API keys and a Delete account button

<UserProfile /> is where a signed-in user manages their own account: name and avatar, email address, linked sign-in methods and password, API keys, and deleting the account. Most apps show it as a dialog through <UserProfileHost />, which <UserButton /> opens.

Quick start

The base registry item mounts <UserProfileHost /> once in the root layout. This is the relevant part of that layout; the rest of it, such as createSvelteAuthClient andthe header, stays as installed:

src/routes/+layout.svelte (excerpt)
src/app/layout.tsx (excerpt)

In SvelteKit, every Auth UI component needs an <AuthProvider> ancestor: it passes api, authClient and authConstants to the components below it.

In Next.js, the components need the ConvexClientProvider.

To show the settings on a page of their own instead of in a dialog, render <UserProfile /> directly:

src/routes/account/+page.svelte
src/app/account/page.tsx

What it includes

The sections render top to bottom in this order.

Name and avatar

The user selects their name to edit it inline. Selecting the avatar lets them pick an image and crop it; the cropped image is uploaded to Convex storage through api.storage.generateUploadUrl and saved with api.users.mutations.updateAvatar. Without an image, the avatar is generated from the user’s name.

Email

Shows the current address with a Verified or Not verified badge. Changing the email calls Better Auth’s changeEmail, which always sends an email: for a verified address, a confirmation to the current address; for an unverified one, a verification link to the new address. Set up sending emails before users rely on it.

Linked accounts

Lists every sign-in method on the account. Each one can be unlinked while more than one is linked; the last remaining method can’t be.

Link new account offers every provider set to true in providers that isn’t linked yet. A social provider starts that provider’s sign-in and links the result to the current user, even when the provider’s email differs (accountLinking.allowDifferentEmails is on in src/convex/auth.ts).

When password is enabled and the user has no password, for example because they signed up with GitHub, Password appears in that list. It opens a dialog that sets one through api.users.mutations.setPassword. Once a password exists, its row has an Update button that opens a form for the current and the new password and calls Better Auth’s changePassword.

API keys

Shown only with apiKeys: true in AUTH_CONSTANTS. Users create, rename, set the expiry of and delete their keys here. Setup and usage are on API keys.

Delete account

Delete account asks for confirmation, then deletes the user through api.users.mutations.deleteUser, signs them out and closes the dialog. Deleting also removes:

  • the uploaded avatar,
  • the user’s API keys, even when apiKeys is off,
  • with organizations: true, their memberships, invitations sent to their email, and every organization where they’re the only member.

Two cases block the deletion and show an error instead:

  • The user owns an organization that has other members. They transfer ownership or remove the members first.
  • The session is older than 24 hours. Better Auth only deletes an account without a password from a fresh session (its default session.freshAge), so the user signs out and in again.

Props

initialData takes the user and linked accounts loaded on the server, so the sections render on first paint. The types come from #lib/auth/types.js; the base/ssr item’s src/routes/+layout.server.ts returns this data as data.initialData.

Behavior

<UserProfileHost /> opens the dialog while the URL has ?dialog=user-profile and the user is signed in. Closing the dialog removes the parameter, so the browser’s back button closes it too, and a link such as /settings?dialog=user-profile opens it directly. To close it from your own code, call requestCloseUserProfile() from #lib/users/utils/userProfile.jsdispatch a user-profile:close event on window.

The dialog fills the screen on mobile and is centered on larger screens.