Skip to content
playhtml

playhtml.init() options

Every option below can be passed either to playhtml.init({…}) (vanilla) or to <PlayProvider initOptions={{…}}> (React). The underlying InitOptions interface is the same.

import { playhtml } from "playhtml";

playhtml.init({
  room: "my-room",
  cursors: { enabled: true },
  // …
});

Type: string   Default: window.location.pathname + window.location.search

The room to connect users to — users sharing a room share state. Every room is automatically prefixed with window.location.hostname so your rooms can never collide with another site’s rooms.

If you leave this blank, playhtml derives the room from the URL. Two readers on /docs/capabilities share state; a reader on /docs/concepts is in a different room.

Override it when you want to decouple state from the URL — for example, a site-wide guestbook that should behave the same no matter which page it’s embedded on:

playhtml.init({ room: "global-guestbook" });

Type: string   Default: the public playhtml PartyKit host

Pass your own PartyKit host if you want to self-host the syncing server for extra control, custom server-side logic, or data-residency requirements.

playhtml.init({
  host: "mypartykit.user.partykit.dev",
});

You’re responsible for deploying a compatible PartyKit worker — see the playhtml repo for the current worker implementation.

Type: Record<string, PlayEvent>   Default: undefined

Declare event listeners inline at init time. Handy for events that should always be wired up.

playhtml.init({
  events: {
    confetti: {
      type: "confetti",
      onEvent: () => window.confetti({ particleCount: 100 }),
    },
  },
});

You can also register events imperatively later with playhtml.registerPlayEventListener. See Events.

Type: Record<string, ElementInitializer>   Default: undefined

Ship your own can-* capability alongside the built-ins. Most authors never need this — use can-play on individual elements first. Reach for extraCapabilities when you’re packaging a capability you want to reuse across many elements and want the shorter can-mything attribute form.

playhtml.init({
  extraCapabilities: {
    "can-pulse": {
      defaultData: { on: false },
      updateElement: ({ element, data }) => {
        element.classList.toggle("pulsing", data.on);
      },
      onClick: (_e, { data, setData }) => setData({ on: !data.on }),
    },
  },
});

Type: DefaultRoomOptions   Default: undefined

Configuration for the auto-derived URL-based room. Most apps don’t need to touch this.

Type: () => void   Default: undefined

Callback for connection failures. Handy for showing an error UI or logging to your own monitoring system.

playhtml.init({
  onError: () => {
    document.body.classList.add("playhtml-offline");
    console.warn("playhtml could not connect");
  },
});

Type: boolean   Default: false

Enable the in-page devtools panel. Shows element inspector, live data tree, connection status, and tag-type badges — modeled after RollerCoaster Tycoon’s inspect UI. Useful while debugging.

playhtml.init({ developmentMode: true });

Type: CursorOptions   Default: undefined (cursors disabled)

Opt into multiplayer cursors, presence identity, chat, and proximity detection. The full set of cursor options is documented on the Cursors page; this is the top-level shape:

interface CursorOptions {
  enabled: boolean;
  room?: "page" | "domain" | "section" | ((ctx) => string);
  container?:
    | HTMLElement
    | string
    | (() => HTMLElement | null)
    | React.RefObject<HTMLElement>;
  shouldRenderCursor?: (presence) => boolean;
  getCursorStyle?: (presence) => Partial<CSSStyleDeclaration>;
  playerIdentity?: PlayerIdentity;
  proximityThreshold?: number;
  onProximityEntered?: (identity, positions, angle) => void;
  onProximityLeft?: (connectionId) => void;
  visibilityThreshold?: number;
  enableChat?: boolean;
  onCustomCursorRender?: (connectionId, element) => Element | null;
}

container is only needed when your framework swaps document.body on navigation (Astro ViewTransitions, htmx boost, Turbo). Mark the container with the framework’s persist directive (e.g. transition:persist) and cursors survive navigation. See navigation for examples.

Minimal opt-in:

playhtml.init({ cursors: { enabled: true } });

Domain-wide presence with page-specific cursors (common pattern):

playhtml.init({
  cursors: {
    enabled: true,
    room: "domain",
    shouldRenderCursor: (p) => p.page === window.location.pathname,
  },
});

See Cursors for the full config reference and recipes.

Every option on this page is passed through initOptions on <PlayProvider>.

import { PlayProvider } from "@playhtml/react";

<PlayProvider
  initOptions={{
    room: "my-room",
    cursors: { enabled: true },
    developmentMode: true,
  }}
>
  {/* your app */}
</PlayProvider>;

No React-specific options on the provider itself — all config flows through the shared InitOptions shape.