Skip to main content

Quick Start

The fastest way to add ZKPassport to your app is the Verify with ZKPassport button from @zkpassport/ui. Clicking it opens ZKPassport's hosted verification page in a popup, which guides the user through the verification with the ZKPassport app and hands the proofs back to your page. You then verify the proofs on your server with @zkpassport/sdk. It works in React and in any vanilla JS / framework setup.

Installation​

Install the UI package alongside the SDK using npm (or any JavaScript package manager such as yarn, pnpm or bun):

npm install @zkpassport/sdk @zkpassport/ui

No API key and no account are required to get started — just install the packages and you're good to go. Your app's name and logo come from your dashboard project; until you register your domain, the ZKPassport app shows the domain itself.

Add the verify button​

The button takes a purpose to show the user and a query describing what to verify. Below we verify the user is 18 or older. When the user is done, onSuccess receives the proofs and the result, which you send to your server to be verified.

import { VerifyWithZKPassport } from "@zkpassport/ui/react-button";

export default function VerifyPage() {
return (
<VerifyWithZKPassport
purpose="Prove you are 18+ years old"
service={{ scope: "adult" }}
query={{ age: { min: 18 } }}
onSuccess={async ({ proofs, result }) => {
const response = await fetch("/api/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ proofs, result }),
});
const { verified } = await response.json();
// Returning false shows the button's error state instead of success
return verified;
}}
/>
);
}

In the Next.js App Router, the React entry is marked "use client", so import it from a client component.

The query is a plain object: one entry per attribute, with min/max for bounds, included/excluded for country sets, and disclose: true to reveal a value. See Basic Usage for the full shape.

Verify the proofs on your server​

On your server, recreate the same query with createQuery() and pass it to verify() along with the proofs, the result, and the scope you used. Create the ZKPassport instance with the domain of the page the button runs on.

import { ZKPassport } from "@zkpassport/sdk";

const zkPassport = new ZKPassport("your-domain.com");

// Call this from the route behind POST /api/verify with the request body
export async function verifyProofs({ proofs, result }) {
// Recreate the query rather than accepting it from the browser
const { query } = zkPassport.createQuery().gte("age", 18).done();

const { verified, uniqueIdentifier } = await zkPassport.verify({
proofs,
originalQuery: query,
queryResult: result,
scope: "adult",
});

if (verified) {
console.log("User is 18+", result.age.gte.result);
// The same ID always gets the same identifier for your domain and scope
console.log("Unique identifier", uniqueIdentifier);
}
return { verified };
}

That's a complete, working verification flow. See the Client-Server example for a full server.

Where to go next​

  • Basic Usage — build richer queries, understand the full lifecycle callbacks, and drop down to the SDK directly when you need custom UI.
  • Dashboard & Policies — manage branding and the exact request from the ZKPassport Dashboard and reference it by its policy id instead of building the query in code.
  • Examples — age, nationality, residency, personhood, KYC, FaceMatch, and client-server verification.
tip

Prefer a full project to start from? Check out our Next.js boilerplate here.