Needle Engine Documentation
Getting Started
Tutorials
How-To Guides
Explanation
Reference
Help
Getting Started
Tutorials
How-To Guides
Explanation
Reference
Help

Manual Networking

Send and receive custom network messages with full control.

Needle Engine provides a low-level API for sending and receiving messages. We call this "manual networking". The principles are the same as automatic networking, but you're in full control of when to send messages, what data to send, and how to handle incoming messages.

When to Use Manual Networking

Use manual networking when you need:

  • Custom message formats
  • Complex data structures
  • Fine-grained control over when messages are sent
  • High-performance binary messages
  • Custom server-side logic

For simple field synchronization, use @syncField instead.

Sending Messages

Send JSON Messages

Send a message to all users in the same room:

this.context.connection.send(key: string, data: IModel | object | boolean | string | number | null);

Example:

// Send a custom event
this.context.connection.send("player-jumped", {
    playerId: this.playerId,
    height: 2.5,
    timestamp: Date.now()
});

Send Binary Messages (Advanced)

Advanced - Not Needed for Most Use Cases

Binary messages with FlatBuffers are an advanced optimization for very high-frequency updates (100+ messages per second). Most applications should use JSON messages which are simpler and easier to work with.

For extreme performance scenarios, send binary (Flatbuffer) messages:

this.context.connection.sendBinary(byteArray: Uint8Array);

See Using Flatbuffers for details.

Receiving Messages

Subscribe to Messages

You can subscribe to events in the room using a specific key. Typically, you want to match subscribing with unsubscribing:

Option 1: Enable/Disable Lifecycle Subscribe in onEnable and unsubscribe in onDisable. With this approach, no messages will be received while the object is disabled.

onEnable() {
    this.context.connection.beginListen("player-jumped", this.onPlayerJumped);
}

onDisable() {
    this.context.connection.stopListen("player-jumped", this.onPlayerJumped);
}

private onPlayerJumped = (data: any) => {
    console.log("Player jumped:", data.playerId, data.height);
}

Option 2: Start/Destroy Lifecycle Subscribe in start and unsubscribe in onDestroy. With this approach, messages will still be received while the object is disabled.

start() {
    this.context.connection.beginListen("player-jumped", this.onPlayerJumped);
}

onDestroy() {
    this.context.connection.stopListen("player-jumped", this.onPlayerJumped);
}

private onPlayerJumped = (data: any) => {
    console.log("Player jumped:", data.playerId, data.height);
}

Unsubscribe from Messages

Always unsubscribe when you're done listening:

this.context.connection.stopListen(key: string);

Message Persistence

When sending network messages, you can control whether messages should be saved in the room state (persistent) or only sent to users currently in the room (non-persistent).

Persistent Messages

To persist a message, include a guid field. This field typically identifies which object the message applies to:

// This message will be saved in room state
// and sent to users who join later
this.context.connection.send("my-message", {
    guid: this.guid,
    myData: "myValue"
});

Non-Persistent Messages

Messages without a guid field are only sent to users currently in the room:

// This message will NOT be saved in room state
// Only current users receive it
this.context.connection.send("my-message", {
    myData: "myValue"
});

Prevent Persistence (with guid)

To target a specific object (include a guid) but still prevent persistence, set dontSave to true:

// Send to a specific object but don't persist
this.context.connection.send("my-message", {
    guid: this.guid,
    myData: "myValue",
    dontSave: true
});

Delete on Disconnect

To persist a message but delete it when the user disconnects, use deleteOnDisconnect:

// Persist until the user disconnects
this.context.connection.send("my-message", {
    guid: this.guid,
    myData: "myValue",
    deleteOnDisconnect: true
});

Delete Specific State

To delete state for a specific guid from the backend storage:

this.context.connection.send("delete-state", {
    guid: "guid_to_delete"
});

Debug Flags

Use these URL parameters to understand network messages:

FlagDescription
?debugnetLog all incoming and outgoing network messages
?debugownerLog all ownership requests and changes
?debugnetbinLog binary message details

Example:

http://localhost:3000/?debugnet

Using Flatbuffers (Advanced)

Advanced Feature - Not Necessary for Most Projects

You probably don't need this. FlatBuffers are an advanced optimization for extreme performance scenarios (e.g., 100+ network messages per second).

For most multiplayer applications, JSON messages are:

  • Much easier to implement and debug
  • Fully sufficient for typical gameplay (movement, interactions, chat, etc.)
  • Already optimized by Needle Engine

Only consider FlatBuffers if you have profiled your application and confirmed that JSON message serialization is a bottleneck.

Binary messages using Flatbuffers can be faster and more efficient than JSON messages for very high-frequency updates or extremely large data structures.

Requirements:

  • Define custom FlatBuffer schemas (.fbs files)
  • Compile schemas to TypeScript using the FlatBuffers compiler
  • Register schemas with the networking system
  • Write custom serialization/deserialization code

Register a Schema

import { registerBinaryType } from '@needle-tools/engine';
import { MyDataModel } from 'my-data-model.js';

const MySchemaIdentifier = "MYSC";

registerBinaryType(MySchemaIdentifier, MyDataModel.getRootAsSyncedTransformModel);

Send Binary Messages

import { MyDataModel } from 'my-data-model.js';

const MySchemaIdentifier = "MYSC";
const builder = new flatbuffers.Builder();

// Construct a Flatbuffer message
function createMyCustomModel(somePayload: string): Uint8Array {
    builder.clear();
    MyDataModel.startMyDataModel(builder);
    const guidObj = builder.createString(guid);
    MyDataModel.addSomePayload(builder, guidObj);
    const res = MyDataModel.endMyDataModel(builder);
    builder.finish(res, MySchemaIdentifier);
    return builder.asUint8Array();
}

// Send the data
function sendData() {
    const data = createMyCustomModel("your-payload");
    this.context.connection.sendBinary(data);
}

Receive Binary Messages

import { MyDataModel } from 'my-data-model.js';

const MySchemaIdentifier = "MYSC";

this.context.connection.beginListenBinary(MySchemaIdentifier, (data) => {
    const model = MyDataModel.getRootAsMyDataModel(data);
    console.log("Received binary message", model, model.somePayload());
});

Unsubscribe from Binary Messages

this.context.connection.stopListenBinary(identifier: string);

Built-in Flatbuffer Schemas

Needle Engine uses these binary schemas internally:

  • SyncedTransform ('STRS') - High-frequency transform updates
  • SyncedCamera ('SCAM') - Camera position/rotation sync
  • Vec2/3/4 - Optimized vector types

Custom Binary Messages

Currently, custom binary messages can't be automatically persisted on the networking server. To enable persistence, you need to modify the networking server to process your custom flatbuffer schemas.

Flatbuffer Resources

  • Built-in schemas in Needle Engine
  • Generating a schema
  • Using the schema compiler
  • Flatbuffers in TypeScript

Next Steps

Learn more:

  • Host Your Own Server - Deploy custom networking infrastructure
  • Understanding Networking - Learn how messages work

Reference:

  • Networking Events API - Complete event reference
  • Built-in Components - Networking component catalog
Suggest changes
Last Updated: 1/27/26, 4:32 PM

On this page

Extras

Copy for AI (LLMs)