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

# Migrating from Para v2.x to v3.0

> Guide for migrating theme configuration and portal URL consumers from Para v2.x to v3.0

Use this guide for assistance migrating from Para version 2.x to Para's version 3.0 release.

## Summary of Breaking Changes

The v3.0 release simplifies the modal theming system and updates the portal URL helper return shape for advanced custom UI consumers:

1. **`foregroundColor` now drives the entire UI palette** — buttons, accents, rings, and primary colors are all generated from it via OKLCH color space. Previously, `foregroundColor` was the text/icon color; that role is now auto-generated from the background.

2. **`accentColor` is deprecated** — it still works for backward compatibility (and takes precedence over `foregroundColor` when both are provided), but you should migrate to using `foregroundColor` instead.

3. **Dark mode variant colors removed** — `darkForegroundColor`, `darkBackgroundColor`, and `darkAccentColor` no longer exist. Just provide your dark colors as `backgroundColor` and `foregroundColor` directly — the system auto-detects dark/light mode from the background color lightness.

4. **`customPalette`, `customFontSizes`, `customBorderRadii` removed** — replaced by `cssOverrides` for advanced cases.

5. **`overlayBackground` removed** from the theme type.

6. **`oAuthLogoVariant` removed** from the theme type.

7. **New properties added** — `foregroundMixRatio` and `cssOverrides`.

8. **Portal URL helpers now return `{ url, fullUrl }`** - default modal users do not need to change anything, but custom UI consumers that read portal URL fields directly should review the [Portal URL Return Shape](#portal-url-return-shape) section.

<Note>
  Beyond theming, v3 also moves most configuration (auth methods, external wallets, branding, and more) to the **partner record** in the Developer Portal. That change is **non-breaking**; your existing `paraModalConfig` props keep working as a deprecated fallback, and `configOverrides` is available when a setting must still be controlled in code. See [Configuration moves to the partner record](#configuration-moves-to-the-partner-record) below.
</Note>

## Property Mapping

| v2 Property           | v3 Equivalent                                                   |
| --------------------- | --------------------------------------------------------------- |
| `foregroundColor`     | `foregroundColor` (new semantics: drives accent palette)        |
| `backgroundColor`     | `backgroundColor` (unchanged)                                   |
| `accentColor`         | `foregroundColor` (`accentColor` still accepted but deprecated) |
| `darkForegroundColor` | Removed — provide dark colors via `foregroundColor` directly    |
| `darkBackgroundColor` | Removed — provide dark colors via `backgroundColor` directly    |
| `darkAccentColor`     | Removed — provide dark colors via `foregroundColor` directly    |
| `overlayBackground`   | Removed                                                         |
| `oAuthLogoVariant`    | Removed                                                         |
| `customPalette`       | `cssOverrides`                                                  |
| `customFontSizes`     | `cssOverrides`                                                  |
| `customBorderRadii`   | `cssOverrides`                                                  |
| `mode`                | `mode` (unchanged)                                              |
| `borderRadius`        | `borderRadius` (unchanged)                                      |
| `font`                | `font` (unchanged)                                              |
| —                     | `foregroundMixRatio` (new, default 0.04)                        |
| —                     | `cssOverrides` (new)                                            |

## Before & After

<CodeGroup>
  ```tsx v2.x Theme theme={null}
  paraModalConfig={{
    theme: {
      foregroundColor: "#333333",
      backgroundColor: "#FFFFFF",
      accentColor: "#007AFF",

      darkForegroundColor: "#FFFFFF",
      darkBackgroundColor: "#1C1C1E",
      darkAccentColor: "#0A84FF",

      mode: "light",
      borderRadius: "md",
      font: "Inter, sans-serif",
      overlayBackground: "rgba(0, 0, 0, 0.5)",
      oAuthLogoVariant: "default",

      customPalette: {
        text: {
          primary: "#333333",
          secondary: "#666666"
        },
        primaryButton: {
          surface: {
            default: "#007AFF",
            hover: "#0056CC"
          }
        }
      }
    }
  }}
  ```

  ```tsx v3.0 Theme theme={null}
  paraModalConfig={{
    theme: {
      foregroundColor: "#007AFF",
      backgroundColor: "#FFFFFF",
      mode: "light",
      borderRadius: "md",
      font: "Inter, sans-serif"
    }
  }}
  ```
</CodeGroup>

<Info>
  In most cases, the v3.0 theme is dramatically simpler. The OKLCH color generation system automatically produces a harmonious palette from your `foregroundColor` and `backgroundColor`. Only use `cssOverrides` if you need to fine-tune specific generated colors.
</Info>

## Detailed Changes

<AccordionGroup>
  <Accordion title="Color Properties Simplified">
    **What changed:** `foregroundColor` previously set the text and icon color. In v3, it sets the primary interactive color (buttons, accents, rings, links) and the text color is auto-generated from the background lightness.

    **`accentColor` deprecated:** If you were using `accentColor` to set the interactive color, rename it to `foregroundColor`. If you pass both, `accentColor` takes precedence for backward compatibility.

    **Dark mode variants removed:** Instead of maintaining separate `darkForegroundColor`, `darkBackgroundColor`, and `darkAccentColor`, provide your dark colors directly as `backgroundColor` and `foregroundColor`. The system auto-detects dark mode from the background color lightness:

    ```tsx theme={null}
    // v2.x
    theme: {
      foregroundColor: "#333333",
      backgroundColor: "#FFFFFF",
      accentColor: "#007AFF",
      darkForegroundColor: "#FFFFFF",
      darkBackgroundColor: "#1C1C1E",
      darkAccentColor: "#0A84FF",
    }

    // v3.0 — dark mode is auto-detected from the background color
    theme: {
      foregroundColor: "#0A84FF",
      backgroundColor: "#1C1C1E",
    }
    ```

    If the auto-detection misclassifies your background color (e.g., a color near the light/dark boundary), use `mode` to override it:

    ```tsx theme={null}
    theme: {
      foregroundColor: "#0A84FF",
      backgroundColor: "#4A4A4A", // ambiguous lightness
      mode: "dark" // override: treat as dark
    }
    ```
  </Accordion>

  <Accordion title="Custom Palette → CSS Overrides">
    **What changed:** The deeply nested `customPalette`, `customFontSizes`, and `customBorderRadii` objects have been replaced by a flat `cssOverrides` map.

    **Migration:** Identify the specific colors you customized and map them to CSS custom properties:

    ```tsx theme={null}
    // v2.x
    theme: {
      customPalette: {
        primaryButton: {
          surface: {
            default: "#007AFF",
            hover: "#0056CC"
          }
        },
        text: {
          secondary: "#666666"
        }
      }
    }

    // v3.0
    theme: {
      foregroundColor: "#007AFF",
      backgroundColor: "#FFFFFF",
      cssOverrides: {
        "--para-color-primary": "#007AFF",
        "--para-color-muted-foreground": "#666666"
      }
    }
    ```

    <Tip>
      Most `customPalette` usage is no longer needed. The OKLCH color generation from `foregroundColor` and `backgroundColor` handles palette creation automatically. Only use `cssOverrides` for specific overrides.
    </Tip>

    See the [Style the Modal](/v3/react/guides/customization/modal-theming#available-css-variables) guide for the full list of available CSS variables.
  </Accordion>

  <Accordion title="Removed Properties">
    **`overlayBackground`** — The modal overlay backdrop styling is no longer configurable through the theme. Use the `className` prop on `paraModalConfig` for custom overlay styling if needed.

    **`oAuthLogoVariant`** — The OAuth provider logo variant (`'dark' | 'light' | 'default'`) has been removed. Logos are automatically styled based on the theme mode.
  </Accordion>

  <Accordion title="New Properties">
    **`foregroundMixRatio`** (default: `0.04`): Controls how much the `foregroundColor` mixes into UI surfaces like buttons, inputs, and borders. Useful for fine-tuning the visual weight of your accent color across the interface.

    ```tsx theme={null}
    theme: {
      foregroundColor: "#007AFF",
      backgroundColor: "#FFFFFF",
      foregroundMixRatio: 0.12  // stronger accent presence
    }
    ```

    **`cssOverrides`** — A `Record<string, string>` for setting raw CSS custom properties after theme generation. This is an advanced escape hatch for overriding specific generated colors without affecting the rest of the palette.

    ```tsx theme={null}
    theme: {
      foregroundColor: "#007AFF",
      backgroundColor: "#FFFFFF",
      cssOverrides: {
        "--para-color-destructive": "#E74C3C",
        "--para-radius": "0.75rem"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## Backward Compatibility

<Note>
  Existing v2.x theme configurations will largely continue to work:

  * `accentColor` is still accepted and maps to `foregroundColor`
  * `backgroundColor`, `mode`, `borderRadius`, and `font` work identically
  * Unknown properties (like `darkForegroundColor` or `customPalette`) are silently ignored

  The main action required is updating `accentColor` to `foregroundColor` to avoid deprecation warnings, and replacing `customPalette` with `cssOverrides` if you used advanced palette customization.
</Note>

## Configuration moves to the partner record

In v3, your **partner record** (managed in the [Developer Portal](https://developer.getpara.com)) is the source of truth for most configuration, including authentication methods, theme, external wallets, links, and more. The SDK reads that record at runtime, then applies any explicit `configOverrides`. The matching `paraModalConfig` props still work but are now **deprecated**: they're the lowest-priority fallback and will be removed in the next major release. Each logs a one-time deprecation warning.

<Info>
  This change is **non-breaking**. If you upgrade without touching the Developer Portal, your existing props keep working exactly as before — migrate at your own pace. For the full model (layering, `configOverrides`, enforcement), see [How Configuration Works](/v3/react/guides/customization/configuration).
</Info>

### What to do instead

Move each setting to the partner record in the Developer Portal. For overrides that must live in code (e.g. one API key powering multiple brand contexts), use the `configOverrides` prop on `ParaProvider` instead of `paraModalConfig`.

### Migrate with the Para CLI

Use the Para CLI migration when you want an automated first pass before reviewing and committing the v3 changes. In this walkthrough, you will install the latest CLI, authenticate, run a dry run, review the planned local and API-key changes, apply the migration, and verify the result. Run it from a clean git branch so you can review or discard the generated diff safely.

<Info>
  The migration command is intended for apps that use `ParaProvider` with static `paraModalConfig` or `externalWalletConfig` props. If your app uses a custom provider abstraction, custom UI flow, or stores Para configuration outside `ParaProvider`, move those settings manually in the Developer Portal, then use `configOverrides` only for values that must stay in code.
</Info>

First, install or update the CLI:

```bash theme={null}
npm install -g @getpara/cli@latest
para --version
```

Authenticate with the Developer Portal account that owns the project you are migrating:

```bash theme={null}
para login
para whoami
```

If the CLI is not already pointed at the right organization and project, select them before applying changes:

```bash theme={null}
para orgs list
para projects list
para keys list
para config set defaultOrganizationId <organization-id>
para config set defaultProjectId <project-id>
```

From the app directory, start with a dry run. The JSON output shows package changes, files that would be edited, and each config value the command inspected.

```bash theme={null}
para migrate v3 . --dry-run --json
```

Review the plan before applying it. Static `paraModalConfig` and `externalWalletConfig` values can move to API-key configuration, including auth methods, disabled login methods, guest mode, 2FA, modal layout, theme, logo, external wallet lists, and external wallet behavior. Runtime values, imported values, connector instances, environment-derived values, and values without a deterministic portal equivalent stay in code or are flagged for manual review.

<Note>
  Passing `--key` tells the CLI which API key should receive migrated configuration. The command refuses to overwrite existing non-empty API-key config when it conflicts with local props.
</Note>

Apply the migration once the dry run looks correct:

```bash theme={null}
para migrate v3 . --apply --key <api-key-id>
```

The command updates supported `@getpara/*` dependencies, removes migrated static props from `ParaProvider`, writes the matching values to the selected API key, and creates a `.para-migrate-v3.json` rollback manifest in the app directory. After applying, inspect both the code diff and the API-key configuration:

```bash theme={null}
git diff
para keys config show <api-key-id> --json
```

The API key should now contain the moved settings under fields such as `authConfig`, `modalConfig`, `themeConfig`, `logoUrl`, `externalWalletConfig`, and `partnerLinks`. The SDK reads these settings from the partner record at runtime, and any explicit `configOverrides` in code still take precedence.

If you need to undo the migration, use the rollback manifest. Rollback restores the edited files and the captured previous API-key configuration.

```bash theme={null}
para migrate rollback --manifest ./.para-migrate-v3.json --apply
```

| Deprecated `paraModalConfig` prop         | Configure instead                                                             |
| ----------------------------------------- | ----------------------------------------------------------------------------- |
| `oAuthMethods`                            | Portal → Authentication, or `configOverrides.authConfig.oAuthMethods`         |
| `disableEmailLogin` / `disablePhoneLogin` | Portal → Authentication, or `configOverrides.authConfig.*`                    |
| `twoFactorAuthEnabled`                    | Portal → Authentication, or `configOverrides.authConfig.twoFactorAuthEnabled` |
| `isGuestModeEnabled`                      | Portal → Authentication, or `configOverrides.authConfig.isGuestModeEnabled`   |
| `authLayout`                              | Portal → Authentication, or `configOverrides.modalConfig.authLayout`          |
| `hideWallets`                             | Portal, or `configOverrides.modalConfig.hideWallets`                          |
| `theme`                                   | Portal → Branding, or `configOverrides.themeConfig`                           |
| `logo`                                    | Portal → Branding, or `configOverrides.modalConfig.logo`                      |
| `supportedAccountLinks`                   | Portal (partner-authoritative)                                                |
| `balances`                                | Portal (partner-authoritative)                                                |

### Before & After

<CodeGroup>
  ```tsx v2.x (config in props) theme={null}
  paraModalConfig={{
    oAuthMethods: ["GOOGLE", "APPLE"],
    disableEmailLogin: false,
    twoFactorAuthEnabled: true,
    isGuestModeEnabled: false,
    theme: {
      foregroundColor: "#007AFF",
      backgroundColor: "#FFFFFF",
    },
  }}
  ```

  ```tsx v3.0 (partner record + optional code override) theme={null}
  // OAuth methods, 2FA, guest mode, and theme typically live on the partner
  // record (Developer Portal). Configure them there once; changing them later
  // needs no redeploy.

  // Only reach for configOverrides when you need a per-instance value in code:
  <ParaProvider
    paraClientConfig={{ apiKey: process.env.NEXT_PUBLIC_PARA_API_KEY || "" }}
    configOverrides={{
      themeConfig: {
        foregroundColor: "#007AFF",
        backgroundColor: "#FFFFFF",
      },
    }}
  >
    {children}
  </ParaProvider>
  ```
</CodeGroup>

### New enforcement behavior

Because the SDK now enforces the resolved auth and wallet configuration at its entry points, calling a method for something that is **explicitly disabled** throws a `PartnerConfigError` (with a stable `.code`, e.g. `GUEST_MODE_DISABLED`). This applies to direct SDK calls and custom UI; the default `<ParaModal />` simply hides disabled options.

<Warning>
  Only an **explicit** disable in the resolved config triggers this. A setting you never configured stays permissive, so upgrading without changing the portal or adding `configOverrides` won't start throwing. See [How Configuration Works → Enforcement](/v3/react/guides/customization/configuration#enforcement-partnerconfigerror) for the full list of error codes.
</Warning>

## Portal URL Return Shape

v3.0 introduces companion `*FullUrl` fields on the auth state types and changes the internal portal URL helpers to return `{ url, fullUrl }` instead of a bare string. This powers the new preloaded portal iframe — navigating a preloaded iframe to a shortened URL forces a redirect that negates the preload, so the SDK now surfaces the unshortened URL alongside the (possibly shortened) one.

### Who needs to care

* **Default modal users (`<ParaModal />`):** No action required. The modal consumes the new fields internally.
* **Custom UI consumers** (callers of `signUpOrLogIn`, `verifyOAuth`, `verifyNewAccount`, `onStatePhaseChange`, etc. who read `passkeyUrl` / `passwordUrl` / `pinUrl` / `loginUrl` themselves): read the note below.
* **Callers of `PortalUrlService.constructPortalUrl` / `PortalUrlService.getLoginUrl`, or subclasses that call `ParaCore.constructPortalUrl` directly:** the return value is now `{ url, fullUrl }` instead of `string`. Update your call sites to destructure.

### What changed on the auth state types

`AuthStateVerify`, `AuthStateLogin`, and `AuthStateSignup` each gained optional `*FullUrl` siblings next to their existing URL fields:

| Existing field | New companion field | Where it appears                                                                                                   |
| -------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `loginUrl`     | `loginFullUrl`      | [`AuthStateVerify`](/v3/references/types/authstateverify)                                                          |
| `passkeyUrl`   | `passkeyFullUrl`    | [`AuthStateLogin`](/v3/references/types/authstatelogin), [`AuthStateSignup`](/v3/references/types/authstatesignup) |
| `passwordUrl`  | `passwordFullUrl`   | [`AuthStateLogin`](/v3/references/types/authstatelogin), [`AuthStateSignup`](/v3/references/types/authstatesignup) |
| `pinUrl`       | `pinFullUrl`        | [`AuthStateLogin`](/v3/references/types/authstatelogin), [`AuthStateSignup`](/v3/references/types/authstatesignup) |

When `useShortUrls` is `false` (the default), `*Url` and `*FullUrl` hold the same value. When `useShortUrls` is `true`, `*Url` is the shortened URL and `*FullUrl` is the unshortened one.

The same fields are mirrored on `authStateInfo` exposed by `onStatePhaseChange` / `useParaStatus`: `passkeyFullUrl`, `passwordFullUrl`, `pinFullUrl`, and `verificationFullUrl`.

The OAuth callback signature also picked up the full URL as a second argument:

```ts theme={null}
// v2
redirectCallbacks: {
  onOAuthUrl: (url: string) => {},
}

// v3
redirectCallbacks: {
  onOAuthUrl: (url: string, fullUrl?: string) => {},
}
```

### Picking which URL to open

<AccordionGroup>
  <Accordion title="Opening in a popup or new tab (passkeys, QR codes, email handoff)">
    Keep using the existing `passkeyUrl` / `passwordUrl` / `pinUrl` / `loginUrl` / `url` values. You get short URLs when you opt in via `useShortUrls: true`, which is still the right call for QR codes and links you pass around.
  </Accordion>

  <Accordion title="Navigating a preloaded portal iframe">
    Use the `*FullUrl` variant (or the `fullUrl` second arg on `onOAuthUrl`). A shortened URL would force a redirect inside the iframe and throw away the preloaded state.
  </Accordion>
</AccordionGroup>

### Custom UI example

```tsx theme={null}
// v2 — still works in v3, but you miss out on the preloaded iframe optimization
const { passkeyUrl, passwordUrl, pinUrl } = authStateInfo;
window.open(passkeyUrl ?? passwordUrl ?? pinUrl, "ParaPortal", "popup");

// v3 — keep the short URL for popups; use *FullUrl for preloaded iframe nav
const { passkeyUrl, passkeyFullUrl, passwordUrl, passwordFullUrl, pinUrl, pinFullUrl } = authStateInfo;

// Popups: short URL is fine
window.open(passkeyUrl ?? passwordUrl ?? pinUrl, "ParaPortal", "popup");

// Preloaded iframe: point at the full URL instead
iframeRef.current.src = passkeyFullUrl ?? passwordFullUrl ?? pinFullUrl ?? "";
```

### Service-layer callers (advanced)

If you call `PortalUrlService` directly, or subclass `ParaCore` and call `constructPortalUrl`, the method signatures changed:

```ts theme={null}
// v2
const url: string = await portalUrlService.constructPortalUrl("loginAuth", opts);
const loginUrl: string = await portalUrlService.getLoginUrl(opts);

// v3
const { url, fullUrl } = await portalUrlService.constructPortalUrl("loginAuth", opts);
const { url: loginUrl, fullUrl: loginFullUrl } = await portalUrlService.getLoginUrl(opts);
```

`ParaCore.getLoginUrl()`, `ParaCore.getOAuthUrl()`, and `ParaCore.addCredential()` still return a bare `string`; those are unchanged.
