This is an advanced feature. It is recommended that you reach out to the Privy team if you are interested in building a custom global wallet experience.
Privy allows you to build your own custom global wallet experience, instead of using Privy’s default user interfaces. To build your own experience, you will create two new pages using methods from the @privy-io/cross-app-provider library to communicate via the Privy cross-app protocol. Your app handles the user interfaces, and Privy handles the rest.
The @privy-io/cross-app-provider SDK is a vanilla JavaScript library. We recommend using this library alongside @privy-io/react-auth for the best development experience.
This guide highlights the core interfaces of the @privy-io/cross-app-provider SDK and how they integrate with the React SDK. For a more complete reference implementation, see the cross-app provider starter repo.
1

Set up a cross-app provider client

// client.ts
import {createClient} from '@privy-io/cross-app-provider';

export const client = createClient({
  appId: '<your-privy-app-id>',
  appClientId: '<your-privy-app-client-id>',
  // e.g. https://privy.your-app.com
  privyDomain: '<your-privy-auth-domain>'
});
2

Build the connect experience

Allow user’s to accept (or reject) a cross-app connection request by using the client to handle parsing input and responging to the requesting app.First, use the client.getConnectionRequestFromUrlParams method when the page loads to read the request into your app.
const connectionRequest = client.getConnectionRequestFromUrlParams();
Then, use client.acceptConnection and client.rejectConnection to allow your users to explicitly approve or deny the connection request respectively via clicking a button like so:
// Approve
<button
  onClick={async () => {
    await client.acceptConnection({
      accessToken,
      address,
      userId: user.id,
      connectionRequest
    });
  }}
>
  Accept
</button>
// Deny
<button
  onClick={async () => {
    await client.rejectConnection({
      accessToken,
      requesterOrigin: connectionRequest.requesterOrigin
    });
  }}
>
  Deny
</button>
3

Build the request experience

This page allows you to control the experience of a global wallet request.First read the request using client.getVerifiedWalletRequest to decrypt, parse and verify the input. This method will also return the connection object used to communicate a result back to the requester.
const {request, connection} = await client.getVerifiedWalletRequest({
  userId: user.id
});
Once you have the result of handling this request you can return the response to the requester using client.handleSuccess, for example responding to a signature request:
const message = request.params[0];
const {signature} = await signMessage({message});

await client.handleSuccess({
  accessToken,
  result: signature,
  connection
});
Similarly, if the user rejects the request use client.rejectRequest to send an error message to the requester:
await client.rejectRequest({
  accessToken,
  requesterOrigin: connection.requesterOrigin
});
If there are other errors (e.g. not enough funds for a transaction) use the client.handleError method to pass the error back to the requester:
await client.handleError({
  accessToken,
  error: new Error('<message>'),
  requesterOrigin: connection.requesterOrigin
});
4

Upgrade your clients

Ensure client apps are using the latest version of @privy-io/cross-app-connect
5

Set up your custom URLs in the Privy Dashboard

Prior to enabling the custom URLs in your dashboard, ensure that your integration is ready to receive production traffic, including setting a strong CSP and adding your custom URLs as allowed domains for your Privy app.
From the Privy dashboard, navigate to Global Wallet > Advanced. Enable Custom URLs, input the URL(s) where your pages are hosted, and save your changes.
That’s it, now your global wallet has a fully custom experience!