Skip to main content
Typed clients for all Immutable APIs and services.
Who is this for? Web developers and TypeScript developers looking to build web3 applications and integrate blockchain features seamlessly.

Packages

Install only what you need:
PackagePurposeInstall
@imtbl/authAuthentication & sessionsnpm i @imtbl/auth
@imtbl/walletEmbedded wallets & transactionsnpm i @imtbl/wallet
@imtbl/auth-next-serverNext.js server-side auth (Auth.js v5)npm i @imtbl/auth-next-server
@imtbl/auth-next-clientNext.js client-side hooks & componentsnpm i @imtbl/auth-next-client
@imtbl/orderbookNFT tradingnpm i @imtbl/orderbook
@imtbl/blockchain-dataOn-chain data queries (Indexer)npm i @imtbl/blockchain-data
@imtbl/minting-backendServer-side mintingnpm i @imtbl/minting-backend
@imtbl/contractsSmart contract ABIs & typesnpm i @imtbl/contracts
@imtbl/webhookWebhook signature validationnpm i @imtbl/webhook
@imtbl/configEnvironment configurationnpm i @imtbl/config
Install individual packages instead of @imtbl/sdk for smaller bundles and better tree-shaking.

Installation

npm install @imtbl/sdk
Or install individual packages for smaller bundles:
npm install @imtbl/auth @imtbl/wallet @imtbl/config

Framework Setup

Use @imtbl/auth-next-server and @imtbl/auth-next-client for full Next.js integration with server-side session management, automatic token refresh, and route protection.
These packages require Next.js 14+ with the App Router. For Pages Router or other React frameworks, use @imtbl/auth directly.

Prerequisites

  • Client ID from your Passport client in Hub
  • Next.js 14 or 15 with App Router
  • An AUTH_SECRET environment variable (any random string, minimum 32 characters)

Installation

npm install @imtbl/auth-next-server @imtbl/auth-next-client next-auth@5

Setup

1

Create auth configuration

Create your auth configuration:
// lib/auth.ts
import { NextAuth, createAuthConfig } from "@imtbl/auth-next-server";

export const { handlers, auth, signIn, signOut } = NextAuth({
  ...createAuthConfig({
    clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
    redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
  }),
  secret: process.env.AUTH_SECRET,
  trustHost: true,
});
Create the API route handler:
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/lib/auth";

export const { GET, POST } = handlers;
Wrap your app with SessionProvider:
// app/layout.tsx
import { SessionProvider } from "next-auth/react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <SessionProvider>{children}</SessionProvider>
      </body>
    </html>
  );
}
Set environment variables:
# .env.local
NEXT_PUBLIC_IMMUTABLE_CLIENT_ID=your_client_id
NEXT_PUBLIC_BASE_URL=http://localhost:3000
AUTH_SECRET=your-secret-key-min-32-characters
2

Create a callback page

Handle the OAuth redirect after login.
// app/callback/page.tsx
'use client';

import { CallbackPage, type ImmutableAuthConfig } from '@imtbl/auth-next-client';

const config: ImmutableAuthConfig = {
  clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
  redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
  audience: 'platform_api',
  scope: 'openid profile email offline_access transact',
};

export default function Callback() {
  return (
    <CallbackPage
      config={config}
      loadingComponent={<p>Completing authentication...</p>}
    />
  );
}
3

Set environment variables

# .env.local
NEXT_PUBLIC_IMMUTABLE_CLIENT_ID=your_client_id
NEXT_PUBLIC_BASE_URL=http://localhost:3000
AUTH_SECRET=your-secret-key-min-32-characters
For login, logout, session management, and wallet operations using the client hooks, see the Next.js tabs on the Authentication and Wallet pages.

Next.js Configuration

createAuthConfig

Creates an Auth.js v5 configuration object for Immutable authentication.
OptionTypeRequiredDefault
clientIdstringYes (when config provided)Sandbox client ID
redirectUristringYes (when config provided)origin + '/callback'
audiencestringNo"platform_api"
scopestringNo"openid profile email offline_access transact"
authenticationDomainstringNo"https://auth.immutable.com"
When called with no arguments, createAuthConfig() uses sandbox defaults for quick prototyping.
Zero-config mode uses a shared public sandbox client ID. For production, always use your own client ID from Immutable Hub.

Extending the configuration

You can spread the base config and add any Auth.js options. This is the pattern used in the Passport sample app to support multiple environments:
import { NextAuth, createAuthConfig } from "@imtbl/auth-next-server";

export const { handlers, auth, signIn, signOut } = NextAuth({
  ...createAuthConfig({
    clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
    redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
  }),
  secret: process.env.AUTH_SECRET,
  trustHost: true,
  basePath: "/api/auth",
});

Next.js Server Utilities

Use these in Server Components and Server Actions to handle authentication on the server.

getAuthProps

Pass authentication state to a Client Component when data fetching happens client-side.
// app/dashboard/page.tsx
import { auth } from "@/lib/auth";
import { getAuthProps } from "@imtbl/auth-next-server";
import { redirect } from "next/navigation";
import { DashboardClient } from "./DashboardClient";

export default async function DashboardPage() {
  const authProps = await getAuthProps(auth);

  if (authProps.authError) {
    redirect("/login");
  }

  return <DashboardClient {...authProps} />;
}

getAuthenticatedData

Fetch data server-side for faster initial loads, with automatic client-side fallback when the token is expired.
// app/profile/page.tsx
import { auth } from "@/lib/auth";
import { getAuthenticatedData } from "@imtbl/auth-next-server";
import { redirect } from "next/navigation";

async function fetchProfile(accessToken: string) {
  const res = await fetch("https://api.immutable.com/v1/user/profile", {
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  return res.json();
}

export default async function ProfilePage() {
  const result = await getAuthenticatedData(auth, fetchProfile);

  if (result.authError) redirect("/login");

  return <ProfileClient {...result} />;
}

createProtectedFetchers

Define auth error handling once and reuse it across all protected pages.
// lib/protected.ts
import { auth } from "@/lib/auth";
import { createProtectedFetchers } from "@imtbl/auth-next-server";
import { redirect } from "next/navigation";

export const { getAuthProps, getData } = createProtectedFetchers(
  auth,
  (error) => redirect(`/login?error=${error}`),
);
// app/settings/page.tsx
import { getAuthProps } from "@/lib/protected";

export default async function SettingsPage() {
  const authProps = await getAuthProps();
  return <SettingsClient {...authProps} />;
}

getValidSession

Get fine-grained control over different authentication states.
import { auth } from "@/lib/auth";
import { getValidSession } from "@imtbl/auth-next-server";

export default async function AccountPage() {
  const result = await getValidSession(auth);

  switch (result.status) {
    case "authenticated":
      return <FullAccountPage session={result.session} />;
    case "token_expired":
      return <AccountPageSkeleton session={result.session} />;
    case "unauthenticated":
      return <LoginPrompt />;
    case "error":
      return <AuthErrorPage error={result.error} />;
  }
}

Next.js Route Protection

Middleware

Protect entire sections of your app at the routing level before pages render. Use middleware when you have groups of pages that all require authentication (such as /dashboard/* or /settings/*) and you want to redirect unauthenticated users before any page code runs.
Do not use middleware for pages that show different content for authenticated vs unauthenticated users, or for public pages with optional authenticated features. Use page-level checks with getAuthProps or getValidSession instead.
// middleware.ts
import { createAuthMiddleware } from "@imtbl/auth-next-server";
import { auth } from "@/lib/auth";

export default createAuthMiddleware(auth, {
  loginUrl: "/login",
  publicPaths: ["/", "/about", "/api/public"],
});

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
OptionTypeDescription
loginUrlstringRedirect target for unauthenticated users (default: "/login")
protectedPaths(string | RegExp)[]Paths that require authentication
publicPaths(string | RegExp)[]Paths that skip authentication (takes precedence)

Protected API Routes

Use withAuth to protect individual Route Handlers or Server Actions.
// app/api/user/inventory/route.ts
import { auth } from "@/lib/auth";
import { withAuth } from "@imtbl/auth-next-server";
import { NextResponse } from "next/server";

export const GET = withAuth(auth, async (session, request) => {
  const inventory = await fetchUserInventory(session.accessToken);
  return NextResponse.json(inventory);
});
// app/actions/transfer.ts
"use server";

import { auth } from "@/lib/auth";
import { withAuth } from "@imtbl/auth-next-server";

export const transferAsset = withAuth(auth, async (session, formData: FormData) => {
  const assetId = formData.get("assetId") as string;
  const toAddress = formData.get("toAddress") as string;

  return await executeTransfer({
    from: session.user.sub,
    to: toAddress,
    assetId,
    accessToken: session.accessToken,
  });
});
Inside withAuth handlers, the session object includes accessToken directly since these run server-side. This is different from the client-side useImmutableSession hook where you must use getAccessToken().

Next.js Session Type Reference

The server-side session (available in withAuth handlers and getAuthenticatedData fetchers) includes the following fields:
FieldTypeDescription
user.substringImmutable user ID
user.emailstring?User’s email address
user.nicknamestring?User’s display name
accessTokenstringCurrent access token (server-side only)
refreshTokenstring?Refresh token
accessTokenExpiresnumberToken expiry timestamp (ms)
zkEvm.ethAddressstring?zkEVM wallet address
zkEvm.userAdminAddressstring?Admin wallet address
errorstring?"TokenExpired" or "RefreshTokenError"
The client-side session from useImmutableSession intentionally omits accessToken to prevent use of stale tokens. Use getAccessToken() for API calls or getUser() for wallet integration.
The idToken is not stored in the session cookie to stay within CDN header size limits. On the client, @imtbl/auth-next-client persists it in localStorage so wallet operations can access it via getUser().

Next.js Exported Utilities

The server package exports low-level utilities for manual token handling:
import {
  isTokenExpired,
  refreshAccessToken,
  extractZkEvmFromIdToken,
} from "@imtbl/auth-next-server";
UtilityDescription
isTokenExpired(expiresAt)Check if an access token has expired
refreshAccessToken(token)Manually refresh tokens using a refresh token
extractZkEvmFromIdToken(idToken)Extract zkEVM claims (ethAddress, userAdminAddress) from an ID token

Environment Configuration

EnvironmentChainAPI Base
SANDBOXImmutable Testnetapi.sandbox.immutable.com
PRODUCTIONImmutable Mainnetapi.immutable.com

Next Steps

Getting Started

Additional Resources