This guide covers how to connect to and interact with actors from client applications.

Setting Up the Client

The first step is to create a client that will connect to your actor service:

import { createClient } from "actor-core/client";
import type { App } from "../src/index";

// Create a client with the connection address and app type
const client = createClient<App>(/* CONNECTION ADDRESS */);
See the setup guide for your platform for details on how to get the connection address.

Finding & Connecting to Actors

ActorCore provides several methods to connect to actors:

get(tags, opts) - Find or Create

The most common way to connect is with get(), which finds an existing actor matching the provided tags or creates a new one:

// Connect to a chat room for the "general" channel
const room = await client.chatRoom.get({
  name: "chat_room",
  channel: "general"
});

// Now you can call methods on the actor
await room.sendMessage("Alice", "Hello everyone!");

create(opts) - Explicitly Create New

When you specifically want to create a new actor instance:

// Create a new document actor
const doc = await client.myDocument.create({
  create: {
    tags: {
      name: "my_document",
      docId: "123"
    }
  }
});

await doc.initializeDocument("My New Document");

getWithId(id, opts) - Connect by ID

Connect to an actor using its internal ID:

// Connect to a specific actor by its ID
const myActorId = "55425f42-82f8-451f-82c1-6227c83c9372";
const doc = await client.myDocument.getWithId(myActorId);

await doc.updateContent("Updated content");

It’s usually better to use tags for discovery rather than directly using actor IDs.

Calling Actions

Once connected, calling actor actions are straightforward:

// Call an action
const result = await mathUtils.multiplyByTwo(5);
console.log(result); // 10

// Call an action with multiple parameters
await chatRoom.sendMessage("Alice", "Hello everyone!");

// Call an action with an object parameter
await gameRoom.updateSettings({
  maxPlayers: 10,
  timeLimit: 300,
  gameMode: "capture-the-flag"
});

All actor action calls are asynchronous and require await, even if the actor’s action is not async.

Listening for Events

Actors can send realtime updates to clients using events:

on(eventName, callback) - Continuous Listening

To listen for events that will happen repeatedly:

// Listen for new chat messages
chatRoom.on("newMessage", ({ sender, text, timestamp }) => {
  console.log(`${sender}: ${text}`);
  updateChatUI(sender, text, timestamp);
});

// Listen for game state updates
gameRoom.on("stateUpdate", (gameState) => {
  updateGameUI(gameState);
});

once(eventName, callback) - One-time Listening

For events you only need to hear once:

// Listen for when a request is approved
actor.once("requestApproved", () => {
  showApprovalNotification();
  unlockFeatures();
});

Connection Options

Authentication and Connection Parameters

Pass information with your connection using the params option:

const chatRoom = await client.chatRoom.get({ channel: "super-secret" }, {
  params: { 
    userId: "1234",
    authToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    displayName: "Alice"
  }
});

The actor can access these parameters in the onBeforeConnect or createConnState hook:

import { actor } from "actor-core";

const chatRoom = actor({
  state: { messages: [] },
  
  createConnState: (c, { params }) => {
    // Validate authentication token
    const { userId, authToken, displayName } = params;
    if (!validateToken(userId, authToken)) {
      throw new Error("Invalid authentication");
    }
    
    // Return connection state
    return { userId, displayName };
  },
  
  actions: {
    // ...
  }
});

Read more about connection parameters.

Additional Options

opts.noCreate

Connect only if an actor exists, without creating a new one:

try {
  const doc = await client.document.get({ documentId: "doc-123" }, { noCreate: true });
  await doc.doSomething();
} catch (error) {
  console.log("Document doesn't exist");
}

Client Options

// Example with all client options
const client = createClient<App>(
  "https://actors.example.com",
  {
    // Data serialization format
    encoding: "cbor", // or "json"
    
    // Network transports in order of preference
    supportedTransports: ["websocket", "sse"]
  }
);

encoding

"cbor" | "json" (optional)

Specifies the data encoding format used for communication:

  • "cbor" (default): Binary format that’s more efficient for data transfer
  • "json": Text-based format with wider compatibility across environments

supportedTransports

("websocket" | "sse")[] (optional)

Configures which network transport mechanisms the client will use to communicate with actors, sorted by priority:

  • "websocket": Real-time bidirectional communication, best for most applications
  • "sse" (Server-Sent Events): Works in more restricted environments where WebSockets may be blocked

Default is ["websocket", "sse"], which automatically negotiates the best available option.

Error Handling

ActorCore provides specific error types to help you handle different failure scenarios:

Action Errors

When an action fails, it throws an error with details about the failure:

try {
  await actor.someAction();
} catch (error) {
  console.error(`Action failed: ${error.code} - ${error.message}`);
  // Handle specific error codes
  if (error.code === "permission_denied") {
    // Handle permission errors
  }
}

These errors can be thrown from within the actor with UserError:

import { actor, UserError } from "actor-core";

const documentActor = actor({
  state: { content: "" },
  
  actions: {
    editDocument: (c, userId, newContent) => {
      // Check if user has permission to edit
      if (!hasPermission(userId, "edit")) {
        throw new UserError("You don't have permission to edit this document", {
          code: "permission_denied",
          meta: { userId }
        });
      }
      
      c.state.content = newContent;
    }
  }
});

ActorCore doesn’t expose internal errors to clients for security, helping to prevent the exposure of sensitive information or internal implementation details.

Other Errors

Other common errors you might encounter:

  • InternalError: Error from your actor that’s not a subclass of UserError
  • ManagerError: Issues when connecting to or communicating with the actor manager
  • NoSupportedTransport: When the client and server have no compatible transport

Disconnecting and Cleanup

The client connection is automatically cleaned up when it goes out of scope.

If you need to explicitly disconnect:

// Disconnect from the actor
await actor.dispose();

// Disconnect the entire client
await client.dispose();

Offline and Auto-Reconnection

Clients automatically attempt to reconnect (with exponential backoff) when disconnected. Actions made while disconnected are queued.

On reconnection, event subscriptions are reestablished & queued actions are executed.

This makes your applications resilient to temporary network failures without any extra code.

Next Steps