Specify docs

React

Add Specify to a React app.

Set up with the wizard

npx @specify-sh/wizard --publisher

The 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-sdk

2. 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.

lib/specify.js
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.

components/AdSlot.jsx
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.

Wire your consent platform to the SDK, high in the component tree. Consent starts false and the SDK never stores it.

components/ConsentBridge.jsx
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.

components/WalletBridge.jsx
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.

components/AdSlot.jsx
specify.serve({ imageFormat: ImageFormat.LANDSCAPE, adUnitId: 'sidebar' })

Next steps

On this page