---
title: "Let users edit their profile"
description: "Let a signed-in person change their display name and avatar, and move their account to a new email address with a code from each address."
canonical_url: "https://nuxt-backend.dev/docs/auth/accounts/profile"
---
# Let users edit their profile

> Let a signed-in person change their display name and avatar, and move their account to a new email address with a code from each address.

The module mounts `/profile` (module option `pages.profile`) behind the `auth` middleware. It renders `<ProfileSettings>`: the person's identity (initials, verification, role and workspace), their display name, and a change of email.

```vue
<template>
  <ProfileSettings @name-saved="name => toast(`Saved as ${name}`)" />
</template>
```

## The display name and avatar

`useAuth().updateUser({ name?, image? })` changes them from the browser. From the server, and for agents, `updateProfile` (re-exported from `backend/auth.ts`) takes `{ name }` and nothing else: an email change never goes through it.

## A new email address

The person proves both mailboxes with a code: one from the current address, then one from the new address. No link is sent, so nobody who merely opens an email can move the account. `<ProfileSettings>` runs it for you, and `validateEmail` gates the new address before any code goes out. For a form of your own, `useEmailChange()` is the same flow as a step machine:

```ts
const change = useEmailChange({ onChanged: email => toast(`Now ${email}`) })

change.newEmail.value = 'ada@new.example'
await change.start()           // emails a code to the current address → 'confirm-current'
change.otp.value = '482913'
await change.confirmCurrent()  // checks it, emails a code to the new address → 'verify-new'
change.otp.value = '193557'
await change.verifyNew()       // checks it, the account moves → 'done'
```

Like `useLoginFlow()`, it never rejects: a step that fails stays where it was, with the reason in `error` and the wait of a rate limit in `retryAfter`. With the scaffold's `rateLimiter` wired, each account can have five codes sent to new addresses an hour (the `emailChange` limit), and every code check counts against the address the code went to (`emailOtpAttempt`, ten an hour). A new address that already belongs to an account receives no code and the flow reports nothing different, so it can't be used to find out who has an account.

Before a code goes to the new address, the server asks your [`canSignIn`](https://nuxt-backend.dev/docs/auth/setup#cross-component-wiring) gate about it (`purpose: 'email-change'`), and again before the account moves; `confirmCurrent()` reports a refusal's message in `error`. A new address the deployment could not deliver to is refused at the same step, before any code exists. An administrator acting as the account cannot change its address.

## Its props, events and slots

`<ProfileSettings>` takes `validateEmail` and `showIdentity` (`true`), emits `name-saved(name)`, `email-changed(email)` and `error(message)`, and has the slots `identity`, `name`, `email` and `footer`, each receiving the name state with `saveName` and the `emailChange` flow. Its copy comes from `appConfig.backend.labels.profile`: see [Change copy and labels](https://nuxt-backend.dev/docs/ui/customize/labels).


## Sitemap

See the full [sitemap](https://nuxt-backend.dev/sitemap.md) for all pages.
