Creating a Liveblocks example
Ground rules
- Examples live in
examples/ and are NOT part of the pnpm workspace. They
depend on the latest published Liveblocks packages, use plain npm
(commit package-lock.json), and cannot be tested against local package
source.
- Start by copying the closest existing example and adapting it. Good bases:
nextjs-ai-slideshow — AI chat + document layout, Tailwind v4 + shadcn
UI kit, AI Elements components, database.ts, help button.
nextjs-ai-elements-realtime — multiplayer AI chat on Feeds with
streamed replies written server-side via @liveblocks/node.
nextjs-comments-notifications — gallery param helpers
(example.ts / example.client.ts).
nextjs-comments-ai — AI replies to comments (webhook + workflow).
- Verify features against the published package (e.g. check typings on
unpkg), not the local monorepo source — they can differ. When an example
relies on an experimental release (e.g.
3.23.0-exp2), pin @liveblocks/*
to that exact version; use caret ranges (^3.21.0) for stable releases.
Every @liveblocks/* package must use the same version.
Required boilerplate (gallery conventions)
Every example needs these, copied and adapted from an existing example:
- Room id:
liveblocks:examples:<example-name> (append :<subid> for
multi-room examples). The auth route allows
session.allow("liveblocks:examples:*", ["*:write"]).
- Gallery URL params (used when the example is embedded on liveblocks.io;
harmless locally):
exampleId — isolates a gallery session: append -${exampleId} to the
room id (and to user ids where user identity must be isolated).
examplePreview — a number identifying the preview pane; use it to pick
a distinct demo user per pane: users[examplePreview % users.length].
- Use the
useExampleRoomId hook pattern (or example.ts +
example.client.ts from nextjs-comments-notifications when you also
need examplePreview), with the standard "used when deploying an example
on liveblocks.io, ignore locally" comment.
app/database.ts (as in nextjs-ai-slideshow): a hardcoded mock user
database typed as Liveblocks["UserMeta"][], avatars from
https://liveblocks.io/avatars/avatar-N.png, getUser/getUsers/
getRandomUser helpers. For AI examples add AI_USER_ID = "ai-assistant"
with the https://liveblocks.io/api/avatar?u=ai-assistant&agent=true
avatar.
- Auth:
/api/liveblocks-auth route using the secret key. The client
picks a demo user (random, or via examplePreview) and POSTs
{ room, userId } to the route via an authEndpoint function — there is
no exampleUser param; user identity always flows through this userId.
- Providers (see
nextjs-ai-slideshow/app/providers.tsx):
throttle={16}
authEndpoint as described above
resolveUsers → /api/users?userIds=...
resolveMentionSuggestions → /api/users/search?text=...
- both API routes backed by
database.ts
- Help button (as in
nextjs-ai-slideshow/components/help-button.tsx): a
small ? button opening a dialog with the example name (linking to its
gallery page https://liveblocks.io/examples/<slug>/<example-name>) and
3-4 short feature descriptions explaining what the example does and how to
try the multiplayer behavior (e.g. "open in two tabs").
liveblocks.config.ts: declare all types globally (UserMeta,
Presence, Storage, FeedMessageData, …) with brief comments.
.env.example: LIVEBLOCKS_SECRET_KEY (with dashboard link) plus any
optional keys (e.g. AI_GATEWAY_API_KEY) documented with their fallback
behavior.
README.md: copy the house template — Liveblocks header SVGs, badges,
one-paragraph description with docs links,
npx create-liveblocks-app@latest --example <name> --api-key, collapsed
"Manual setup" / "Deploy on Vercel" / "CodeSandbox" sections.
vercel.json, next.config.ts (with the monorepo-root turbopack
setting), package.json name (@liveblocks-examples/<name>) and
description — copy from the base example.
Styling
- Tailwind v4 (
@tailwindcss/postcss) + shadcn-style components. Style after
nextjs-ai-slideshow unless told otherwise: h-dvh shell on
bg-neutral-50, rounded white panels (rounded-lg bg-white shadow ring-1 ring-neutral-950/5), a fixed-width (~380px) right panel for chat-style
UIs, lucide-react icons, AvatarStack in the header.
- Tailwind v4 gates
hover: behind @media (hover: hover); if hover-only
controls must also work with touch/synthetic pointers, add
@custom-variant hover (&:hover); in globals.css.
- Tailwind preflight resets typography — rich-text editors need explicit CSS
for headings, lists, quotes, and code in
globals.css.
AI examples
- Always ship a keyless mock fallback: without
AI_GATEWAY_API_KEY, stream a
canned reply through the same code path (e.g. updateFeedMessage loop) and
still perform any real side effects (document edits, reactions) so the
whole loop works with only a Liveblocks key.
- Stream AI replies server-side with
@liveblocks/node
(createFeedMessage + repeated updateFeedMessage); clients see them live
via useFeedMessages. Throttle updates (~100ms) while streaming.
- Give models Markdown interfaces (in prompts and tool inputs), not raw
ProseMirror/JSON. Convert server-side (
marked + generateJSON from
@tiptap/html, or markdownToCommentBody from @liveblocks/node).
- When the AI edits shared state, prefer targeted, merge-friendly writes over
whole-document replaces (e.g.
mutateStorage with ops scoped to the blocks
being changed) so concurrent human edits survive.
- For examples with comments, add AI comment replies like
nextjs-comments-ai: a commentCreated webhook
(LIVEBLOCKS_WEBHOOK_SECRET_KEY) starts a Workflow SDK workflow that
replies only when the AI user is @-mentioned — it leaves a 👀 reaction,
shows AI presence, creates a placeholder comment plus a feed, and streams
the reply into the feed.
Local testing (keep it light)
- Do NOT create screen recordings or screenshots, and skip browser-driven
manual testing. Verify with
npx tsc --noEmit, a running dev server, and
terminal-level checks (curl against API routes, small scripts). Deep
end-to-end testing is not expected for examples.
- To run against the local dev server (
pnpm dlx liveblocks dev --port 1153), put in .env.local: LIVEBLOCKS_SECRET_KEY=sk_localdev and, if
the example supports it, LIVEBLOCKS_BASE_URL /
NEXT_PUBLIC_LIVEBLOCKS_BASE_URL set to http://localhost:1153. Passing
baseUrl through LiveblocksProvider and new Liveblocks({...}) from
these env vars is acceptable in committed code (with a comment) — it's
inert in production.
- Known dev-server limitations (don't mistake these for app bugs):
- REST feed endpoints and client thread endpoints are stubs — server-side
createFeedMessage/updateFeedMessage no-op and comments don't
persist. Websocket-driven client features work.
- Version history endpoints are unimplemented.
- Rooms must exist before
mutateStorage (404 otherwise).
- Storage is in-memory: everything resets on dev-server restart.
- Newer experimental storage types may be rejected by its decoders
(silently, as 4xx on
send-message) — check the dev-server log.
- Features that only exist on the production backend need a cloud key to
verify; say so in your report instead of building elaborate workarounds.
Thumbnails, social cards, and publishing PRs
Every example needs two Figma images, created after the example code is
complete, then used in two PRs. IMPORTANT: always pause after creating the
images — link both Figma nodes to the human and wait for approval (they
usually make final edits by hand). Only export and open PRs once approved.
The images are made with the official Figma MCP server. If it isn't
available in your environment, let the user know they can set it up if they
want help with the images — this step is optional (you can skip it and they
can make the images themselves), but doing it via the MCP is preferred.
1. Gallery thumbnail (Figma)
- File: Examples thumbnails,
page "🆗 Version 3.0" (node
801:8644).
- Layout: one master component per example (1072×714, named by the example
slug, no
nextjs- prefix), plus "Website thumbnails" and "Github
thumbnails" columns holding plain instances of the masters.
- Workflow: clone the newest master (bottom of the masters column), rename it
to the new slug, place it one row below (row spacing 825), rework the
content to depict the new example, then
createInstance() twice and place
the instances in the two instance columns at the same row offset.
- Match the style vocabulary, don't invent: placeholder pills are black at
10% opacity with cornerRadius 16 (toolbar icons 4); panels are no-fill with
a 1px black-8% stroke, radius 8; the purple accent is
rgb(144, 99, 246);
the white app card and drop shadow come free with the clone. Placeholder
paragraph lines should wrap naturally (full lines first, short line last).
2. Social card (Figma)
- File: Social Images Thumbnails
(page node
1343:2040).
- Cards are 1200×630 frames named
examples/<slug> in a column at x=9100,
row spacing 730. Clone the newest one, rename, place below.
- The big title is a
heading TEXT component property on the
templates/examples instance; small mock UI text is regular text nodes.
Figma font gotcha
The brand font (Suisse Intl) is licensed and unavailable in the Figma MCP
plugin environment: loadFontAsync fails, so characters edits and
setProperties on text properties throw. Workaround: create replacement
text nodes in Inter Medium, copying fontSize / lineHeight / letterSpacing /
fills / position from the original, then remove the original (or hide it and
overlay, for text inside instances, e.g. the social card title). Tell the
human which nodes are Inter so they can flip the family back to Suisse Intl.
3. After approval: example README image (liveblocks/liveblocks)
Export the thumbnail component at 1x (1072×714) as PNG and commit it to
.github/assets/examples/<slug>.png on the example PR branch.
Embed it in the example README below the badges block:
<img src="https://raw.githubusercontent.com/liveblocks/liveblocks/main/.github/assets/examples/<slug>.png" width="536" alt="<Example title>" />
4. After approval: website PR (liveblocks/liveblocks.io)
Model it on liveblocks.io#3307
— three files, one short PR body linking the example PR:
public/images/examples/thumbnails/<slug>.jpg — thumbnail export,
1072×714 JPG.
public/images/social-images/examples/<slug>.png — social card export,
1200×630 PNG.
src/constants/examples.ts — add an EXAMPLES_INDEX entry next to
similar examples: title, slug, categories / products /
useCaseTechnologies / documentStorage enums, featured, image and
socialImage set to the two paths above, and a technologies array with
the example directory, previewUrl
(https://<directory>.liveblocks.app), preview options, defaultFile,
environmentVariables (secret key) and extras like AI_GATEWAY_API_KEY
in additionalEnvironmentVariables.
5. When everything is done: tell the human their next steps
Once the example, images, and both PRs are complete, finish by telling the
human what they need to do to deploy (these are manual steps for them, not
for the agent):
- Merge the example PR (liveblocks/liveblocks).
- Merge
main into the examples branch.
- Run the Set up Vercel example
workflow with the example's folder name (e.g.
nextjs-tiptap-ai-chat) —
this creates the https://<folder-name>.liveblocks.app deployment that
the website PR's previewUrl points at.
Publishing notes
- Gallery registration happens in the liveblocks.io repo (see above); the
example folder + house-format README is all that's needed in this repo.
- Examples are downloaded verbatim by
create-liveblocks-app — don't commit
scratch scripts, .env.local, or test artifacts into the example folder.
1---2name: create-example3description: Create a new Liveblocks example app for the gallery under examples/. Use when asked to build a new example, demo, or showcase app, or when restructuring an existing example. Covers scaffolding, gallery conventions (exampleId, examplePreview, database.ts, HelpButton), providers configuration, AI patterns, styling, local testing against the dev server, and READMEs.4---56# Creating a Liveblocks example78## Ground rules910- Examples live in `examples/` and are NOT part of the pnpm workspace. They11 depend on the latest _published_ Liveblocks packages, use plain `npm`12 (commit `package-lock.json`), and cannot be tested against local package13 source.14- Start by copying the closest existing example and adapting it. Good bases:15 - `nextjs-ai-slideshow` — AI chat + document layout, Tailwind v4 + shadcn16 UI kit, AI Elements components, `database.ts`, help button.17 - `nextjs-ai-elements-realtime` — multiplayer AI chat on Feeds with18 streamed replies written server-side via `@liveblocks/node`.19 - `nextjs-comments-notifications` — gallery param helpers20 (`example.ts` / `example.client.ts`).21 - `nextjs-comments-ai` — AI replies to comments (webhook + workflow).22- Verify features against the _published_ package (e.g. check typings on23 unpkg), not the local monorepo source — they can differ. When an example24 relies on an experimental release (e.g. `3.23.0-exp2`), pin `@liveblocks/*`25 to that exact version; use caret ranges (`^3.21.0`) for stable releases.26 Every `@liveblocks/*` package must use the same version.2728## Required boilerplate (gallery conventions)2930Every example needs these, copied and adapted from an existing example:3132- **Room id**: `liveblocks:examples:<example-name>` (append `:<subid>` for33 multi-room examples). The auth route allows34 `session.allow("liveblocks:examples:*", ["*:write"])`.35- **Gallery URL params** (used when the example is embedded on liveblocks.io;36 harmless locally):37 - `exampleId` — isolates a gallery session: append `-${exampleId}` to the38 room id (and to user ids where user identity must be isolated).39 - `examplePreview` — a number identifying the preview pane; use it to pick40 a distinct demo user per pane: `users[examplePreview % users.length]`.41 - Use the `useExampleRoomId` hook pattern (or `example.ts` +42 `example.client.ts` from `nextjs-comments-notifications` when you also43 need `examplePreview`), with the standard "used when deploying an example44 on liveblocks.io, ignore locally" comment.45- **`app/database.ts`** (as in `nextjs-ai-slideshow`): a hardcoded mock user46 database typed as `Liveblocks["UserMeta"][]`, avatars from47 `https://liveblocks.io/avatars/avatar-N.png`, `getUser`/`getUsers`/48 `getRandomUser` helpers. For AI examples add `AI_USER_ID = "ai-assistant"`49 with the `https://liveblocks.io/api/avatar?u=ai-assistant&agent=true`50 avatar.51- **Auth**: `/api/liveblocks-auth` route using the secret key. The client52 picks a demo user (random, or via `examplePreview`) and POSTs53 `{ room, userId }` to the route via an `authEndpoint` function — there is54 no `exampleUser` param; user identity always flows through this `userId`.55- **Providers** (see `nextjs-ai-slideshow/app/providers.tsx`):56 - `throttle={16}`57 - `authEndpoint` as described above58 - `resolveUsers` → `/api/users?userIds=...`59 - `resolveMentionSuggestions` → `/api/users/search?text=...`60 - both API routes backed by `database.ts`61- **Help button** (as in `nextjs-ai-slideshow/components/help-button.tsx`): a62 small `?` button opening a dialog with the example name (linking to its63 gallery page `https://liveblocks.io/examples/<slug>/<example-name>`) and64 3-4 short feature descriptions explaining what the example does and how to65 try the multiplayer behavior (e.g. "open in two tabs").66- **`liveblocks.config.ts`**: declare all types globally (`UserMeta`,67 `Presence`, `Storage`, `FeedMessageData`, …) with brief comments.68- **`.env.example`**: `LIVEBLOCKS_SECRET_KEY` (with dashboard link) plus any69 optional keys (e.g. `AI_GATEWAY_API_KEY`) documented with their fallback70 behavior.71- **`README.md`**: copy the house template — Liveblocks header SVGs, badges,72 one-paragraph description with docs links,73 `npx create-liveblocks-app@latest --example <name> --api-key`, collapsed74 "Manual setup" / "Deploy on Vercel" / "CodeSandbox" sections.75- **`vercel.json`**, **`next.config.ts`** (with the monorepo-root turbopack76 setting), **`package.json`** `name` (`@liveblocks-examples/<name>`) and77 `description` — copy from the base example.7879## Styling8081- Tailwind v4 (`@tailwindcss/postcss`) + shadcn-style components. Style after82 `nextjs-ai-slideshow` unless told otherwise: `h-dvh` shell on83 `bg-neutral-50`, rounded white panels (`rounded-lg bg-white shadow ring-184 ring-neutral-950/5`), a fixed-width (~380px) right panel for chat-style85 UIs, `lucide-react` icons, `AvatarStack` in the header.86- Tailwind v4 gates `hover:` behind `@media (hover: hover)`; if hover-only87 controls must also work with touch/synthetic pointers, add88 `@custom-variant hover (&:hover);` in `globals.css`.89- Tailwind preflight resets typography — rich-text editors need explicit CSS90 for headings, lists, quotes, and code in `globals.css`.9192## AI examples9394- Always ship a keyless mock fallback: without `AI_GATEWAY_API_KEY`, stream a95 canned reply through the same code path (e.g. `updateFeedMessage` loop) and96 still perform any real side effects (document edits, reactions) so the97 whole loop works with only a Liveblocks key.98- Stream AI replies server-side with `@liveblocks/node`99 (`createFeedMessage` + repeated `updateFeedMessage`); clients see them live100 via `useFeedMessages`. Throttle updates (~100ms) while streaming.101- Give models Markdown interfaces (in prompts and tool inputs), not raw102 ProseMirror/JSON. Convert server-side (`marked` + `generateJSON` from103 `@tiptap/html`, or `markdownToCommentBody` from `@liveblocks/node`).104- When the AI edits shared state, prefer targeted, merge-friendly writes over105 whole-document replaces (e.g. `mutateStorage` with ops scoped to the blocks106 being changed) so concurrent human edits survive.107- For examples with comments, add AI comment replies like108 `nextjs-comments-ai`: a `commentCreated` webhook109 (`LIVEBLOCKS_WEBHOOK_SECRET_KEY`) starts a Workflow SDK workflow that110 replies only when the AI user is @-mentioned — it leaves a 👀 reaction,111 shows AI presence, creates a placeholder comment plus a feed, and streams112 the reply into the feed.113114## Local testing (keep it light)115116- Do NOT create screen recordings or screenshots, and skip browser-driven117 manual testing. Verify with `npx tsc --noEmit`, a running dev server, and118 terminal-level checks (curl against API routes, small scripts). Deep119 end-to-end testing is not expected for examples.120- To run against the local dev server (`pnpm dlx liveblocks dev --port121 1153`), put in `.env.local`: `LIVEBLOCKS_SECRET_KEY=sk_localdev` and, if122 the example supports it, `LIVEBLOCKS_BASE_URL` /123 `NEXT_PUBLIC_LIVEBLOCKS_BASE_URL` set to `http://localhost:1153`. Passing124 `baseUrl` through `LiveblocksProvider` and `new Liveblocks({...})` from125 these env vars is acceptable in committed code (with a comment) — it's126 inert in production.127- Known dev-server limitations (don't mistake these for app bugs):128 - REST feed endpoints and client thread endpoints are stubs — server-side129 `createFeedMessage`/`updateFeedMessage` no-op and comments don't130 persist. Websocket-driven client features work.131 - Version history endpoints are unimplemented.132 - Rooms must exist before `mutateStorage` (404 otherwise).133 - Storage is in-memory: everything resets on dev-server restart.134 - Newer experimental storage types may be rejected by its decoders135 (silently, as 4xx on `send-message`) — check the dev-server log.136 - Features that only exist on the production backend need a cloud key to137 verify; say so in your report instead of building elaborate workarounds.138139## Thumbnails, social cards, and publishing PRs140141Every example needs two Figma images, created after the example code is142complete, then used in two PRs. IMPORTANT: always pause after creating the143images — link both Figma nodes to the human and wait for approval (they144usually make final edits by hand). Only export and open PRs once approved.145146The images are made with the official Figma MCP server. If it isn't147available in your environment, let the user know they can set it up if they148want help with the images — this step is optional (you can skip it and they149can make the images themselves), but doing it via the MCP is preferred.150151### 1. Gallery thumbnail (Figma)152153- File: [Examples thumbnails](https://www.figma.com/design/Qcr7GAO1zTm6lAdYrFsMRx/Examples-thumbnails),154 page "🆗 Version 3.0" (node `801:8644`).155- Layout: one master component per example (1072×714, named by the example156 slug, no `nextjs-` prefix), plus "Website thumbnails" and "Github157 thumbnails" columns holding plain instances of the masters.158- Workflow: clone the newest master (bottom of the masters column), rename it159 to the new slug, place it one row below (row spacing 825), rework the160 content to depict the new example, then `createInstance()` twice and place161 the instances in the two instance columns at the same row offset.162- Match the style vocabulary, don't invent: placeholder pills are black at163 10% opacity with cornerRadius 16 (toolbar icons 4); panels are no-fill with164 a 1px black-8% stroke, radius 8; the purple accent is `rgb(144, 99, 246)`;165 the white app card and drop shadow come free with the clone. Placeholder166 paragraph lines should wrap naturally (full lines first, short line last).167168### 2. Social card (Figma)169170- File: [Social Images Thumbnails](https://www.figma.com/design/rslApB7BFH57mPfa2k6xUV/Social-Images-Thumbnails)171 (page node `1343:2040`).172- Cards are 1200×630 frames named `examples/<slug>` in a column at x=9100,173 row spacing 730. Clone the newest one, rename, place below.174- The big title is a `heading` TEXT component property on the175 `templates/examples` instance; small mock UI text is regular text nodes.176177### Figma font gotcha178179The brand font (Suisse Intl) is licensed and unavailable in the Figma MCP180plugin environment: `loadFontAsync` fails, so `characters` edits and181`setProperties` on text properties throw. Workaround: create replacement182text nodes in Inter Medium, copying fontSize / lineHeight / letterSpacing /183fills / position from the original, then remove the original (or hide it and184overlay, for text inside instances, e.g. the social card title). Tell the185human which nodes are Inter so they can flip the family back to Suisse Intl.186187### 3. After approval: example README image (liveblocks/liveblocks)188189- Export the thumbnail component at 1x (1072×714) as PNG and commit it to190 `.github/assets/examples/<slug>.png` on the example PR branch.191- Embed it in the example README below the badges block:192193 ```html194 <img src="https://raw.githubusercontent.com/liveblocks/liveblocks/main/.github/assets/examples/<slug>.png" width="536" alt="<Example title>" />195 ```196197### 4. After approval: website PR (liveblocks/liveblocks.io)198199Model it on [liveblocks.io#3307](https://github.com/liveblocks/liveblocks.io/pull/3307)200— three files, one short PR body linking the example PR:201202- `public/images/examples/thumbnails/<slug>.jpg` — thumbnail export,203 1072×714 JPG.204- `public/images/social-images/examples/<slug>.png` — social card export,205 1200×630 PNG.206- `src/constants/examples.ts` — add an `EXAMPLES_INDEX` entry next to207 similar examples: title, slug, `categories` / `products` /208 `useCaseTechnologies` / `documentStorage` enums, `featured`, `image` and209 `socialImage` set to the two paths above, and a `technologies` array with210 the example `directory`, `previewUrl`211 (`https://<directory>.liveblocks.app`), preview options, `defaultFile`,212 `environmentVariables` (secret key) and extras like `AI_GATEWAY_API_KEY`213 in `additionalEnvironmentVariables`.214215### 5. When everything is done: tell the human their next steps216217Once the example, images, and both PRs are complete, finish by telling the218human what they need to do to deploy (these are manual steps for them, not219for the agent):2202211. Merge the example PR (liveblocks/liveblocks).2222. Merge `main` into the `examples` branch.2233. Run the [Set up Vercel example](https://github.com/liveblocks/liveblocks/actions/workflows/setup-vercel-example.yml)224 workflow with the example's folder name (e.g. `nextjs-tiptap-ai-chat`) —225 this creates the `https://<folder-name>.liveblocks.app` deployment that226 the website PR's `previewUrl` points at.227228## Publishing notes229230- Gallery registration happens in the liveblocks.io repo (see above); the231 example folder + house-format README is all that's needed in this repo.232- Examples are downloaded verbatim by `create-liveblocks-app` — don't commit233 scratch scripts, `.env.local`, or test artifacts into the example folder.