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
| Property | Type | Description | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| string | Method name (max 32 characters). Exposed as | ||||||||||||
| function | Handler function, called as
| ||||||||||||
| 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
Scriptcomponent 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
| Property | Type | Description |
|---|---|---|
| number | Handle returned by |
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
| Property | Type | Description |
|---|---|---|
| string | Full RPC method name (e.g. |
| string | Source address of the caller. |
| string | Destination address. |
| string | Authenticated username, if any. |
| string | Channel type the call arrived on (e.g. |
request.result()
Sends a successful response.
request.result(data) -> boolean
| Property | Type | Description |
|---|---|---|
| any | Any JSON-serializable value (object, array, string, number, boolean, or |
request.error()
Sends an error response.
request.error(code, message) -> boolean
| Property | Type | Description |
|---|---|---|
| number | Error code (e.g. |
| string | Error message. |
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
| Limit | Default | Build variable |
|---|---|---|
| Max handlers per script | 5 | SHELLY_SCRIPT_MAX_RPC_HANDLERS |
| Max concurrent in-flight calls | 5 | SHELLY_SCRIPT_MAX_RPC_HANDLER_CALLS |
| Handler response timeout | 5 s | SHELLY_SCRIPT_RPC_HANDLER_TIMEOUT_MS |
| Method name max length | 32 | SHELLY_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);