> ## Documentation Index
> Fetch the complete documentation index at: https://upstash.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# FCALL_RO

> Call a read-only function.

Use `FCALL_RO` to invoke a function that is declared read-only.

The function must have been registered with the `no-writes` flag; calling a function without it returns an error. In exchange the server knows the call cannot modify data, so it can serve it on replicas and reject accidental writes outright.

Apart from that restriction it behaves like [`FCALL`](/docs/redis/commands/functions/fcall): `<numkeys>` splits the arguments into the keys the function receives in `KEYS` and the plain arguments it receives in `ARGV`.

Being read-only does not by itself make the call concurrent with others. The function takes the global lock unless it was also registered with the `allow-key-locking` flag, as in `flags={'no-writes', 'allow-key-locking'}`. With both flags, the call takes shared read locks on the keys passed in the key list, so several readers of the same key proceed together. See [Key-Based Locking](/docs/redis/features/key-locking).

## Syntax

```redis theme={"system"}
FCALL_RO <function> <numkeys> [<key> [<key> ...]] [<arg> [<arg> ...]]
```

## Arguments

| Argument | Required | Repeatable | Description |
| - | - | - | - |
| `<function>` | Yes | No | Name of the registered function to call. |
| `<numkeys>` | Yes | No | Number of key arguments that follow. |
| `<key>` | No | Yes | Redis key targeted by the command. |
| `<arg>` | No | Yes | Additional argument passed to the function. |

## Important points

* `numkeys` must equal the number of key arguments that immediately follow it; remaining arguments are available to the script or function as ordinary arguments.
* A `no-writes` function still takes the global lock unless it was also registered with the `allow-key-locking` flag. See [Key-Based Locking](/docs/redis/features/key-locking).
* Pass every key the function reads in the key list when possible. With `allow-key-locking`, a dynamic key is accepted only when its [hash tag](/docs/redis/features/key-locking#hash-tags) is already locked, and a cold dynamic key is loaded with that lock held. See [Dynamic Keys and Latency](/docs/redis/features/key-locking#dynamic-keys-and-latency).

## Reply conversion

`redis.setresp()` and the RESP2 and RESP3 conversions applied to `redis.call` replies work exactly as they do for [`EVAL`](/docs/redis/commands/scripting/eval#reply-conversion).

## Response

The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.

| Protocol | Reply |
| - | - |
| RESP2 | Reply returned by the invoked read-only function |
| RESP3 | Reply returned by the invoked read-only function |

> [!NOTE]
>
> Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.

## Examples

TCP examples use the TLS `REDIS_URL` from the Upstash console. REST examples use `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.

> **Redis CLI**
>
> ```bash theme={"system"}
> FCALL_RO my_function 1 my-key value
> ```

> **@upstash/redis**
>
> ```ts theme={"system"}
> import { Redis } from "@upstash/redis";
>
> const redis = Redis.fromEnv();
>
> const code = `
> #!lua name=ro_lib
>
> local function get_value(keys, args)
>   return redis.call('GET', keys[1])
> end
>
> redis.register_function({
>   function_name='get_value',
>   callback=get_value,
>   flags={ 'no-writes' }
> })
> `;
>
> await redis.functions.load({ code, replace: true });
>
> // Call the read-only function
> // Note: We can modify the keys usage here, but since it represents a read-only operation
> // and we marked it with 'no-writes', it is safe to use callRo.
> const value = await redis.functions.callRo("get_value", ["mykey"])
> ```

> **upstash\_redis**
>
> > [!NOTE]
> >
> > This command is not supported yet in `upstash_redis`.

> **ioredis**
>
> ```ts theme={"system"}
> import Redis from "ioredis";
>
> const redis = new Redis(process.env.REDIS_URL!);
> const result = await redis.fcall_ro("my_function", "1", "my-key", "value");
> console.log(result);
> ```

> **node-redis**
>
> ```ts theme={"system"}
> import { createClient } from "redis";
>
> const client = await createClient({ url: process.env.REDIS_URL })
>   .on("error", console.error)
>   .connect();
> const result = await client.fCallRo("my_function", { keys: ["my-key"], arguments: ["value"] });
> console.log(result);
> ```

> **redis-py**
>
> ```python theme={"system"}
> import os
> import redis
>
> client = redis.from_url(os.environ["REDIS_URL"])
> result = client.fcall_ro("my_function", 1, "my-key", "value")
> print(result)
> ```

> **go-redis**
>
> ```go theme={"system"}
> package main
>
> import (
>     "context"
>     "fmt"
>     "os"
>
>     "github.com/redis/go-redis/v9"
> )
>
> func main() {
>     opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
>     if err != nil {
>         panic(err)
>     }
>     client := redis.NewClient(opts)
>     result, err := client.FCallRO(context.Background(), "my_function", []string{"my-key"}, "value").Result()
>     if err != nil {
>         panic(err)
>     }
>     fmt.Println(result)
> }
> ```

> **jedis**
>
> ```java theme={"system"}
> import java.net.URI;
>
> import redis.clients.jedis.Jedis;
>
> try (Jedis jedis = new Jedis(new URI(System.getenv("REDIS_URL")))) {
>   Object result = jedis.fcallReadonly("my_function", java.util.List.of("my-key"), java.util.List.of("value"));
>   System.out.println(result);
> }
> ```

> **redis-rs**
>
> ```rust theme={"system"}
> fn main() -> redis::RedisResult<()> {
>     let url = std::env::var("REDIS_URL").expect("REDIS_URL is not set");
>     let client = redis::Client::open(url)?;
>     let mut connection = client.get_connection()?;
>
>     let mut command = redis::cmd("FCALL_RO");
>     command.arg("my_function");
>     command.arg("1");
>     let result: redis::Value = command.query(&mut connection)?;
>     println!("{result:?}");
>     Ok(())
> }
> ```


## Related topics

- [FCALL_RO](/docs/redis/sdks/ts/commands/functions/call_ro.md)
- [FCALL](/docs/redis/commands/functions/fcall.md)
- [Key-Based Locking](/docs/redis/features/key-locking.md)
- [Changelog](/docs/redis/overall/changelog.md)
- [FUNCTION LOAD](/docs/redis/commands/functions/function-load.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.