React and Next.js API

Add your pet with one component, then control it from your own components: show a message after a visitor acts, hide it on some routes, and respond when visitors click or pet it.

Install the package

@petmysite/react works with React 18 and later, including Next.js with the App Router or Pages Router. It loads the same pet.js script as your installation code and adds typed controls on top.

npm install @petmysite/react
  1. In PetMySite, open your website's Installation page and copy the data-site value from the script. It starts with pms_ and is safe to use in browser code.
  2. Render <PetMySite /> once, in the layout that stays on screen as visitors move between routes.
  3. Deploy to the domain you registered, then select Verify installation in PetMySite.

Next.js App Router

The component is a client component, so you can render it straight from a server layout.

// app/layout.tsx
import { PetMySite } from "@petmysite/react";

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en">
      <body>
        {children}
        <PetMySite siteKey="YOUR_SITE_KEY" />
      </body>
    </html>
  );
}

Next.js Pages Router

// pages/_app.tsx
import type { AppProps } from "next/app";
import { PetMySite } from "@petmysite/react";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Component {...pageProps} />
      <PetMySite siteKey="YOUR_SITE_KEY" />
    </>
  );
}

Other React apps

// src/App.tsx (Vite, React Router, or any React app)
import { PetMySite } from "@petmysite/react";

export default function App() {
  return (
    <>
      <YourRoutes />
      <PetMySite siteKey="YOUR_SITE_KEY" />
    </>
  );
}

To keep the key out of your source, read it from an environment variable that reaches the browser, such as NEXT_PUBLIC_PETMYSITE_KEY in Next.js or VITE_PETMYSITE_KEY in Vite:

<PetMySite siteKey={process.env.NEXT_PUBLIC_PETMYSITE_KEY!} />

Use either the package or the script tag, not both. If the page already has the script with the same key, the package reuses it. With a different key it logs an error and leaves the page as it is.

Component props

siteKey: string

Required. Your public pms_ key. An invalid key logs a [PetMySite] error in the console instead of breaking your app.

hidden?: boolean

Hides the pet, its menu and any message while true, and shows it again when it turns false. Leave it out to keep the pet visible.

onClick?: (event: PetEvent) => void

The visitor clicked or tapped the pet, or pressed Enter or Space while it had focus. Runs before the pet opens your chat or menu; call event.preventDefault() to stop that.

onPetting?: (event: PetEvent) => void

The visitor finished petting the pet.

onRightClick?: (event: PetEvent) => void

The visitor right-clicked or long-pressed the pet.

The component renders nothing itself and does nothing during server rendering. Handlers can change on every render; the latest one always runs.

Control the pet

Import petMySite in any client component. It has the same methods as the JavaScript API, and you can call it before the pet has loaded: calls wait and then run in order.

"use client";
import { petMySite } from "@petmysite/react";

export function AddToCartButton() {
  return (
    <button
      onClick={() => {
        petMySite.play("celebrate");
        petMySite.showBubble("Nice pick! Your cart is ready.", "Check out", "/checkout");
      }}
    >
      Add to cart
    </button>
  );
}

petMySite.show() · petMySite.hide()

Show or hide the pet. Prefer the hidden prop when visibility follows your app's state.

petMySite.showBubble(message, linkText?, action?, options?)

Shows a speech bubble. message is plain text of 1 to 160 characters; linkText is an optional button label of up to 40. action is an http(s) address or a function, and { newTab: true } opens an address in a new tab.

petMySite.hideBubble()

Closes a bubble you opened with showBubble().

petMySite.play(state)

Plays wave, celebrate, excited, surprised, curious, loved, look_around or peek. Nothing plays while the pet is hidden or when the visitor prefers reduced motion.

petMySite.open()

Does what a click on the pet does: opens your chat, or the contact menu. Unlike the other methods it doesn't wait: it returns false if the pet hasn't loaded yet or there's nothing to open.

Methods never throw. Invalid arguments log a warning starting with [PetMySite] and are ignored. If the pet can't load, for example because the domain doesn't match, calls are quietly dropped.

Listen for events

There are three events: click, petting and rightClick. Handle them with the component's props:

"use client";
import { PetMySite } from "@petmysite/react";

export function Pet() {
  return (
    <PetMySite
      siteKey="YOUR_SITE_KEY"
      onPetting={() => analytics.track("pet_petting")}
      onRightClick={(e) => e.pet.play("surprised")}
    />
  );
}

Or, in any component further down the tree, with usePetMySiteEvent(type, handler). The handler is removed when the component unmounts.

"use client";
import { petMySite, usePetMySiteEvent } from "@petmysite/react";

export function PettingThanks() {
  usePetMySiteEvent("petting", () => {
    petMySite.showBubble("That tickles! Thanks for saying hi.");
  });
  return null;
}

Outside React, petMySite.onClick(handler), onPetting, onRightClick and on(type, handler) each return a function that removes the handler.

Every handler receives an event with type, pet.id (for example "piko"), pet.play(state), cancelable, defaultPrevented and preventDefault(). Only click can be cancelled.

Routing

The pet follows client-side navigation. When the route changes it closes its bubble and menu, then checks your Smart Bubbles for the new path, just as it would on a full page load.

Hide the pet on some routes

hidden and petMySite.hide() last across route changes until you show the pet again, so route-based hiding is one prop:

// app/pet.tsx
"use client";
import { usePathname } from "next/navigation";
import { PetMySite } from "@petmysite/react";

export function Pet() {
  const pathname = usePathname();
  return (
    <PetMySite
      siteKey="YOUR_SITE_KEY"
      hidden={pathname.startsWith("/checkout")}
    />
  );
}

// app/layout.tsx: render <Pet /> inside <body> instead of <PetMySite />

Bubble links that stay in your app

A bubble with an address as its action does a full page load. To keep client-side navigation, pass a function that uses your router:

"use client";
import { useRouter } from "next/navigation";
import { petMySite } from "@petmysite/react";

export function TourPrompt() {
  const router = useRouter();
  return (
    <button
      onClick={() =>
        petMySite.showBubble("Want a two-minute tour?", "Start the tour", () =>
          router.push("/tour"),
        )
      }
    >
      Show tip
    </button>
  );
}

Examples

Open your own help panel instead of chat

"use client";
import { useState } from "react";
import { PetMySite } from "@petmysite/react";

export function Pet() {
  const [helpOpen, setHelpOpen] = useState(false);
  return (
    <>
      <PetMySite
        siteKey="YOUR_SITE_KEY"
        onClick={(e) => {
          e.preventDefault();   // skip the configured chat or menu
          e.pet.play("wave");
          setHelpOpen(true);
        }}
      />
      {helpOpen && <HelpPanel onClose={() => setHelpOpen(false)} />}
    </>
  );
}

Use the page API directly

withPetMySite(callback) runs your callback with the raw window.petMySite object as soon as the script has loaded, or straight away if it already has.

import { withPetMySite } from "@petmysite/react";

withPetMySite((api) => {
  const stop = api.onClick(() => console.log("clicked"));
  // later: stop();
});

TypeScript

The package ships its own types. You don't need the declarations from the JavaScript API page.

import type {
  PetMySiteProps,   // props of <PetMySite />
  PetMySiteApi,     // the window.petMySite object
  PetEvent,         // what event handlers receive
  PetEventType,     // "click" | "petting" | "rightClick"
  PetEventHandler,
  PetPlayState,     // animations play() accepts
} from "@petmysite/react";

Limits and privacy

  • Events tell you what happened, never who did it. They include no pointer position, page content or visitor details.
  • The package runs in the visitor's browser. It can't change your PetMySite settings, plan or attribution.
  • Unmounting <PetMySite /> doesn't remove the pet. Use hidden to hide it, and a full page load to switch to a different PetMySite website.
  • If your site sets a Content Security Policy, it must allow cdn.petmysite.com. See troubleshooting.

Questions or ideas for the package? Email support@petmysite.com.