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

# Using the vanilla JavaScript SDK

The `@privy-io/js-sdk-core` library is a vanilla JavaScript library for browser-like environments. It provides secure authentication, non-custodial embedded wallets, and user management without requiring React or any other UI framework.

<Note>
  `@privy-io/js-sdk-core` is a low-level library. Please do not attempt to use this library without
  first reaching out to the Privy team to discuss your project and which Privy SDK options may be
  better suited to it.
</Note>

## Prerequisites

Before you begin:

* [Set up your Privy app](/basics/get-started/dashboard/create-new-app) and obtain your **app ID** from the Privy Dashboard
* Obtain your **client ID** from the Dashboard under **Settings → Clients**

## Installation

<CodeGroup>
  ```bash npm theme={"system"}
  npm install @privy-io/js-sdk-core@latest
  ```

  ```bash pnpm theme={"system"}
  pnpm install @privy-io/js-sdk-core@latest
  ```

  ```bash yarn theme={"system"}
  yarn add @privy-io/js-sdk-core@latest
  ```
</CodeGroup>

## 1. Create the Privy client

Import the `Privy` class and create a single instance for your application. The client accepts the following configuration:

```ts theme={"system"}
import Privy, {LocalStorage} from '@privy-io/js-sdk-core';

const privy = new Privy({
  appId: '<your-privy-app-id>',
  clientId: '<your-privy-client-id>',
  storage: new LocalStorage()
});
```

<Warning>
  Only instantiate a single Privy client for your application. Creating multiple instances will
  cause unexpected behavior.
</Warning>

<Expandable title="Configuration options">
  <ParamField path="appId" type="string" required>
    Your Privy app ID from the Dashboard.
  </ParamField>

  <ParamField path="clientId" type="string">
    Your Privy client ID from the Dashboard under **Settings → Clients**.
  </ParamField>

  <ParamField path="storage" type="Storage" required>
    A storage adapter for persisting session state. Use `LocalStorage` for browsers, or implement a
    custom adapter for other environments.
  </ParamField>

  <ParamField path="logger" type="Logger">
    A custom logger object to replace the default console-based logger. Useful for routing Privy logs
    to your observability stack.
  </ParamField>
</Expandable>

<Accordion title="Custom storage adapters">
  The `Storage` interface requires four methods. Implement this interface if `LocalStorage` is not suitable for your environment (e.g., encrypted storage, server-side rendering, or non-browser runtimes):

  ```ts {skip-check} theme={"system"}
  import type {Storage} from '@privy-io/js-sdk-core';

  const myStorage: Storage = {
    get(key: string): Promise<string | null> {
      /* return value for key, or null */
    },
    put(key: string, val: string): Promise<void> {
      /* persist key-value pair */
    },
    del(key: string): Promise<void> {
      /* delete key */
    },
    getKeys(): Promise<string[]> {
      /* return all stored keys */
    }
  };
  ```
</Accordion>

## 2. Initialize the client

After creating the client, call `initialize()` to establish a connection with the Privy backend and restore any existing session. This must complete before performing any other operations.

```ts {skip-check} theme={"system"}
try {
  await privy.initialize();
} catch (e) {
  // Initialization can fail if storage access is blocked or network is unavailable
  console.error('Privy initialization failed:', e);
}
```

<Info>
  After `initialize()` resolves, call `client.user.get()` to check for an existing authenticated
  session. If the user previously logged in and the session is still valid, this returns the user
  object without requiring re-authentication.
</Info>

### Restoring a returning user's session

```ts {skip-check} theme={"system"}
await privy.initialize();

// Check if a user is already logged in from a previous session
const {user} = await privy.user.get();
if (user) {
  // Store the user object in your application state (e.g., a store, context, or signal)
  // This is the source of truth for the authenticated user throughout your app
} else {
  // No active session — prompt the user to log in
}
```

## 3. Connect to the secure context

The Privy secure context is an iframe that handles embedded wallet key material. Your app must mount this iframe and wire up bidirectional message passing.

### Mount the iframe

```ts {skip-check} theme={"system"}
const iframe = document.createElement('iframe');
iframe.src = privy.embeddedWallet.getURL();
iframe.style.display = 'none';

// Track when the iframe is ready
let isProxyReady = false;
iframe.onload = () => {
  isProxyReady = true;
};

document.body.appendChild(iframe);
```

### Wire up message passing

```ts {skip-check} theme={"system"}
// Allow the Privy client to post messages to the iframe
privy.setMessagePoster(iframe.contentWindow);

// Forward messages from the iframe to the Privy client
window.addEventListener('message', (e) => {
  // Only process messages from the Privy iframe
  if (e.source !== iframe.contentWindow) return;
  const data = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
  privy.embeddedWallet.onMessage(data);
});
```

<Tip>
  If you are using a UI rendering library or framework, render the iframe and register event
  listeners using that library's lifecycle methods instead of manipulating the DOM directly.
</Tip>

## 4. Authenticate a user

The Privy core SDK supports email, SMS, OAuth, and JWT-based authentication.

<CodeGroup>
  ```ts Email {skip-check} theme={"system"}
  const emailAddress = 'user@example.com';
  await privy.auth.email.sendCode(emailAddress);

  // Collect the OTP from your UI
  const otp = '123456';
  const session = await privy.auth.email.loginWithCode(emailAddress, otp);
  ```

  ```ts SMS {skip-check} theme={"system"}
  // Format: '+1 555-555-5555'
  const phoneNumber = '+1 555-555-5555';
  await privy.auth.phone.sendCode(phoneNumber);

  // Collect the OTP from your UI
  const otp = '123456';
  const session = await privy.auth.phone.loginWithCode(phoneNumber, otp);
  ```

  ```ts OAuth {skip-check} theme={"system"}
  // Note: OAuth flows use PKCE and require `window.crypto.subtle` to be available.
  // This is supported in all modern browsers but may not be available in non-secure
  // contexts (e.g., HTTP without TLS).
  const provider = 'google';
  const redirectURI = `${window.location.origin}/login-callback`;
  const oauthURL = await privy.auth.oauth.generateURL(provider, redirectURI);

  // Redirect the user to the OAuth provider
  window.location.assign(oauthURL);

  // When the user returns to your app at the redirectURI
  const queryParams = new URLSearchParams(window.location.search);
  const oauthCode = queryParams.get('privy_oauth_code');
  const oauthState = queryParams.get('privy_oauth_state');
  const session = await privy.auth.oauth.loginWithCode(oauthCode, oauthState);
  ```

  ```ts JWT (custom auth) {skip-check} theme={"system"}
  const authToken = 'your-jwt-access-or-identity-token';
  const session = await privy.auth.customProvider.syncWithToken(authToken);
  ```

  ```ts SIWE (Ethereum) {skip-check} theme={"system"}
  const [address] = await ethereum.request({method: 'eth_requestAccounts'});

  const wallet = {
    address,
    chainId: 'eip155:1'
  };

  const {message} = await privy.auth.siwe.init(wallet, window.location.host, window.location.origin);

  const signature = await ethereum.request({
    method: 'personal_sign',
    params: [message, address]
  });

  const {user} = await privy.auth.siwe.loginWithSiwe(signature, wallet, message, 'login-or-sign-up');
  ```

  ```ts SIWS (Solana) {skip-check} theme={"system"}
  const address = solana.publicKey.toBase58();

  const {nonce} = await privy.auth.siws.fetchNonce({address});

  const message = createSiwsMessage({
    address,
    nonce,
    domain: window.location.host,
    uri: window.location.origin
  });

  const encodedMessage = new TextEncoder().encode(message);
  const signed = await solana.signMessage(encodedMessage, 'utf8');

  const signature = btoa(String.fromCharCode(...signed.signature));

  const {user} = await privy.auth.siws.login({
    message,
    signature,
    mode: 'login-or-sign-up'
  });
  ```
</CodeGroup>

## 5. Get access tokens for your backend

After authentication, use `getAccessToken()` to retrieve the user's access token. Include this token in requests to your backend to verify the user's identity.

```ts {skip-check} theme={"system"}
const token = await privy.getAccessToken();

// Send authenticated requests to your backend
const response = await fetch('https://your-server.com/api/protected', {
  headers: {
    Authorization: `Bearer ${token}`
  }
});
```

<Info>
  `getAccessToken()` automatically handles token refresh when the access token is near expiration.
  The returned token is always valid at the time of return.
</Info>

## 6. Create an embedded wallet

Your app can [**manually** create wallets](/wallets/wallets/create/create-a-wallet) for users when desired.

<Info>Privy can provision wallets for your users on both **Ethereum** and **Solana**.</Info>

<CodeGroup>
  ```ts Ethereum {skip-check} theme={"system"}
  import {getUserEmbeddedEthereumWallet, getEntropyDetailsFromUser} from '@privy-io/js-sdk-core';

  const {user} = await privy.embeddedWallet.create({});
  const wallet = getUserEmbeddedEthereumWallet(user);
  const {entropyId, entropyIdVerifier} = getEntropyDetailsFromUser(user);
  const provider = await privy.embeddedWallet.getEthereumProvider({
    wallet,
    entropyId,
    entropyIdVerifier
  });
  ```

  ```ts Solana {skip-check} theme={"system"}
  import {getUserEmbeddedSolanaWallet, getEntropyDetailsFromUser} from '@privy-io/js-sdk-core';

  const {user} = await privy.embeddedWallet.createSolana();
  const account = getUserEmbeddedSolanaWallet(user);
  const {entropyId, entropyIdVerifier} = getEntropyDetailsFromUser(user);
  const provider = await privy.embeddedWallet.getSolanaProvider(
    account,
    entropyId,
    entropyIdVerifier
  );
  ```
</CodeGroup>

## 7. Connect to an existing wallet

When a user returns to your app on a subsequent visit, the wallet already exists but a provider must be obtained.

```ts {skip-check} theme={"system"}
import {getUserEmbeddedEthereumWallet, getEntropyDetailsFromUser} from '@privy-io/js-sdk-core';

const {user} = await privy.user.get();
const wallet = getUserEmbeddedEthereumWallet(user);
const {entropyId, entropyIdVerifier} = getEntropyDetailsFromUser(user);

const provider = await privy.embeddedWallet.getEthereumProvider({
  wallet,
  entropyId,
  entropyIdVerifier
});
```

## 8. Use the embedded wallet

<Info>
  Your wallet must have funds to pay for gas. Use a testnet
  [faucet](https://console.optimism.io/faucet) to test on Base Sepolia, or send funds to the wallet
  on your preferred network.
</Info>

With the embedded wallet provider, your app can prompt the user to sign messages and send transactions.

<CodeGroup>
  ```ts Ethereum {skip-check} theme={"system"}
  const provider = await privy.embeddedWallet.getEthereumProvider({...args});

  // Sign a message
  await provider.request({
    method: 'personal_sign',
    params: ['hello', signingAddress]
  });

  // Send a transaction
  await provider.request({
    method: 'eth_sendTransaction',
    params: [
      {
        to: '<recipient_address>',
        value: '0x2386F26FC10000' // 0.01 ETH in wei
      }
    ]
  });
  ```

  ```ts Solana {skip-check} theme={"system"}
  const provider = await privy.embeddedWallet.getSolanaProvider(...args);

  // Sign a message
  await provider.request({
    method: 'signMessage',
    params: {message: 'hello'}
  });
  ```
</CodeGroup>

<Tip>
  [Learn more](/wallets/using-wallets/ethereum/send-a-transaction) about sending transactions with
  the embedded wallet. Privy enables you to take many actions on the embedded wallet, including
  [sign a message](/wallets/using-wallets/ethereum/sign-a-message), [sign typed
  data](/wallets/using-wallets/ethereum/sign-typed-data), and [sign a
  transaction](/wallets/using-wallets/ethereum/sign-a-transaction).
</Tip>

## 9. Log the user out

To end the user's session and clean up resources:

```ts {skip-check} theme={"system"}
const {user} = await privy.user.get();
await privy.auth.logout({userId: user.id});

// Clean up the iframe and event listeners
window.removeEventListener('message', listener);
iframe.remove();
```

After logout, the user must authenticate again to access any protected resources or wallet functionality.

## Full integration example

Below is a minimal end-to-end integration showing the complete lifecycle:

```ts {skip-check} theme={"system"}
import Privy, {
  LocalStorage,
  getUserEmbeddedEthereumWallet,
  getEntropyDetailsFromUser
} from '@privy-io/js-sdk-core';

// 1. Create the client (once, at app startup)
const privy = new Privy({
  appId: '<your-privy-app-id>',
  clientId: '<your-privy-client-id>',
  storage: new LocalStorage()
});

// 2. Initialize and check for existing session
await privy.initialize();
let {user} = await privy.user.get();

// 3. Set up the secure context
const iframe = document.createElement('iframe');
iframe.src = privy.embeddedWallet.getURL();
iframe.style.display = 'none';
document.body.appendChild(iframe);

privy.setMessagePoster(iframe.contentWindow);
const listener = (e) => {
  if (e.source !== iframe.contentWindow) return;
  const data = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
  privy.embeddedWallet.onMessage(data);
};
window.addEventListener('message', listener);

// 4. Authenticate (if no existing session)
if (!user) {
  await privy.auth.email.sendCode('user@example.com');
  const session = await privy.auth.email.loginWithCode('user@example.com', '123456');
  user = session.user;
}

// 5. Get the wallet provider
const wallet = getUserEmbeddedEthereumWallet(user);
const {entropyId, entropyIdVerifier} = getEntropyDetailsFromUser(user);

const provider = await privy.embeddedWallet.getEthereumProvider({
  wallet,
  entropyId,
  entropyIdVerifier
});

// 6. Use the wallet
await provider.request({
  method: 'personal_sign',
  params: ['hello world', wallet.address]
});

// 7. Get a token for backend requests
const accessToken = await privy.getAccessToken();
```
