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 CLI you already use (Claude Code, Codex etc.). It inspects your codebase, proposes an integration plan for your approval, implements it as a reviewable diff, and verifies the result.

Prefer to do it by hand? The manual steps below are the same setup.

Manual setup

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

serve() is async, so it belongs in an useEffect.

components/AdSlot.jsx
import { useEffect, useState } from 'react';
import { ImageFormat } from '@specify-sh/publisher-sdk';
import { specify } from '../lib/specify';

export function AdSlot({ wallets }) {
  const [ad, setAd] = useState(null);
  // Depend on the addresses, not the array: a new array each render would restart the effect
  const key = wallets.join(',');

  useEffect(() => {
    let active = true;

    specify
      .serve(wallets, { imageFormat: ImageFormat.LANDSCAPE, adUnitId: 'sidebar' })
      .then((result) => {
        // Ignore a result that arrives after unmount
        if (active) setAd(result);
      });

    return () => {
      active = false;
    };
  }, [key]);

  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>
  );
}

Pass a wallet address that the user controls and choose the image format that matches your ad placement.

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