Skip to main content
Version: 1.0

RPC Handlers

User scripts can register custom RPC methods on the device using Script.addRpcHandler and Script.removeRpcHandler. Registered methods are callable via HTTP, WebSocket, MQTT, or any other RPC channel, just like built-in component methods.

Overview

Registered methods use the Script.<MethodName> namespace and are routed to the correct script instance via the id parameter in the RPC request. They are callable over any RPC channel and follow the standard RPC protocol, just like built-in component methods. Multiple scripts can register handlers for the same method name; the id parameter determines which script handles the call.

This lets a script expose a clean, structured RPC API to external integrations, instead of parsing raw HTTP requests with HTTPServer. Responses go through the standard RPC machinery, so error codes, authentication, and channel framing all work the same way as for native device methods.

Quick Example

Script.addRpcHandler("Hello", function (request, params) {
request.result({greeting: "Hello, " + (params.name || "world") + "!"});
});

Call it:

curl http://<device_ip>/rpc/Script.Hello -d '{"id":0,"name":"Alice"}'
# {"greeting":"Hello, Alice!"}

API Reference

Script.addRpcHandler()

Registers a handler for a custom RPC method.

Script.addRpcHandler(method, callback[, userdata]) -> number

Parameters

PropertyTypeDescription

method

string

Method name (max 32 characters). Exposed as Script.MethodName.

callback

function

Handler function, called as callback(request, params, userdata)

PropertyTypeDescription

request

object

The RPCall object used to respond to the call. See RPCall object.

params

object

The params object from the RPC request (includes the routing id).

userdata

any

The userdata passed during registration.

userdata

any

Optional data passed as the third argument to the callback.

Return Value

Returns a handler handle (a number greater than 0) on success, or 0 on error.

Errors

The following conditions raise a JavaScript exception (and return 0):

  • Method name is empty or longer than 32 characters.
  • Method name is reserved. Reserved names are the built-in Script component methods: GetStatus, GetConfig, SetConfig, Start, Stop, Eval, PutCode, GetCode, Create, Delete, List.
  • Too many handlers are already registered (default limit: 5 per script).

Script.removeRpcHandler()

Removes a previously registered RPC handler.

Script.removeRpcHandler(handle) -> boolean

Parameters

PropertyTypeDescription

handle

number

Handle returned by Script.addRpcHandler.

Return Value

Returns true if the handler was removed, false if no handler with that handle exists.

RPCall object

The request argument passed to the handler callback is an RPCall object representing the incoming call. It carries metadata about the caller and provides the methods used to respond.

Properties

PropertyTypeDescription

method

string

Full RPC method name (e.g. "Script.Toggle").

src

string

Source address of the caller.

dst

string

Destination address.

user

string

Authenticated username, if any.

ch_type

string

Channel type the call arrived on (e.g. "http", "ws", "mqtt").

request.result()

Sends a successful response.

request.result(data) -> boolean
PropertyTypeDescription

data

any

Any JSON-serializable value (object, array, string, number, boolean, or null) to return to the caller.

request.error()

Sends an error response.

request.error(code, message) -> boolean
PropertyTypeDescription

code

number

Error code (e.g. -1, 404, 500).

message

string

Error message.

caution

Exactly one of result() or error() must be called for each request. If neither is called within 5 seconds, the framework automatically responds with a timeout error.

Limits

LimitDefaultBuild variable
Max handlers per script5SHELLY_SCRIPT_MAX_RPC_HANDLERS
Max concurrent in-flight calls5SHELLY_SCRIPT_MAX_RPC_HANDLER_CALLS
Handler response timeout5 sSHELLY_SCRIPT_RPC_HANDLER_TIMEOUT_MS
Method name max length32SHELLY_SCRIPT_RPC_METHOD_MAX_LEN

Lifecycle

  • Handlers are automatically unregistered when the script stops or crashes.
  • In-flight calls to a stopped script receive a 503 (Unavailable) error.
  • Handlers are per-script-instance; restarting a script clears all of its handlers, so the script must re-register them on startup.

Complete Examples

Basic handler

Script.addRpcHandler("Hello", function (request, params) {
request.result({greeting: "Hello, " + (params.name || "world") + "!"});
});

Call: curl http://<device_ip>/rpc/Script.Hello -d '{"id":0,"name":"Alice"}'

Response: {"greeting":"Hello, Alice!"}

Toggle a switch

Script.addRpcHandler("Toggle", function (request, params) {
let sw = Shelly.getComponentStatus("switch", 0);
if (!sw) {
request.error(-1, "switch not found");
return;
}
Shelly.call("Switch.Toggle", {id: 0}, function (result, code, msg) {
if (code !== 0) {
request.error(code, msg);
} else {
request.result({was: sw.output, now: !sw.output});
}
});
});

Using userdata

let config = {multiplier: 2};

Script.addRpcHandler("Multiply", function (request, params, ud) {
if (typeof params.value !== "number") {
request.error(-1, "value must be a number");
return;
}
request.result({result: params.value * ud.multiplier});
}, config);

Error handling

Script.addRpcHandler("Divide", function (request, params) {
if (typeof params.a !== "number" || typeof params.b !== "number") {
request.error(-1, "a and b must be numbers");
return;
}
if (params.b === 0) {
request.error(-1, "division by zero");
return;
}
request.result({result: params.a / params.b});
});

Removing a handler

let h = Script.addRpcHandler("Temp", function (request, params) {
request.result({ok: true});
});

// Later, when no longer needed:
Script.removeRpcHandler(h);