Skip to main content
This page is part of an AI coding agent skill and is written for agents, not humans. For the human-readable Base44 docs, see the developer documentation.

Actors Module

Connect to realtime server rooms via base44.actors. An actor is a stateful server room over WebSockets. There is one live instance per room id, every client connected to that id shares it, and the actor is authoritative — clients send inputs, the actor validates and broadcasts the result.

Contents

Methods

base44.actors.<ActorName>(instanceId)

  • <ActorName>: the deployed actor’s name — a property on base44.actors, not a string argument
  • instanceId: the room id. Everyone who passes the same id shares one server instance
  • Returns an ActorRef — a handle, not yet a connection

connect

  • options.id (optional): the connection id, which becomes the actor’s conn.id. Supply a stable value so a reconnect reuses the same server-side identity; omit for an auto-generated one
  • Returns a Connection synchronously — messages you send are buffered until the socket opens
  • Idempotent per ref: calling connect() again on the same ref returns the same connection. But base44.actors.ChatRoom(id) mints a new ref on every call, so base44.actors.ChatRoom(id).connect() opens a new socket each time — hold the ref (or the connection) yourself, as the React example does

Connection.subscribe

  • callback(data): called for every message the actor sends to this client
  • Multiple listeners are allowed; returns { unsubscribe() } which removes only that listener

Connection.send

Sends a JSON message to the actor (arrives as msg in its handleMessage).

Connection.close

Tears down the socket, the heartbeat, and all listeners.

Connection.id

The connection id the actor sees as conn.id.

Connecting

From React

Send operations, not outcomes, and throttle high-frequency input (cursor moves ~20–30/s max) — never send once per render or animation frame. Always connect inside useEffect with a cleanup — a bare connect() in the component body opens a socket per render.

Vanilla

Reconnects

Reconnection, heartbeats, and half-open detection are handled by the SDK — you do not write retry logic. What you do control is identity: pass a stable options.id so the actor recognizes a returning client and can hand back its seat, role, or score. A reconnect replaces the stale socket holding that id without firing the actor’s handleClose, which is what makes the seat reclaimable. Persist that id in sessionStorage (per tab), not localStorage — two live connections cannot share an id, so a second tab reusing it is refused. React Native: the same API applies, but there is no sessionStorage — keep the connection id in module scope or state. A fresh launch mints a new identity. base44.cleanup() closes every live room, so a forgotten close() cannot leak a heartbeat timer.

Instance Ids

The instance id is what separates one room from another.
  • Printable ASCII, 1–256 characters, no /
  • Use a meaningful id when the room is public or discoverable (e.g. a Room entity’s record id)
  • Private rooms: there is no room-level auth — the id is the admission control. Mint it with crypto.randomUUID(), keep it out of any readable list, and share it only as an invite link or code

Authentication

Actors use direct connections. The SDK authenticates a connection-token request using the user’s existing session, then opens the WebSocket directly to Cloudflare with the returned credentials. You do not mint or pass tokens yourself. Anonymous (logged-out) connections are allowed when the app permits them, and a login or logout is picked up on the next reconnect. On the actor, conn.identity is the platform-verified principal: { type: "authenticated", userId } or { type: "anonymous", anonymousId }. It survives hibernation. For user attribution and permissions, check for an authenticated identity and use userId — never a user id supplied in a message. An anonymous visitor is not a signed-in user. conn.id is client-chosen reconnect bookkeeping. Keep it out of broadcasts, public records, and author fields; use a separate server-assigned participant id for presence. A returning connection id alone must not grant access to another user’s state or permissions.

Writing an Actor

Actors live in base44/actors/<Name>/entry.ts and default-export a class extending Actor:
This example attributes signed-in authors with verified userId; guest messages have authorUserId: null. Connection IDs never enter the chat history or presence messages. Instance fields are lost when the room hibernates: persist in this.storage and rehydrate in handleStart. this.broadcast() sends room-wide state; conn.send() sends to one client. this.client uses the anonymous role, while this.client.asServiceRole provides admin-level access for validated, room-owned work. Persist canonical room results from the actor through asServiceRole, using an explicit field from authenticated conn.identity.userId when attribution is needed. Keep frontend writes limited to user-owned records. For the complete authoring contract — naming, lifecycle handlers, storage and hibernation, scheduled wakes, rooms and discovery, deployment — see actors-create.md in base44-cli.

When to Use an Actor

Type Definitions

How to get typed actor names: the Base44 CLI generates an augmentation of ActorNameRegistry from your project (base44 types generate). For how to run it, use the base44-cli skill. Message types are hand-authored in ActorRegistry, so the actor and the client share one source of truth. Put this in a declaration file included by your TypeScript project (e.g. base44/.types/actor-messages.d.ts). Keep the top-level import: it makes this a module augmentation instead of shadowing the SDK’s exports.
With that in place, subscribe callbacks and send payloads are typed:
Type the actor class off the same registry so the two cannot drift:
base44:runtime/actors is a virtual module resolved at deploy time, so add an ambient declaration for your editor (e.g. base44/.types/runtime.d.ts):

Interfaces

Server-side types (Actor, Conn, Storage) come from the actor base class:
The members you implement (handleConnect, handleMessage, handleClose, handleTick, handleStart, handleWake, shouldTick, tickIntervalMs) are public — override them without a modifier. The members you only call (broadcast, getConnections, schedule, cancelSchedule, instanceId, storage, client) are protected: reachable via this inside your actor, invisible from outside it. Marking an override protected where the base declares it public is a compile error (TS2416), so don’t add the modifier to a handler. handleTick is an abstract member, so a TypeScript actor has to declare it to compile — handleTick() {} is all it needs. Plain-JavaScript actors can omit it. It only runs if you opt into the managed ticker by overriding shouldTick(): the platform then calls handleTick() every tickIntervalMs while that returns true, and stops (letting the room idle out) when it returns false. Reach for it when the room must advance on its own — a game loop, a visible countdown, a simulation step — and see the base44-cli skill’s actors-create.md for the full pattern.

Notes

  • base44.actors.<Name>(id) returns a handle; you must call .connect() to get a Connection
  • subscribe, send, and close live on the Connection, not on the ref
  • Messages are JSON in both directions; type values beginning with __ are reserved by the platform
  • Actors are frontend-facing: the client connects from the browser, and the actor itself is the backend half
  • Actors do not run under base44 dev — verify against a deployed actor