HatchedDocs
Guides

Browser usage (publishable keys)

Use @hatched/sdk-js in the browser with a publishable key — read buddies, mint widget sessions, no server round-trip.

For pages where you only need to read buddy state or mint widget sessions, a publishable key lets you talk to Hatched directly from the browser — no server endpoint of your own, no secret-key leak risk.

Decide: is this the right tool?

You want to...Use
Read buddy state on a static sitePublishable key
Show the buddy widgetMint widget session with a publishable key
Send events (lesson_completed, etc.)Secret key on your server
Spend coins, equip items via APISecret key on your server
Manage webhook configsSecret key on your server

If you need mutations, keep your secret key on the server. See Auth model.

Create a publishable key

Dashboard → Developers → API keys → Create publishable key.

  • Pick a label.
  • Confirm the scopes (default: all read scopes + write:widget-sessions).
  • Copy the hatch_pk_* value. Unlike secret keys, it's safe to put in client-side config (NEXT_PUBLIC_*, <meta> tags, etc.).

Initialise the client in the browser

import { HatchedClient } from '@hatched/sdk-js';

const hatched = new HatchedClient({
  publishableKey: process.env.NEXT_PUBLIC_HATCHED_PK!,
});

The server-only runtime guard is disabled for publishable-key clients, so this works in a React client component, a Vite SPA, a static landing page, anywhere.

Read buddy state

'use client';
import { useEffect, useState } from 'react';
import { HatchedClient, type Buddy } from '@hatched/sdk-js';

const hatched = new HatchedClient({
  publishableKey: process.env.NEXT_PUBLIC_HATCHED_PK!,
});

export function BuddyBadge({ buddyId }: { buddyId: string }) {
  const [buddy, setBuddy] = useState<Buddy | null>(null);
  useEffect(() => {
    hatched.buddies.get(buddyId).then(setBuddy).catch(console.error);
  }, [buddyId]);

  if (!buddy) return null;
  return (
    <div>
      <img src={buddy.thumbUrl ?? undefined} alt={buddy.name} />
      <div>
        Level {buddy.evolutionStage} · {buddy.coins} coins
      </div>
    </div>
  );
}

Mint a widget session in the browser

Publishable keys can mint their own widget sessions — no server endpoint needed:

const session = await hatched.widgetSessions.create({
  buddyId: 'bdy_abc',
  userId: currentUserId,
  scopes: ['buddy:read', 'buddy:interact'],
  ttlSeconds: 900,
});

document
  .querySelector('[data-hatched-widget]')!
  .setAttribute('data-hatched-token', session.token);

What fails and what you'll see

Attempting a mutation from a publishable-key client fails before the network call with PublishableKeyScopeError — no latency cost:

import { PublishableKeyScopeError } from '@hatched/sdk-js';

try {
  await hatched.events.send({ ... }); // server-only
} catch (err) {
  if (err instanceof PublishableKeyScopeError) {
    console.error('move this call to your backend with HATCHED_API_KEY');
  }
}

If you somehow bypass the SDK and hit the API directly, you'll get 403 publishable_key_scope with the same semantic.

Rotation

Publishable keys rotate like secret keys — Dashboard → Developers → API keys → revoke. Revocation is instant; browser sessions error on their next call and you deploy a new NEXT_PUBLIC_HATCHED_PK.

Try it

Head to the Playground — paste a publishable key, hit "Get buddy", see the response inline.