Remote Procedure Calls, or RPCs, are a way for an external program (eg. a frontend) to communicate with a Substrate node. They are used for checking storage values, submitting transactions, and querying the current consensus authorities. Substrate comes with several default RPCs. In many cases it is useful to add custom RPCs to your node.

The RPC extensions builder

In order to connect custom RPCs you must provide a function known as an "RPC extension builder". This function takes a parameter for whether the node should deny unsafe RPC calls, and returns an IoHandler that the node needs to create a JSON RPC. For more context, read more by looking at the RpcExtensionBuilder trait API documentation.

RPC types

RPCs can be interfaces to a node's consensus mechanisms, or interfaces with any outside user to submit transactions to the blockchain. In all cases, it's important to consider what endpoints RPCs expose.

Launch a node and run this command to see a full list of your node's RPC APIs:

curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "rpc_methods"}' http://localhost:9933/

Public RPCs

Substrate nodes contain a set of CLI flags that allow you to expose the RPC interfaces publicly, namely:


Each have additional --unsafe-* versions. By default the node will refuse to start if you try to expose an RPC interface and run a validator node at the same time. --unsafe-* flags allow to suppress this security measure. Exposing RPC interfaces may open up a huge surface of attacks and has to be carefully reviewed.

There are quite a few RPC methods that can be used to control the node's behaviour and should never (or rarely) be exposed:

Other RPCs are not so much dangerous but can take a long time to execute, potentially blocking the client from syncing. These include:

  • storage_keys_paged - get all the keys in the state with a particular prefix and pagination support.
  • state_getPairs - get all the keys in the state with a particular prefix together with their values.

These RPCs are declared by using the #[rpc(name = "rpc_method")] macro, where "rpc_method" would be the name of the function — author_submitExtrinsic for example, corresponding to submit_extrinsic.

It's critical to filter out these kind of calls if the requests are coming from untrusted users. The way to do it is through a JSON-RPC proxy that is able to inspect calls and only pass allowed-set of APIs.

RPCs for remote_externalities

There exists a special type of using RPCs in the context of remote_externalities. This rpc_api allows you to make one-off RPC calls to a Substrate node, useful for testing purposes with tools like try-runtime for example.


When starting any Substrate node, these two endpoints are made available to you:

  • HTTP Endpoint: http://localhost:9933/
  • Websocket Endpoint: ws://localhost:9944/

Most of the Substrate front-end libraries and tools use the more powerful WebSocket endpoint to interact with the blockchain. Through WebSockets, you can subscribe to the chain states, such as events, and receive push notifications whenever changes in your blockchain occur.

To call the Metadata endpoint, run this command alongside a running node:

curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "state_getMetadata"}' http://localhost:9933/

The return value of this command is not in human readable format. For that, it needs to use the SCALE codec encoding scheme.

Each storage item has a relative storage key associated to it which is used to query storage. This is how RPC endpoints know where to look.



RPC request:

function get_metadata_request(endpoint) {
  let request = new Request(endpoint, {
    method: 'POST',
    body: JSON.stringify({
      id: 1,
      jsonrpc: '2.0',
      method: 'state_getMetadata',
    headers: { 'Content-Type': 'application/json' },
  return request

Naive text decoding:

function decode_metadata(metadata) {
  return new TextDecoder().decode(util.hexToU8a(metadata))


RPC request:

Request:   {"id":1,"jsonrpc":"2.0","method":"state_getStorage",["{storage_key}"]}

Where storage_key is a parameter generated by the name of a pallet, function and key (optionally):

function get_runtime_storage_parameter_with_key(
) {
  // We use xxhash 128 for strings the runtime developer can control
  let module_hash = util_crypto.xxhashAsU8a(module_name, 128)
  let function_hash = util_crypto.xxhashAsU8a(function_name, 128)

  // We use blake2 256 for strings the end user can control
  let key_hash = util_crypto.blake2AsU8a(keyToBytes(key))

  // Special syntax to concatenate Uint8Array
  let final_key = new Uint8Array([

  // Return a hex string
  return util.u8aToHex(final_key)

Further learning

Last edit: on

Run into problems?
Let us Know