Skip to content

Limits

Explicit-subject and current-subject API limits differ. In particular, the OpenFGA admin list functions cap their results, while the current-subject list functions return every accessible result, whether called from application code or RLS. The Cedar functions have size limits on policies and entities, and no cap on results.

Use a test database and a server client named permissions. The batching examples assume the document#viewer model from the error examples.

The sections Limits (OpenFGA) and Cedar limits list the limits of each model. The rbac_* functions have no limits.

The limits of the API functions are the default limits of OpenFGA. You cannot change them.

Limit Value What happens when you go over it
Tuple keys in one fga_write call, writes and deletes together 100 Error openfga:2053.
Results of fga_list_objects and fga_list_users 1,000 Additional results are silently omitted.
Tuples in one page of fga_read 50 by default. page_size can be 1 to 100. Another page_size is error openfga:2000.
Length of a continuation_token 5,120 bytes Error openfga:2000.
Levels in one Check, ListObjects or ListUsers 25 Error openfga:2002, with SQLSTATE 54001.
Length of a type name 254 characters Error openfga:2047.
Length of a relation name 50 characters Error openfga:2050.
Length of an expression of a condition 512 bytes Error openfga:2000.
Parameters of a condition 25 Error openfga:2000.
Length of the name of a condition or a parameter 50 characters Error openfga:2000.
Cost of the evaluation of a condition 100 steps Error openfga:2000 when the extension evaluates the condition.

The current-subject functions fga_list_objects_current and fga_list_object_ids_current have no cap on the number of results. A cap there would hide rows and give no sign of it. fga_check_current has the same depth limit as fga_check. The rbac_* functions have no limits.

The extension has no built-in time limit on API calls. OpenFGA stops a list after 3 seconds and can return partial results. To bound execution in Postgres, set statement_timeout; exceeding it raises an error instead of returning a partial list.

To write more than 100 tuples, write them in batches. These examples commit every batch together.

import pg from 'pg'
import { createServerClient } from 'permissions'
async function writeBatches(write) {
const db = new pg.Client({ connectionString: process.env.DATABASE_URL })
await db.connect()
try {
await db.query('begin')
await write(createServerClient(db, { throwOnError: true }))
await db.query('commit')
} finally {
// Closing the connection rolls back any uncommitted writes.
await db.end()
}
}
const tuples = Array.from({ length: 200 }, (_, i) => ({
user: 'user:u' + (i + 1), relation: 'viewer', object: 'document:big',
}))
await writeBatches(async (client) => {
for (let start = 0; start < tuples.length; start += 100) {
await client.fga.admin.write({ writes: tuples.slice(start, start + 100) })
}
})

A call with 101 tuple keys fails:

const writes = Array.from({ length: 101 }, (_, i) => ({
user: 'user:v' + (i + 1),
relation: 'viewer',
object: 'document:big',
}))
const { error } = await permissions.fga.admin.write({ writes })
console.log(error?.reason?.code) // 2053

These limits are fixed and exceed the maxima in the tested Cedar corpus. The extension has no evaluation-step counter. Although Cedar has no loops, the number of candidate policies, the entity hierarchy and collection sizes still affect the work of a request. Use statement_timeout to bound execution time.

Limit Value What happens when you go over it
Text of one policy or template 64 KiB Error cedar:limit_exceeded with limit='policy_bytes'.
Expression nodes in one policy 4,096 Error cedar:limit_exceeded with limit='policy_nodes'.
Nesting depth of one expression 64 Error cedar:limit_exceeded with limit='policy_depth'. The parser checks it too.
Nesting of sets and records in entity data and in the context 32 Error cedar:limit_exceeded with limit='value_depth'. The entity error also has the index of the entity.
Entities in one cedar_put_entities call 10,000 Error cedar:limit_exceeded with limit='put_entities'.
Entities in the entities argument of one cedar_is_authorized call 1,000 Error cedar:limit_exceeded with limit='request_entities'.
Depth of the entity hierarchy None. A cycle is an error. Not applicable.
Number of policies, templates and links None Not applicable.
Results of cedar_list_resources_current None Not applicable.
Size of a Long -2^63 to 2^63-1 An entity with another number is cedar:invalid_entity. A result that goes beyond the range is an overflow error of the policy.

cedar_list_resources_current has no result cap: a cap could silently hide rows from an RLS query. cedar_is_authorized_current returns a single boolean. The extension has no built-in time limit; exceeding Postgres’s statement_timeout raises an error rather than returning a partial result.

To store more than 10,000 entities, put them in batches. These examples commit every batch together.

const items = Array.from({ length: 20000 }, (_, i) => ({
uid: { type: 'Item', id: 'i' + (i + 1) },
}))
// Use the writeBatches helper in the tuple example above.
await writeBatches(async (client) => {
for (let start = 0; start < items.length; start += 10000) {
await client.cedar.admin.putEntities(items.slice(start, start + 10000))
}
})

A call with 10,001 entities fails:

const entities = Array.from({ length: 10001 }, (_, i) => ({
uid: { type: 'Big', id: 'b' + (i + 1) },
}))
const { error } = await permissions.cedar.admin.putEntities(entities)
console.log(error?.reason?.code) // 'limit_exceeded'