Rivet Actors
Build high-performance, long-lived, in-memory processes on Rivet's actor runtime. Use this skill whenever HTTP endpoints, databases, or queues no longer meet the requirements for AI agents, realtime collaboration, workflow orchestration, or geo-distributed data.
MCP Server (Recommended)
For the best experience building with Rivet, install the Rivet MCP server. This gives you access to live documentation search (docs.search , docs.get ) so every response is grounded in official Rivet docs.
Prompt the user:
Would you like to install the Rivet MCP server for enhanced documentation access? See rivet.gg/docs/general/mcp for setup instructions.
Once installed, always call docs.search and docs.get before answering Rivet-related questions to ensure responses cite the latest official documentation.
First Steps
-
Install RivetKit npm install rivetkit
-
Define a registry with setup({ use: { /* actors */ } }) .
-
Expose registry.serve() or registry.handler() (serverless) or registry.startRunner() (runner mode).
-
Verify /api/rivet/metadata returns 200 before deploying.
-
Configure Rivet Cloud or self-hosted engine (registry token, project, region, metadata endpoint).
-
Integrate clients
Minimal Project
Backend
actors.ts
import { actor, setup } from "rivetkit";
const counter = actor({ state: { count: 0 }, actions: { increment: (c, amount: number) => { c.state.count += amount; c.broadcast("count", c.state.count); return c.state.count; }, }, });
export const registry = setup({ use: { counter }, });
server.ts
Integrate with the user's existing server if applicable. Otherwise, default to Hono.
No Framework
import { actor, setup } from "rivetkit";
const counter = actor({ state: { count: 0 }, actions: { increment: (c, amount: number) => c.state.count += amount } });
const registry = setup({ use: { counter } });
// Exposes Rivet API on /api/rivet/ to communicate with actors export default registry.serve();
Hono
import { Hono } from "hono"; import { actor, setup } from "rivetkit"; import { createClient } from "rivetkit/client";
const counter = actor({ state: { count: 0 }, actions: { increment: (c, amount: number) => c.state.count += amount } });
const registry = setup({ use: { counter } });
// Build client to communicate with actors (optional) const client = createClient<typeof registry>();
const app = new Hono();
// Exposes Rivet API to communicate with actors app.all("/api/rivet/*", (c) => registry.handler(c.req.raw));
export default app;
Elysia
import { Elysia } from "elysia"; import { actor, setup } from "rivetkit"; import { createClient } from "rivetkit/client";
const counter = actor({ state: { count: 0 }, actions: { increment: (c, amount: number) => c.state.count += amount } });
const registry = setup({ use: { counter } });
// Build client to communicate with actors (optional) const client = createClient<typeof registry>();
const app = new Elysia() // Exposes Rivet API to communicate with actors .all("/api/rivet/*", (c) => registry.handler(c.request));
export default app;
Minimal Client
import { actor, setup } from "rivetkit"; import { createClient } from "rivetkit/client";
const counter = actor({ state: { count: 0 }, actions: { increment: (c, amount: number) => c.state.count += amount } });
const registry = setup({ use: { counter } }); const client = createClient<typeof registry>(); const counterHandle = client.counter.getOrCreate(["my-counter"]); await counterHandle.increment(1);
See the client quick reference for more details.
Actor Quick Reference
State
Persistent data that survives restarts, crashes, and deployments. State is persisted on Rivet Cloud or Rivet self-hosted, so it survives restarts if the current process crashes or exits.
Static Initial State
import { actor } from "rivetkit";
const counter = actor({ state: { count: 0 }, actions: { increment: (c) => c.state.count += 1, }, });
Dynamic Initial State
import { actor } from "rivetkit";
interface CounterState { count: number; }
const counter = actor({ state: { count: 0 } as CounterState, createState: (c, input: { start?: number }): CounterState => ({ count: input.start ?? 0, }), actions: { increment: (c) => c.state.count += 1, }, });
Documentation
Input
Pass initialization data when creating actors.
import { actor, setup } from "rivetkit"; import { createClient } from "rivetkit/client";
const game = actor({ createState: (c, input: { mode: string }) => ({ mode: input.mode }), actions: {}, });
const registry = setup({ use: { game } }); const client = createClient<typeof registry>();
// Client usage const gameHandle = client.game.getOrCreate(["game-1"], { createWithInput: { mode: "ranked" } });
Documentation
Temporary Variables
Temporary data that doesn't survive restarts. Use for non-serializable objects (event emitters, connections, etc).
Static Initial Vars
import { actor } from "rivetkit";
const counter = actor({ state: { count: 0 }, vars: { lastAccess: 0 }, actions: { increment: (c) => { c.vars.lastAccess = Date.now(); return c.state.count += 1; }, }, });
Dynamic Initial Vars
import { actor } from "rivetkit";
const counter = actor({ state: { count: 0 }, createVars: () => ({ emitter: new EventTarget(), }), actions: { increment: (c) => { c.vars.emitter.dispatchEvent(new Event("change")); return c.state.count += 1; }, }, });
Documentation
Actions
Actions are the primary way clients and other actors communicate with an actor.
import { actor } from "rivetkit";
const counter = actor({ state: { count: 0 }, actions: { increment: (c, amount: number) => (c.state.count += amount), getCount: (c) => c.state.count, }, });
Documentation
Events & Broadcasts
Events enable real-time communication from actors to connected clients.
import { actor } from "rivetkit";
const chatRoom = actor({ state: { messages: [] as string[] }, actions: { sendMessage: (c, text: string) => { // Broadcast to ALL connected clients c.broadcast("newMessage", { text }); }, }, });
Documentation
Connections
Access all connected clients via c.conns . Each connection has state defined by connState or createConnState .
import { actor } from "rivetkit";
interface ConnState { userId: string; }
const chatRoom = actor({ state: {}, connState: { userId: "" } as ConnState, createConnState: (c, params: { userId: string }): ConnState => ({ userId: params.userId }), actions: { // Send to a specific connection sendPrivate: (c, targetUserId: string, text: string) => { for (const conn of c.conns.values()) { if (conn.state.userId === targetUserId) { conn.send("privateMessage", { text }); break; } } }, // Send to all except current connection notifyOthers: (c, text: string) => { for (const conn of c.conns.values()) { if (conn !== c.conn) conn.send("notification", { text }); } }, // Disconnect a client kickUser: (c, userId: string) => { for (const conn of c.conns.values()) { if (conn.state.userId === userId) { conn.disconnect("Kicked by admin"); break; } } }, }, });
Documentation
Actor-to-Actor Communication
Actors can call other actors using c.client() .
import { actor, setup } from "rivetkit";
const inventory = actor({ state: { stock: 100 }, actions: { reserve: (c, amount: number) => { c.state.stock -= amount; } } });
const order = actor({ state: {}, actions: { process: async (c) => { const client = c.client<typeof registry>(); await client.inventory.getOrCreate(["main"]).reserve(1); }, }, });
const registry = setup({ use: { inventory, order } });
Documentation
Scheduling
Schedule actions to run after a delay or at a specific time. Schedules persist across restarts, upgrades, and crashes.
import { actor } from "rivetkit";
const reminder = actor({ state: { message: "" }, actions: { // Schedule action to run after delay (ms) setReminder: (c, message: string, delayMs: number) => { c.state.message = message; c.schedule.after(delayMs, "sendReminder"); }, // Schedule action to run at specific timestamp setReminderAt: (c, message: string, timestamp: number) => { c.state.message = message; c.schedule.at(timestamp, "sendReminder"); }, sendReminder: (c) => { c.broadcast("reminder", { message: c.state.message }); }, }, });
Documentation
Destroying Actors
Permanently delete an actor and its state using c.destroy() .
import { actor } from "rivetkit";
const userAccount = actor({
state: { email: "", name: "" },
onDestroy: (c) => {
console.log(Account ${c.state.email} deleted);
},
actions: {
deleteAccount: (c) => {
c.destroy();
},
},
});
Documentation
Lifecycle Hooks
Actors support hooks for initialization, connections, networking, and state changes.
import { actor } from "rivetkit";
interface RoomState { users: Record<string, boolean>; name?: string; }
interface RoomInput { roomName: string; }
interface ConnState { userId: string; joinedAt: number; }
const chatRoom = actor({ state: { users: {} } as RoomState, vars: { startTime: 0 }, connState: { userId: "", joinedAt: 0 } as ConnState,
// State & vars initialization createState: (c, input: RoomInput): RoomState => ({ users: {}, name: input.roomName }), createVars: () => ({ startTime: Date.now() }),
// Actor lifecycle onCreate: (c) => console.log("created", c.key), onDestroy: (c) => console.log("destroyed"), onWake: (c) => console.log("actor started"), onSleep: (c) => console.log("actor sleeping"), onStateChange: (c, newState) => c.broadcast("stateChanged", newState),
// Connection lifecycle createConnState: (c, params): ConnState => ({ userId: (params as { userId: string }).userId, joinedAt: Date.now() }), onBeforeConnect: (c, params) => { /* validate auth */ }, onConnect: (c, conn) => console.log("connected:", conn.state.userId), onDisconnect: (c, conn) => console.log("disconnected:", conn.state.userId),
// Networking onRequest: (c, req) => new Response(JSON.stringify(c.state)), onWebSocket: (c, socket) => socket.addEventListener("message", console.log),
// Response transformation onBeforeActionResponse: <Out>(c: unknown, name: string, args: unknown[], output: Out): Out => output,
actions: {}, });
Documentation
JavaScript Client Quick Reference
Stateless vs Stateful
import { actor, setup } from "rivetkit"; import { createClient } from "rivetkit/client";
const counter = actor({ state: { count: 0 }, actions: { increment: (c, amount: number) => { c.state.count += amount; c.broadcast("count", c.state.count); return c.state.count; } } });
const registry = setup({ use: { counter } }); const client = createClient<typeof registry>(); const counterHandle = client.counter.getOrCreate(["my-counter"]);
// Stateless: each call is independent, no persistent connection await counterHandle.increment(1);
// Stateful: persistent connection for realtime events const conn = counterHandle.connect(); conn.on("count", (value: number) => console.log(value)); await conn.increment(1);
Documentation
Getting Actors
import { actor, setup } from "rivetkit"; import { createClient } from "rivetkit/client";
const chatRoom = actor({ state: { messages: [] as string[] }, actions: {} });
const game = actor({ state: { mode: "" }, createState: (c, input: { mode: string }) => ({ mode: input.mode }), actions: {} });
const registry = setup({ use: { chatRoom, game } }); const client = createClient<typeof registry>();
// Get or create by key const room = client.chatRoom.getOrCreate(["room-42"]);
// Get existing (returns null if not found) const existing = client.chatRoom.get(["room-42"]);
// Create with input const gameHandle = client.game.create(["game-1"], { input: { mode: "ranked" } });
Documentation
Subscribing to Events
import { actor, setup } from "rivetkit"; import { createClient } from "rivetkit/client";
const chatRoom = actor({ state: { messages: [] as string[] }, actions: {} });
const registry = setup({ use: { chatRoom } }); const client = createClient<typeof registry>();
const conn = client.chatRoom.getOrCreate(["general"]).connect(); conn.on("message", (msg: string) => console.log(msg)); conn.once("gameOver", () => console.log("done"));
Documentation
Calling from Backend
Call actors from your server-side code.
import { Hono } from "hono"; import { actor, setup } from "rivetkit"; import { createClient } from "rivetkit/client";
const counter = actor({ state: { count: 0 }, actions: { increment: (c, amount: number) => { c.state.count += amount; return c.state.count; } } });
const registry = setup({ use: { counter } }); const client = createClient<typeof registry>(); const app = new Hono();
app.post("/increment/:name", async (c) => { const counterHandle = client.counter.getOrCreate([c.req.param("name")]); const newCount = await counterHandle.increment(1); return c.json({ count: newCount }); });
Documentation
React Quick Reference
Setup
import { actor, setup } from "rivetkit";
export const counter = actor({ state: { count: 0 }, actions: { increment: (c, amount: number) => { c.state.count += amount; c.broadcast("count", c.state.count); return c.state.count; } } });
export const registry = setup({ use: { counter } });
import { createRivetKit } from "@rivetkit/react"; import type { registry } from "./registry";
const { useActor } = createRivetKit<typeof registry>();
useActor & Calling Actions
import { actor, setup } from "rivetkit";
export const counter = actor({ state: { count: 0 }, actions: { increment: (c, amount: number) => { c.state.count += amount; return c.state.count; } } });
export const registry = setup({ use: { counter } });
import { createRivetKit } from "@rivetkit/react"; import type { registry } from "./registry";
const { useActor } = createRivetKit<typeof registry>();
function Counter() { const counter = useActor({ name: "counter", key: ["my-counter"] });
const handleClick = async () => { await counter.connection?.increment(1); };
return <button onClick={handleClick}>+</button>; }
Subscribing to Events
import { actor, setup } from "rivetkit";
export const chatRoom = actor({ state: { messages: [] as string[] }, actions: { send: (c, msg: string) => { c.state.messages.push(msg); c.broadcast("message", msg); } } });
export const registry = setup({ use: { chatRoom } });
import { useState } from "react"; import { createRivetKit } from "@rivetkit/react"; import type { registry } from "./registry";
const { useActor } = createRivetKit<typeof registry>();
function ChatRoom() { const [messages, setMessages] = useState<string[]>([]); const chat = useActor({ name: "chatRoom", key: ["general"] });
chat.useEvent("message", (msg: string) => setMessages((prev) => [...prev, msg]));
return <div>{messages.map((m, i) => <p key={i}>{m}</p>)}</div>; }
Documentation
Reference Map
Actors
-
Actions
-
Actor Keys
-
Actor Scheduling
-
AI and User-Generated Rivet Actors
-
Authentication
-
Clients
-
Cloudflare Workers Quickstart
-
Communicating Between Actors
-
Connections
-
Design Patterns
-
Destroying Actors
-
Ephemeral Variables
-
Errors
-
Events
-
External SQL Database
-
Fetch and WebSocket Handler
-
Helper Types
-
Input Parameters
-
Lifecycle
-
Low-Level HTTP Request Handler
-
Low-Level KV Storage
-
Low-Level WebSocket Handler
-
Metadata
-
Next.js Quickstart
-
Node.js & Bun Quickstart
-
React Quickstart
-
Scaling & Concurrency
-
Sharing and Joining State
-
State
-
Testing
-
Types
-
Vanilla HTTP API
-
Versions & Upgrades
Clients
-
Next.js
-
Node.js & Bun
-
React
-
Rust
Connect
-
Deploy To Amazon Web Services Lambda
-
Deploying to AWS ECS
-
Deploying to Cloudflare Workers
-
Deploying to Freestyle
-
Deploying to Google Cloud Run
-
Deploying to Hetzner
-
Deploying to Kubernetes
-
Deploying to Railway
-
Deploying to Vercel
-
Deploying to VMs & Bare Metal
-
Supabase
General
-
Actor Configuration
-
Architecture
-
Cross-Origin Resource Sharing
-
Documentation for LLMs & AI
-
Edge Networking
-
Endpoints
-
Environment Variables
-
HTTP Server
-
Logging
-
MCP Server
-
Registry Configuration
-
Runtime Modes
Self Hosting
-
Configuration
-
Docker Compose
-
Docker Container
-
File System
-
Installing Rivet Engine
-
Kubernetes
-
Multi-Region
-
PostgreSQL
-
Railway Deployment