React
Add Specify to a React app.
Set up with the wizard
npx @specify-sh/wizard --publisherThe wizard runs through the coding agent you already use (Claude Code, Codex etc.). It inspects your codebase, proposes an integration plan and implements it as a reviewable diff.
Prefer to do it by hand? The manual steps below are the same setup.
Manual setup
Runnable example
react-typescript is this guide as a working Vite and wagmi app.
1. Install the SDK
npm i @specify-sh/publisher-sdk2. Initialize the client
You will need a publisher key. See Publisher keys.
Create the client once and import it wherever you serve an ad. Use the publisher key via your environment.
import Specify from '@specify-sh/publisher-sdk';
const specifyPublisherKey = 'spk_123...' // Bring this from your environment
export const specify = new Specify({
publisherKey: specifyPublisherKey
});3. Build an ad component
useSpecifyAd() serves on mount, tries again when the identity improves, and keeps the first ad it gets.
import { ImageFormat } from '@specify-sh/publisher-sdk';
import { useSpecifyAd } from '@specify-sh/publisher-sdk/react';
import { specify } from '../lib/specify';
export function AdSlot() {
const ad = useSpecifyAd({
specify,
imageFormat: ImageFormat.LANDSCAPE,
adUnitId: 'sidebar'
});
if (!ad) return null;
return (
<a href={ad.ctaUrl} target="_blank" rel="noopener noreferrer">
{ad.imageUrl && <img src={ad.imageUrl} alt="" />}
<h3>{ad.headline}</h3>
<p>{ad.content}</p>
<span>{ad.ctaLabel}</span>
</a>
);
}The slot takes no addresses. Wallets registered through identify() ride along, so nothing has to be passed down the tree. To serve against an address you hold in the component, pass it first:
const ad = useSpecifyAd(address, { specify, imageFormat: ImageFormat.LANDSCAPE });Pass every address the visitor controls, not just the active one. More wallets means better targeting.
const ad = useSpecifyAd([address, ...linkedAddresses], {
specify,
imageFormat: ImageFormat.LANDSCAPE
});Available image formats and the fields returned in the ad can be found in the Browser SDK reference.
4. Connect consent
Wire your consent platform to the SDK, high in the component tree. Consent starts false and the SDK never stores it.
import { useEffect } from 'react';
import { specify } from '../lib/specify';
// Adapt this code to your cookie consent management tool
export function ConsentBridge() {
useEffect(() => cmp.onChange((consent) => {
specify.setCookieConsent(consent.targetedAdvertising);
}), []);
return null;
}With consent granted, Specify recognises returning visitors across every site in our network, so a placement can fill even when nobody has connected a wallet to your app.
5. Register connected wallets
If your app has a wallet connection flow, notify the SDK when a wallet is connected. Every later serve() includes it, so components deeper in the tree do not need the address passed down.
import { useEffect } from 'react';
import { useAccount } from 'wagmi';
import { specify } from '../lib/specify';
export function WalletBridge() {
const { address } = useAccount();
useEffect(() => {
if (address) specify.identify(address);
}, [address]);
return null;
}With this in place, an ad slot needs no wallet address at all.
specify.serve({ imageFormat: ImageFormat.LANDSCAPE, adUnitId: 'sidebar' })