> ## Documentation Index
> Fetch the complete documentation index at: https://docs.base44.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Internal links on these pages omit the .md extension. Append .md to a docs page URL, or send an Accept: text/markdown header, to get that page as markdown.

# Actors: Sample Flows

> Sample flows for lifecycle handlers, timers, connections, storage, entities, and backend functions

<div className="dev-docs-banner">
  <div className="dev-docs-banner-content">
    <div className="dev-docs-banner-title">
      You're viewing developer documentation
    </div>

    <div className="dev-docs-banner-text">
      This documentation is for developers working with the Base44 developer
      platform.
    </div>
  </div>
</div>

Use these sample flows as a starting point for your implementation.

## Connect a client to a session

When the frontend needs to join a live session, receive messages from the actor, and send messages to the actor, connect a client to an actor session.

**To connect a client to a session:**

1. Call [`base44.actors.ActorName(sessionId)`](/developers/references/sdk/docs/type-aliases/actors) to select the session.
2. Call [`.connect()`](/developers/references/sdk/docs/type-aliases/actors#connect) to open the WebSocket connection.
3. Call [`conn.subscribe()`](/developers/references/sdk/docs/type-aliases/actors#subscribe) to register a listener for messages from the actor.
4. Call [`conn.send()`](/developers/references/sdk/docs/type-aliases/actors#send) to send a message to the actor.

The following example connects to a `chatRoom` actor session, listens for messages, and sends a message:

```typescript theme={null}
const conn = base44.actors.chatRoom("lobby-1").connect();

conn.subscribe((msg) => {
  if (msg.type === "message") console.log(msg.text);
});

conn.send({ type: "message", text: "Hello" });
```

To learn more about connections, see [connections](/developers/backend/resources/actors/reference#connections).

## Run ticks

When work needs to repeat on an interval while clients are in the session, run ticks. Examples include broadcasting game state or updating a round timer.

**To run ticks:**

1. Set [`tickIntervalMs`](/developers/backend/resources/actors/reference#timer-handlers-and-settings) to the interval length in milliseconds.
2. Implement [`shouldTick()`](/developers/backend/resources/actors/reference#timer-handlers-and-settings) to return `true` to keep ticking and `false` to stop.
3. In [`handleTick()`](/developers/backend/resources/actors/reference#timer-handlers-and-settings), run the work for each interval.

The following example ticks every second while at least two clients are in the session, broadcasting a countdown each tick:

```typescript theme={null}
export default class Game extends Actor<Incoming, Outgoing> {
  tickIntervalMs = 1000;
  countdown = 60;

  shouldTick() {
    return this.getConnections().length >= 2;
  }

  async handleTick() {
    this.countdown--;
    this.broadcast({ type: "tick", countdown: this.countdown });
  }
}
```

To learn more about ticks, see [timer handlers and settings](/developers/backend/resources/actors/reference#timer-handlers-and-settings).

## Schedule wakes

When work needs to run once at a specific time, whether the session is active or idle, schedule a wake. Examples include ending a turn after 30 seconds or closing an empty lobby.

**To schedule a wake:**

1. Call [`schedule()`](/developers/backend/resources/actors/reference#timer-handlers-and-settings) with a key and a time to arm the wake.
2. In [`handleWake()`](/developers/backend/resources/actors/reference#timer-handlers-and-settings), check the key and run the work for that wake.
3. When the wake no longer applies, call [`cancelSchedule()`](/developers/backend/resources/actors/reference#timer-handlers-and-settings) with the key.

The following example schedules a turn timeout when a turn starts and broadcasts a message when the timeout triggers:

```typescript theme={null}
export default class Game extends Actor<Incoming, Outgoing> {
  async handleMessage(_conn, msg) {
    if (msg.type === "start-turn") {
      await this.schedule("turn-timeout", Date.now() + 30_000);
    }
    if (msg.type === "end-turn") {
      await this.cancelSchedule("turn-timeout");
    }
  }

  async handleWake(key: string) {
    if (key === "turn-timeout") {
      this.broadcast({ type: "timeout" });
    }
  }
}
```

To learn more about wakes, see [timer handlers and settings](/developers/backend/resources/actors/reference#timer-handlers-and-settings).

## Manage client connections

When you need to decide who can join a session, reply to a specific client, and reach every client in the session, manage client connections.

**To manage client connections:**

1. In [`handleConnect()`](/developers/backend/resources/actors/reference#lifecycle-handlers), check [`conn.identity`](/developers/backend/resources/actors/reference#connections) and call [`conn.reject()`](/developers/backend/resources/actors/reference#connections) to refuse a client.
2. Call [`conn.send()`](/developers/backend/resources/actors/reference#connections) to reply to only that client.
3. In [`handleMessage()`](/developers/backend/resources/actors/reference#lifecycle-handlers), call [`broadcast()`](/developers/backend/resources/actors/reference#connections) to reach every connected client. To identify the sender, include fields from `conn` in the message.

The following example rejects anonymous clients, sends history to the connecting client, and broadcasts messages with the sender's user ID:

```typescript theme={null}
async handleConnect(conn) {
  if (conn.identity.type === "anonymous") {
    conn.reject(401, "Login required");
    return;
  }
  conn.send({ type: "history", messages: this.messages });
}

async handleMessage(conn, msg) {
  this.broadcast({ type: "message", from: conn.identity.userId, text: msg.text });
}
```

To learn more about connections, see [connections](/developers/backend/resources/actors/reference#connections).

## Persist session data

When values need to survive an idle period, persist session data. Examples include a board, a history list, or the current turn.

**To persist session data:**

1. In [`handleStart()`](/developers/backend/resources/actors/reference#lifecycle-handlers), load values from [`this.storage`](/developers/backend/resources/actors/reference#storage) into class fields.
2. After each change, call [`this.storage.put()`](/developers/backend/resources/actors/reference#storage) to save the updated value.
3. To reset the session, call [`this.storage.delete()`](/developers/backend/resources/actors/reference#storage) or [`this.storage.deleteAll()`](/developers/backend/resources/actors/reference#storage).

The following example loads a board on start, saves it after each move, and clears storage when the last client disconnects:

```typescript theme={null}
async handleStart() {
  this.board = (await this.storage.get<Board>("board")) ?? newBoard();
}

async handleMessage(_conn, msg) {
  applyMove(this.board, msg.move);
  await this.storage.put("board", this.board);
}

async handleClose(_conn) {
  if (this.getConnections().length === 0) {
    await this.storage.deleteAll();
  }
}
```

To learn more about storage, see [storage](/developers/backend/resources/actors/reference#storage).

## Write entity data from a session

When the session needs to create or update app records, write entity data. Examples include a final score or a completed order.

**To write entity data from a session:**

1. Validate the incoming message before writing.
2. Read the user ID from [`conn.identity.userId`](/developers/backend/resources/actors/reference#connections).
3. Use [`this.client.asServiceRole`](/developers/backend/resources/actors/reference#entity-access) for elevated access. Write with a stable key, such as the session ID, so a repeated handler updates the same record.

The following example saves a final score for the authenticated client. A later save for the same session updates that record:

```typescript theme={null}
async handleMessage(conn, msg) {
  if (msg.type !== "save-score" || conn.identity.type !== "authenticated") return;
  if (typeof msg.score !== "number") return;

  const fields = {
    sessionId: this.instanceId,
    userId: conn.identity.userId,
    score: msg.score,
  };
  const [existing] = await this.client.asServiceRole.entities.Score.filter({
    sessionId: this.instanceId,
  });

  if (existing) {
    await this.client.asServiceRole.entities.Score.update(existing.id, fields);
  } else {
    await this.client.asServiceRole.entities.Score.create(fields);
  }
}
```

To learn more about entity access, see [entity access](/developers/backend/resources/actors/reference#entity-access).

## Call a backend function for secret work

Actors can read app secrets with [`secrets.get()`](/developers/backend/resources/actors/reference#read-secrets). When a handler needs work that uses a secret, such as calling an external API with an API key, we recommend calling a [backend function](/developers/backend/resources/backend-functions/overview). The function reads the secret and returns the result, never the secret.

**To call a backend function from an actor:**

1. Create a backend function that reads the secret with `secrets.get()`, does the work, and returns the result.
2. In a handler, call [`this.client.functions.invoke()`](/developers/references/sdk/docs/interfaces/functions#invoke) with the function name and the data to send.
3. Use the result in your session, such as broadcasting it to connected clients.

The following example shows a backend function that reads an API key and returns a translation:

```typescript theme={null}
// base44/functions/translateText/entry.ts
import { secrets } from "base44:runtime";

export default async function (req: Request): Promise<Response> {
  const { text } = await req.json();
  const apiKey = secrets.get("TRANSLATE_API_KEY");

  const res = await fetch("https://api.example.com/translate", {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}` },
    body: JSON.stringify({ text }),
  });
  const { translation } = await res.json();

  return Response.json({ translation });
}
```

The following example calls that function from an actor and broadcasts the result:

```typescript theme={null}
export default class ChatRoom extends Actor<Incoming, Outgoing> {
  async handleMessage(_conn, msg) {
    const result = await this.client.functions.invoke("translateText", { text: msg.text });
    this.broadcast({ type: "message", text: result.data.translation });
  }
}
```

## See also

* [Actors Overview](/developers/backend/resources/actors/overview): Concepts and terminology
* [Create an Actor](/developers/backend/resources/actors/create-actor): Actor files and a minimal class
* [Actors Files and Code](/developers/backend/resources/actors/reference): Complete backend class API
* [`actors deploy`](/developers/references/cli/commands/actors-deploy): Deploy local actors


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.