Skip to content

Plugins overview

A plugin adds live, shared behavior to a deck: a poll, audience Q&A, emoji reactions, or anything you can model as shared state. Plugins are ordinary Bun packages; a deck depends on them like any dependency.

A plugin is a definePlugin({ … }) value with four fields:

FieldWhat it is
idA short string. Used to place the plugin (<Plugin id="poll" />) and to namespace its shared state (plugin:poll).
stateA typed schema describing the shared CRDT state (the SDK’s schema/t).
clientReact surfaces: Slide (in-deck), an optional presenter console (a tab in the presenter view), and a fallback (offline/thumbnail). Runs in every browser.
serverOptional host-side logic. Runs once, on the machine presenting, never in the audience’s browsers.
  • @liebstoeckel/plugin-poll: live poll; everyone votes, results update in real time.
  • @liebstoeckel/plugin-qa: audience asks + upvotes questions from any slide (a 💬 chrome button, no Q&A slide required); presenter moderates from the presenter console; the queue re-ranks live.
  • @liebstoeckel/plugin-reactions: ephemeral floating emoji over the deck (rate-limited, self-pruning).

All three are pure-CRDT (no server part) and make good templates.

Register it with <Present> and place it on a slide:

main.tsx
import poll from "@liebstoeckel/plugin-poll";
<Present plugins={[poll]} slides={[…]} />
a slide
import { Plugin } from "@liebstoeckel/engine";
<Plugin id="poll" props={{ question: "Ship it?", options: ["Yes", "Also yes"] }} />

<Plugin> renders the plugin’s Slide when a live server is connected, else its fallback. On touch screens it provides the tap-to-interact breakout automatically.

The id you place must be in <Present plugins={[…]}>. A <Plugin> whose id isn’t registered renders nothing (the build still succeeds) — a blank spot on a slide almost always means a missing entry in plugins. Dev builds log a console warning to flag it.

A package is recognized as a plugin by a marker in its package.json:

{
"keywords": ["liebstoeckel-plugin"],
"liebstoeckel": { "client": "./src/client.tsx", "server": "./src/server.ts" }
}

At build time the deck’s dependencies are scanned for this marker; matching plugins go into a manifest embedded in the deck. Any server entry is bundled (target bun) and base64-encoded into that manifest. It is decoded and run only by a live server, never in the browser.

Comments

liebstoeckel

Code-first presentations your agent can author. One file out, no server.

© 2026 Leon Kaucher