> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Account merging

> Complete an account merge when a user adds a credential that belongs to a mergeable second account — including resolving field-level conflicts.

## Prerequisites

Before this page: understand [account conflict resolution](/javascript/building-ui/account-conflict-resolution). Merging is the recovery path for one specific conflict — a `MergeAccountsConfirmationError`.

<Note>
  This page covers **only** the merging case (`MergeAccountsConfirmationError`). Linking a credential can fail in other ways — the credential already belongs to an account that **can't** be merged, or it's already in use elsewhere. For the full set of conflicts and how to handle each, see [account conflict resolution](/javascript/building-ui/account-conflict-resolution); use this page for the merge path only.
</Note>

## What you'll build

When a signed-in user adds a credential that already belongs to **another mergeable account**, verification throws a `MergeAccountsConfirmationError`. Merging folds that second account into the current one so the user ends up with a single account holding both credentials.

Two cases:

1. **No conflicts** — the accounts don't disagree on any profile field. You confirm and merge in one call.
2. **Field-level conflicts** — both accounts have a value for the same field (say, a display name). The user picks which value to keep for each field, then you merge.

You'll build the confirmation UI, the per-field chooser, and the call that completes the merge.

<Warning>
  Merging is destructive: the second account is absorbed into the current one. Always confirm with the user before calling `mergeUserAccounts` — don't merge automatically on catching the error.
</Warning>

## The merge flow

```
add credential → MergeAccountsConfirmationError → confirm → resolve conflicts (if any) → mergeUserAccounts → merged
```

The error carries `mergeConflicts`. When it's `undefined` there's nothing to resolve — merge directly. When present, it lists each conflicting `field` with the value held by `fromUser` (the account being merged in) and `currentUser` (the signed-in account).

## Completing the merge

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"system"}
    import {
      MergeAccountsConfirmationError,
      mergeUserAccounts,
    } from '@dynamic-labs-sdk/client';
    import type {
      MergeConflicts,
      MergeUserConflictResolution,
    } from '@dynamic-labs-sdk/client';

    // `linkCredential` is your verification call (e.g. verifyOTP or wallet verify).
    async function addCredentialWithMerge(linkCredential: () => Promise<void>) {
      try {
        await linkCredential();
      } catch (error) {
        if (!(error instanceof MergeAccountsConfirmationError)) throw error;

        // confirmMerge is your implementation — show a dialog explaining the two
        // accounts will be combined, and resolve to true only if the user agrees.
        const confirmed = await confirmMerge();
        if (!confirmed) return;

        if (!error.mergeConflicts) {
          // No field-level conflicts — merge directly.
          await mergeUserAccounts();
          return;
        }

        // Ask the user which value to keep for each conflicting field.
        const conflictResolutions = await resolveConflicts(error.mergeConflicts);
        await mergeUserAccounts({ conflictResolutions });
      }
    }

    // Turn the surfaced conflicts into one resolution per field. `chooseValue` is
    // your UI (a radio group, etc.) returning the userId whose value to keep.
    async function resolveConflicts(
      mergeConflicts: MergeConflicts,
    ): Promise<MergeUserConflictResolution[]> {
      const resolutions: MergeUserConflictResolution[] = [];

      for (const conflict of mergeConflicts.conflicts) {
        const chosenUserId = await chooseValue({
          label: conflict.field.label ?? conflict.field.name,
          keepCurrent: conflict.currentUser.value,
          keepIncoming: conflict.fromUser.value,
          currentUserId: conflict.currentUser.userId,
          incomingUserId: conflict.fromUser.userId,
        });

        resolutions.push({
          fieldKey: conflict.field.name,
          userId: chosenUserId,
          type: conflict.field.type,
        });
      }

      return resolutions;
    }
    ```
  </Tab>

  <Tab title="React">
    When you catch a `MergeAccountsConfirmationError`, read `error.mergeConflicts`: if it's present, render the `ConflictResolver` below with it; if it's `undefined`, confirm and call `merge()` with no arguments. The component renders one control per conflict and merges once every field has a decision:

    ```tsx theme={"system"}
    import type {
      MergeConflicts,
      MergeUserConflictResolution,
    } from '@dynamic-labs-sdk/client';
    import { useMergeUserAccounts } from '@dynamic-labs-sdk/react-hooks';
    import { useState } from 'react';

    function ConflictResolver({ mergeConflicts }: { mergeConflicts: MergeConflicts }) {
      // Track the chosen userId per field key.
      const [choices, setChoices] = useState<Record<string, string>>({});
      const { mutateAsync: merge, isPending } = useMergeUserAccounts();

      const allChosen = mergeConflicts.conflicts.every(
        (conflict) => choices[conflict.field.name],
      );

      const handleMerge = async () => {
        const conflictResolutions: MergeUserConflictResolution[] =
          mergeConflicts.conflicts.map((conflict) => ({
            fieldKey: conflict.field.name,
            userId: choices[conflict.field.name],
            type: conflict.field.type,
          }));
        await merge({ conflictResolutions });
      };

      return (
        <form onSubmit={(event) => { event.preventDefault(); handleMerge(); }}>
          <h2>Combine your accounts</h2>
          {mergeConflicts.conflicts.map((conflict) => {
            const field = conflict.field.name;
            return (
              <fieldset key={field}>
                <legend>{conflict.field.label ?? field}</legend>
                <label>
                  <input
                    type="radio"
                    name={field}
                    checked={choices[field] === conflict.currentUser.userId}
                    onChange={() =>
                      setChoices((prev) => ({
                        ...prev,
                        [field]: conflict.currentUser.userId,
                      }))
                    }
                  />
                  Keep “{conflict.currentUser.value}”
                </label>
                <label>
                  <input
                    type="radio"
                    name={field}
                    checked={choices[field] === conflict.fromUser.userId}
                    onChange={() =>
                      setChoices((prev) => ({
                        ...prev,
                        [field]: conflict.fromUser.userId,
                      }))
                    }
                  />
                  Use “{conflict.fromUser.value}”
                </label>
              </fieldset>
            );
          })}
          <button type="submit" disabled={!allChosen || isPending}>
            {isPending ? 'Merging…' : 'Combine accounts'}
          </button>
        </form>
      );
    }
    ```

    For the no-conflict case, call `merge()` (with no arguments) straight from the confirmation dialog.
  </Tab>
</Tabs>

## Handling errors

| Situation                                                                     | What to do                                                                                                                                              |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MergeAccountsConfirmationError` with `mergeConflicts === undefined`          | Confirm, then `mergeUserAccounts()` with no arguments.                                                                                                  |
| `MergeAccountsConfirmationError` with conflicts                               | Collect one `MergeUserConflictResolution` per conflict, then `mergeUserAccounts({ conflictResolutions })`.                                              |
| `mergeUserAccounts` rejects                                                   | Keep the user on their current account (the merge didn't happen) and let them retry or cancel.                                                          |
| Missing a resolution for a conflicting field                                  | Don't call `mergeUserAccounts` until every conflict has a choice — the backend needs one resolution per field.                                          |
| Any other error linking the credential (not `MergeAccountsConfirmationError`) | This page doesn't cover it — see [account conflict resolution](/javascript/building-ui/account-conflict-resolution) for the full set of link conflicts. |

<Tip>
  On success `mergeUserAccounts` resolves to the merged user and the session updates automatically. Re-render from your auth state (the `useUser` hook, in the [React hooks catalog](/javascript/reference/react-hooks-catalog)) rather than the returned value if the rest of your UI already reads from there.
</Tip>

## See also

* [Account conflict resolution](/javascript/building-ui/account-conflict-resolution) — detecting the conflict that leads here
* [Post-login user setup](/javascript/building-ui/post-auth-user-setup) — where added credentials often originate
