npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

unreal-rc

v0.5.1

Published

Typed client for Unreal Engine Remote Control over WebSocket or HTTP

Readme

unreal-rc

Typed TypeScript client for Unreal Engine's Remote Control plugin. Communicate with a running Unreal Editor or game instance over WebSocket or HTTP.

  • Transport-agnostic — swap between WebSocket and HTTP with one option
  • Type-safe — Effect Schema validation on request and response payloads
  • Resilient — auto-reconnect, configurable retries, end-to-end request timeouts
  • Observable — lifecycle hooks for logging and tracing
  • Effect-powered — bring your own compatible Effect version

Install

npm install unreal-rc effect

Setup

Enable the Remote Control plugin in your Unreal project (Edit > Plugins > search "Remote Control"). The plugin opens two localhost endpoints:

| Protocol | Default Port | |----------|-------------| | HTTP | 30010 | | WebSocket | 30020 |

Creating a Client

import { UnrealRC } from "unreal-rc";

// WebSocket (default) — persistent connection with auto-reconnect
const ue = new UnrealRC();

// HTTP — stateless fetch-based requests
const ue = new UnrealRC({ transport: "http" });

// Full options
const ue = new UnrealRC({
  transport: "ws",          // "ws" | "http"
  host: "127.0.0.1",       // Unreal host
  port: 30020,             // Port (default: 30020 for ws, 30010 for http)
  passphrase: "secret",    // Overrides the default Remote Control HTTP passphrase
  validateResponses: true,  // Validate response schemas with Effect Schema
  retry: {                  // Retry policy (or `true` for defaults, `false` to disable)
    maxAttempts: 3,
    delayMs: 100,           // or (context) => context.attempt * 200
    shouldRetry: (ctx) => ctx.error.kind !== "decode",
  },
});

// Always dispose when done
await ue.dispose();

HTTP clients send Passphrase: "smh ue, this is stupid" by default. Override passphrase if your Unreal Remote Control setup uses a different value. In practice, some Unreal versions require the Passphrase header for /remote/batch even when other HTTP routes appear to work without it.

API Reference

call(args)

Call a function on a remote UObject.

// Call a Blueprint function
await ue.call({
  objectPath: "/Game/Maps/Main.Main:PersistentLevel.MyActor",
  functionName: "SetActorHiddenInGame",
  parameters: { bNewHidden: false }
});

// Call with transaction support (for undo/redo in the editor)
await ue.call({
  objectPath: path,
  functionName: "IncrementCounter",
  parameters: { Delta: 5 },
  transaction: true
});

// Access the return value
const result = await ue.call({ objectPath: path, functionName: "GetHealth" });
console.log(result.ReturnValue); // e.g. 100

Options:

| Option | Type | Description | |--------|------|-------------| | transaction | boolean | Wrap in an editor transaction (enables undo) | | timeoutMs | number | Per-request timeout override for the full request lifecycle, including queued websocket time | | retry | RetryOptions | Per-request retry override |


getProperty<T>(args)

Read a single property from a remote UObject. Returns the property value directly.

const health = await ue.getProperty<number>({ objectPath: path, propertyName: "Health" });
// health === 100

const location = await ue.getProperty<{ X: number; Y: number; Z: number }>({
  objectPath: path,
  propertyName: "RelativeLocation"
});

getProperties<T>(args)

Read all properties from a remote UObject at once.

const props = await ue.getProperties<{ Health: number; Mana: number }>({ objectPath: path });
// props.Health, props.Mana

setProperty(args)

Write a property on a remote UObject.

await ue.setProperty({ objectPath: path, propertyName: "Health", propertyValue: 100 });

// With transaction support
await ue.setProperty({ objectPath: path, propertyName: "Health", propertyValue: 100, transaction: true });

// Setting a struct property
import { vector } from "unreal-rc";
await ue.setProperty({ objectPath: path, propertyName: "RelativeLocation", propertyValue: vector(100, 200, 300) });

Arguments:

| Option | Type | Description | |--------|------|-------------| | access | "WRITE_ACCESS" \| "WRITE_TRANSACTION_ACCESS" | Access mode | | transaction | boolean | Wrap in an editor transaction | | timeoutMs | number | Per-request timeout override for the full request lifecycle, including queued websocket time | | retry | RetryOptions | Per-request retry override |


describe(args)

Get metadata about a remote UObject — its properties, functions, class, and display name.

const meta = await ue.describe({ objectPath: path });

// List all exposed functions
for (const fn of meta.Functions ?? []) {
  console.log(fn.Name, fn.Arguments);
}

// List all exposed properties
for (const prop of meta.Properties ?? []) {
  console.log(prop.Name, prop.Type);
}

Returns: ObjectDescribeResponse

{
  Name?: string;
  Class?: string;
  DisplayName?: string;
  Path?: string;
  Properties?: PropertyMetadata[];
  Functions?: FunctionMetadata[];
}

searchAssets(args)

Search for assets in the project.

const result = await ue.searchAssets({ query: "Chair" });

for (const asset of result.Assets ?? []) {
  console.log(asset.Name, asset.ObjectPath, asset.AssetClass);
}

Arguments:

| Option | Type | Description | |--------|------|-------------| | classNames | string[] | Filter by asset class | | packagePaths | string[] | Filter by package path | | recursivePaths | boolean | Search subdirectories | | recursiveClasses | boolean | Include subclasses |


info(options?)

List all available Remote Control HTTP routes.

const info = await ue.info();
for (const route of info.HttpRoutes ?? info.Routes ?? []) {
  console.log(route.Verb, route.Path, route.Description);
}

event(request, options?)

Wait for a property change event on a remote UObject.

const change = await ue.event({
  objectPath: path,
  propertyName: "Health",
  timeoutSeconds: 30,
});
console.log(change.propertyValue); // new value after change

thumbnail(args)

Get a thumbnail image for an asset.

const thumb = await ue.thumbnail({ objectPath: "/Game/Meshes/Chair" });

batch(configure, options?)

Execute multiple requests in a single round-trip. Each sub-request returns a BatchResult with its own status code and body.

const results = await ue.batch((b) => {
  b.call({ objectPath: path, functionName: "ResetFixtures" });
  b.getProperty({ objectPath: path, propertyName: "Health" });
  b.setProperty({ objectPath: path, propertyName: "Score", propertyValue: 0 });
  b.describe({ objectPath: path });
  b.searchAssets({ query: "Chair" });
  b.request("GET", "/remote/info"); // raw request
});

for (const result of results) {
  console.log(result.requestId, result.statusCode, result.body);
}

If batch requests fail unexpectedly while single-route HTTP calls succeed, make sure the client passphrase matches the Unreal Remote Control configuration.

BatchResult:

{
  requestId: number;
  statusCode: number;
  body: unknown;
  request: BatchRequestItem;
}

dispose()

Shut down the transport and release resources. Always call this when done.

await ue.dispose();

Helpers

Utility functions for building Unreal-specific values.

Path Builders

import { objectPath, piePath, blueprintLibraryPath } from "unreal-rc";

// Build an object path: "/Game/Maps/Main.Main:MyActor"
objectPath("/Game/Maps/Main", "Main", "MyActor");

// Build a PIE (Play In Editor) world name: "UEDPIE_0_Main"
piePath("Main");
piePath("Main", 1); // "UEDPIE_1_Main"

// Build a Blueprint function library path
blueprintLibraryPath("MyModule", "MyBlueprintLibrary");
// "/Script/MyModule.Default__MyBlueprintLibrary"

Struct Constructors

import { vector, rotator, linearColor, transform } from "unreal-rc";

vector(100, 200, 300);
// { X: 100, Y: 200, Z: 300 }

rotator(0, 90, 0);
// { Pitch: 0, Yaw: 90, Roll: 0 }

linearColor(1, 0, 0);
// { R: 1, G: 0, B: 0, A: 1 }

linearColor(1, 0, 0, 0.5);
// { R: 1, G: 0, B: 0, A: 0.5 }

transform(vector(0, 0, 0), rotator(0, 90, 0));
// { Translation: {...}, Rotation: {...}, Scale3D: { X: 1, Y: 1, Z: 1 } }

Response Parsing

import { parseReturnValue } from "unreal-rc";

const response = await ue.call({ objectPath: path, functionName: "GetHealth" });
const health = parseReturnValue<number>(response);          // reads .ReturnValue
const health = parseReturnValue<number>(response, "Health"); // reads .Health

Error Handling

All transport errors are thrown as TransportRequestError with structured metadata.

import { TransportRequestError } from "unreal-rc";

try {
  await ue.call({ objectPath: path, functionName: "DoSomething" });
} catch (error) {
  if (error instanceof TransportRequestError) {
    error.kind;       // "timeout" | "connect" | "disconnect" | "http_status"
                      // | "remote_status" | "decode" | "unknown"
    error.statusCode; // HTTP status code (if applicable)
    error.details;    // Response body from Unreal
    error.verb;       // "GET" | "PUT" | ...
    error.url;        // "/remote/object/call"
    error.transport;  // "ws" | "http"
    error.requestId;  // Server-assigned request ID
  }
}

Error Kinds

| Kind | Description | |------|-------------| | timeout | Request exceeded the timeout | | connect | Could not connect to Unreal | | disconnect | Connection dropped mid-request | | http_status | Non-2xx HTTP response from Unreal | | remote_status | Unreal returned an application-level error | | decode | Response did not match the expected schema | | unknown | Unexpected error |

Retries

By default, timeout, connect, disconnect, and HTTP 502/503/504 errors are retried. Configure globally or per-request:

// Global retry policy
const ue = new UnrealRC({
  retry: { maxAttempts: 5, delayMs: 200 },
});

// Per-request override
await ue.call({
  objectPath: path,
  functionName: "SlowFunction",
  retry: { maxAttempts: 10, delayMs: 500 },
  timeoutMs: 30000
});

// Disable retries for a specific request
await ue.call({ objectPath: path, functionName: "FastFunction", retry: false });

Hooks

Lifecycle hooks for observability — logging, metrics, tracing.

const ue = new UnrealRC({
  onRequest: (ctx) => {
    console.log(`>> ${ctx.verb} ${ctx.url}`);
  },
  onResponse: (ctx) => {
    console.log(`<< ${ctx.statusCode} (${ctx.durationMs}ms)`);
  },
  onError: (ctx) => {
    console.error(`!! ${ctx.error.kind}: ${ctx.error.message}`);
  },
  // Redact sensitive data before it reaches hooks
  redactPayload: (payload, ctx) => {
    if (ctx.phase === "request") return "[redacted]";
    return payload;
  },
});

Hook Contexts

RequestHookContext: { transport, verb, url, body, attempt }

ResponseHookContext: { transport, verb, url, body, requestBody, attempt, durationMs, statusCode, requestId }

ErrorHookContext: { transport, verb, url, body, error, errorBody, attempt, durationMs, statusCode, requestId }

Effect-Native Hooks

Effect-native hooks run inside the request pipeline and propagate failures (unlike callback hooks which silently ignore errors). Use them when you need typed, fail-fast observability in an Effect program.

import { Effect } from "effect";

const ue = new UnrealRC({
  onRequestEffect: (ctx) => Effect.logInfo(`>> ${ctx.verb} ${ctx.url}`),
  onResponseEffect: (ctx) =>
    Effect.logInfo(`<< ${ctx.statusCode} (${ctx.durationMs}ms)`),
  onErrorEffect: (ctx) =>
    Effect.logError(`${ctx.error._tag}: ${ctx.error.message}`),
});

// Callback hooks and Effect hooks can coexist
const ue = new UnrealRC({
  onRequest: (ctx) => { /* callback: errors ignored */ },
  onRequestEffect: (ctx) => Effect.logInfo("..."), // fails propagate
});

Effect API

All request methods are available on ue.effect.* with the same argument types as the Promise API. Effect methods return Effect<...> with tagged TransportError errors.

import { UnrealRC } from "unreal-rc";
import { Effect } from "effect";

const ue = new UnrealRC();

// Same argument shapes as the Promise API
const program = Effect.gen(function* () {
  const result = yield* ue.effect.call({
    objectPath: "/Game/Maps/Main.Main:Actor",
    functionName: "GetHealth"
  });
  return result.ReturnValue;
});

Error Handling with Effect

Effect methods fail with a tagged TransportError union. Narrow errors with catchTag or catchTags:

import { TimeoutError, ConnectError, HttpStatusError } from "unreal-rc/effect";

const robustCall = ue.effect.call({
  objectPath: "/Game/Maps/Main.Main:Actor",
  functionName: "GetHealth"
}).pipe(
  Effect.catchTag("TimeoutError", (e) => Effect.succeed({ ReturnValue: -1 })),
  Effect.catchTag("ConnectError", (e) =>
    Effect.fail(new Error(`Unreal not running: ${e.message}`))
  ),
  Effect.catchTag("HttpStatusError", (e) =>
    Effect.succeed({ ReturnValue: e.statusCode })
  )
);

Error tags: TimeoutError, ConnectError, DisconnectError, HttpStatusError, RemoteStatusError, DecodeError.

Import tagged error classes from unreal-rc/effect:

import {
  TimeoutError,
  ConnectError,
  DisconnectError,
  HttpStatusError,
  RemoteStatusError,
  DecodeError,
  type TransportError
} from "unreal-rc/effect";

Schema-Driven Return Decoding (callReturn)

callReturn calls /remote/object/call and decodes only the ReturnValue field with a schema, eliminating manual unwrapping:

import { Schema } from "effect";

const ScoreSchema = Schema.Struct({ points: Schema.Number, rank: Schema.String });

// Promise API
const score = await ue.callReturn({
  objectPath: "/Game/Maps/Main.Main:Actor",
  functionName: "GetScore",
  returnSchema: ScoreSchema
});
// score: { points: number; rank: string }

// Effect API
const score = yield* ue.effect.callReturn({
  objectPath: "/Game/Maps/Main.Main:Actor",
  functionName: "GetScore",
  returnSchema: ScoreSchema
}).pipe(
  Effect.catchTag("DecodeError", () => Effect.succeed({ points: 0, rank: "unknown" }))
);

Generic Requests (request / requestRaw)

Send arbitrary HTTP requests to Unreal Remote Control endpoints:

// Decoded request — validates response with an optional schema
const data = await ue.request({
  verb: "GET",
  url: "/remote/info",
  responseSchema: InfoResponseSchema
});

// Raw request — returns the full TransportResponse
const raw = await ue.requestRaw({
  verb: "PUT",
  url: "/remote/search/assets",
  body: { query: "Chair" }
});
// raw: { body: unknown; statusCode?: number; requestId?: number | string }

// Effect equivalents
const data = yield* ue.effect.request({ verb: "GET", url: "/remote/info" });
const raw = yield* ue.effect.requestRaw({ verb: "PUT", url: "/custom", body: { key: "val" } });

Effect Layer / Service

For full Effect applications, use the injectable service layer:

import { UnrealRCService, UnrealRCLive } from "unreal-rc/effect";
import { Effect } from "effect";

// Define your program against the service interface
const program = Effect.gen(function* () {
  const ue = yield* UnrealRCService;
  const info = yield* ue.info();
  const result = yield* ue.call({
    objectPath: "/Game/Maps/Main.Main:Actor",
    functionName: "GetHealth"
  });
  return result.ReturnValue;
});

// Provide the live layer at the edge
await Effect.runPromise(
  program.pipe(
    Effect.provide(UnrealRCLive({ transport: "http" }))
  )
);

The service is scoped — dispose is called automatically when the Effect scope ends.

For testing, use UnrealRCTest as a stub layer:

import { UnrealRCTest } from "unreal-rc/effect";

// Replace with your own implementation in tests
const testLayer = Layer.succeed(UnrealRCService, {
  call: () => Effect.succeed({ ReturnValue: "mocked" }),
  // ... other methods
});

Migration Note

Positional overloads have been removed. All methods now take a single object argument:

// ✅ Current (object-arg)
await ue.call({ objectPath: "/Foo", functionName: "Bar" });

// ❌ Removed (positional)
await ue.call("/Foo", "Bar");

This applies to call, getProperty, getProperties, setProperty, describe, searchAssets, thumbnail, and batch builder methods. The Effect API (ue.effect.*) uses the same object-arg shapes.

Low-Level Exports

For building custom tooling or higher-level abstractions, the package also exports:

  • Request builders: buildCallRequest, buildPropertyRequest, buildDescribeRequest, buildBatchRequest
  • Batch builder class: BatchBuilder
  • All Effect schemas: ObjectCallRequestSchema, ObjectCallResponseSchema, etc.
  • Transport layers: HttpTransportLive, WebSocketTransportLive (for Effect-based usage)
  • All TypeScript types: ObjectCallRequest, ObjectCallResponse, FunctionMetadata, etc.
import { buildCallRequest, BatchBuilder } from "unreal-rc";

// Build a raw request body
const body = buildCallRequest({
  objectPath: path,
  functionName: "SetActorHiddenInGame",
  parameters: { bNewHidden: false }
});
// Use with your own HTTP client, CLI tool, etc.

License

MIT