npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@open-policy-agent/opa

v2.0.0

Published

Driver to connect to Open Policy Agent (OPA) and EOPA deployments.

Readme

OPA Typescript SDK

Driver to connect to Open Policy Agent (OPA) and EOPA deployments.

License NPM Version JSR

Reference documentation available at https://open-policy-agent.github.io/opa-typescript

You can use the OPA SDK to connect to Open Policy Agent and EOPA deployments.

SDK Installation

NPM

npm add @open-policy-agent/opa

PNPM

pnpm add @open-policy-agent/opa

Bun

bun add @open-policy-agent/opa

Yarn

yarn add @open-policy-agent/opa zod

# Note that Yarn does not install peer dependencies automatically. You will need
# to install zod as shown above.

Summary

For more information about the API: OpenAPI definition

Table of Contents

Requirements

For supported JavaScript runtimes, please consult RUNTIMES.md.

SDK Example Usage (high-level)

The following examples assume an OPA server equipped with the following Rego policy:

package authz
import rego.v1

default allow := false
allow if input.subject == "alice"

and this data:

{
  "roles": {
    "admin": ["read", "write"]
  }
}

Simple Query

For a simple boolean response without input, use the SDK as follows:

import { OPAClient } from "@open-policy-agent/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "authz/allow";

const allowed = await opa.evaluate(path);
console.log(allowed ? "allowed!" : "denied!");

Default Rule

For evaluating the default rule (configured with your OPA service), use evaluateDefault. input is optional, and left out in this example:

import { OPAClient } from "@open-policy-agent/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);

const allowed = await opa.evaluateDefault();
console.log(allowed ? "allowed!" : "denied!");

Input

Input is provided as a second (optional) argument to evaluate:

import { OPAClient } from "@open-policy-agent/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "authz/allow";

const input = { subject: "alice" };
const allowed = await opa.evaluate(path, input);
console.log(allowed ? "allowed!" : "denied!");

Default Rule with Input

Input is provided as an (optional) argument to evaluateDefault:

import { OPAClient } from "@open-policy-agent/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);

const input = { subject: "alice" };
const allowed = await opa.evaluateDefault(input);
console.log(allowed ? "allowed!" : "denied!");

[!NOTE] Everything that follows applies in the same way to evaluateDefault and evaluate.

Input and Result Types

It's possible to provide your own types for input and results. The evaluate function will then return a typed result, and TypeScript will ensure that you pass the proper types (as declared) to evaluated.

import { OPAClient } from "@open-policy-agent/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "authz";

interface myInput {
  subject: string;
}
interface myResult {
  allow: boolean;
}
const input: myInput = { subject: "alice" };
const result = await opa.evaluate<myInput, myResult>(path, input);
console.log(result);

If you pass in an arbitrary object as input, it'll be stringified (JSON.stringify):

import { OPAClient } from "@open-policy-agent/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "authz/allow";

class User {
  subject: string;
  constructor(name: string) {
    this.subject = name;
  }
}

const inp = new User("alice");
const allowed = await opa.evaluate<User, boolean>(path, inp);
console.log(allowed);

You can control the input that's constructed from an object by implementing ToInput:

import { OPAClient, ToInput } from "@open-policy-agent/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "authz/allow";

class User implements ToInput {
  private n: string;
  constructor(name: string) {
    this.n = name;
  }
  toInput(): Input {
    return { subject: this.n };
  }
}

const inp = new User("alice");
const allowed = await opa.evaluate<User, boolean>(path, inp);
console.log(allowed);

Result Transformations

If the result format of the policy evaluation does not match what you want it to be, you can provide a third argument, a function that transforms the API result.

Assuming that the policy evaluates to

{
  "allowed": true,
  "details": ["input.a is OK", "input.b is OK"]
}

like this (contrived) example:

package authz
import rego.v1
good_a := ["a", "A", "A!"]
good_b := ["b"]
response.allowed if input.subject == "alice"
response.details contains "input.a is OK" if input.a in good_a
response.details contains "input.b is OK" if input.b in good_b

you can turn it into a boolean result like this:

import { OPAClient } from "@open-policy-agent/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "authz/response";
const input = { subject: "alice", a: "A", b: "b" };

const allowed = await opa.evaluate<any, boolean>(
  path,
  input,
  {
    fromResult: (r?: Result) => (r as Record<string, any>)["allowed"] ?? false,
  },
);
console.log(allowed);

Batched Queries

import { OPAClient } from "@open-policy-agent/opa";

const serverURL = "http://localhost:8181";
const path = "authz/allow";
const opa = new OPAClient(serverURL);

const alice = { subject: "alice" };
const bob = { subject: "bob" };
const inputs = { alice: alice, bob: bob };
const responses = await opa.evaluateBatch(path, inputs);

for (const key in responses) {
    console.log(key + ": " + (responses[key] ? "allowed!" : "denied!"));   // Logic here
}
alice: allowed!
bob: denied!

Get Filters

To use the translation of Rego data filter policies into SQL or UCAST expressions, you need to use a recent version of OPA (>=v1.9.0) or EOPA (>=v1.44.0). These examples assume you run OPA or EOPA with the following Rego policy:

package filters

# METADATA
# scope: document
# compile:
#   unknowns: ["input.fruits"]
#   mask_rule: masks
include if input.fruits.colour in input.fav_colours

masks.fruits.supplier.replace.value := "<supplier>"

For Prisma

import { OPAClient } from "@open-policy-agent/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "filters/include";
const input = { fav_colours: ["red", "green"] };
const primary = "fruits";

const { query, mask } = await opa.getFilters(path, input, primary);
console.log(query);

Here, query is an object that can readly be used in a Prisma lookup's where field:

{ colour: { in: [ "red", "green" ] } }

mask is a function that can be applied to the values returned by that lookup.

For example:

const { query, mask } = await opa.getFilters(path, input, primary);
const fruits = (
  await prisma.fruits.findMany({
    where: query,
  })
).map((fruit) => mask(fruit));

For SQL

import { OPAClient } from "@open-policy-agent/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "filters/include";
const input = { fav_colours: ["red", "green"] };
const opts = { target: "postgresql" };

const { query, masks } = await opa.getFilters(path, input, opts);
console.log({ query, masks });

Here we get a SQL WHERE clause as query,

WHERE fruits.colour IN (E'red', E'green')

and masks contains the evaluated mask rule:

{ fruits: { supplier: { replace: { value: "<supplier>" } } } }

Table name mappings

Generate a SQL filter with different column and table names via tableMappings:

const opts = {
  target: "postgresql",
  tableMappings: {
    "fruits": { $self: "f", colour: "col"}
  }
};

this will generate the SQL clause

WHERE f.col IN (E'red', E'green')

For multiple data sources

import { OPAClient } from "@open-policy-agent/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "filters/include";
const input = { fav_colours: ["red", "green"] };
const opts = { targets: ["postgresql", "mysql", "ucastPrisma"] };

const result = await opa.getMultipleFilters(path, input, opts);
console.dir(result, {depth: null});

This produces an object keyed by the requested targets:

{
  ucast: {
    query: {
      type: "field",
      operator: "in",
      field: "fruits.colour",
      value: [ "red", "green" ]
    },
    masks: {
      fruits: { supplier: { replace: { value: "<supplier>" } } }
    }
  },
  postgresql: {
    query: "WHERE fruits.colour IN (E'red', E'green')",
    masks: {
      fruits: { supplier: { replace: { value: "<supplier>" } } }
    }
  },
  mysql: {
    query: "WHERE fruits.colour IN ('red', 'green')",
    masks: {
      fruits: { supplier: { replace: { value: "<supplier>" } } }
    }
  }
}

Advanced options

Request Headers

You can provide your custom headers -- for example for bearer authorization -- via an option argument to the OPAClient constructor.

import { OPAClient } from "@open-policy-agent/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL, { headers: { authorization: "Bearer opensesame" } });
const path = "authz/allow";
const allowed = await opa.evaluate(path);
console.log(allowed);

HTTPClient

You can supply an instance of HTTPClient to supply your own hooks, for example to examine the request sent to OPA:

import { OPAClient } from "@open-policy-agent/opa";
import { HTTPClient } from "@open-policy-agent/opa/lib/http";
const httpClient = new HTTPClient({});
httpClient.addHook("response", (response, request) => {
  console.group("Request Debugging");
  console.log(request.headers);
  console.log(`${request.method} ${request.url} => ${response.status} ${response.statusText}`);
  console.groupEnd();
});
const serverURL = "http://localhost:8181";
const headers = { authorization: "Bearer opensesame" };
const opa = new OPAClient(serverURL, { sdk: { httpClient }, headers });
const path = "authz/allow";

const allowed = await opa.evaluate(path);
console.log(allowed);

Example Projects

Express

In the StyraOSS/styra-demo-tickethub repository, you'll find a NodeJS backend service that is using @open-policy-agent/opa:

router.get("/tickets/:id", [param("id").isInt().toInt()], async (req, res) => {
  const {
    params: { id },
  } = req;
  await authz.evaluated(path, { action: "get", id }, req);

  const ticket = await prisma.tickets.findUniqueOrThrow({
    where: { id },
    ...includeCustomers,
  });
  return res.status(OK).json(toTicket(ticket));
});

NestJS

In opa-sdk-demos/nestjs-demo, we have an decorator-based API authorization example using @open-policy-agent/opa:

@Controller("cats")
@AuthzQuery("cats/allow")
@AuthzStatic({ resource: "cat" })
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Post()
  @Authz(({ body: { name } }) => ({ name, action: "create" }))
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get(":name")
  @AuthzQuery("cats") // For illustration, we're querying the package extent
  @Decision((r) => r.allow)
  @Authz(({ params: { name } }) => ({
    name,
    action: "get",
  }))
  async findByName(@Param("name") name: string): Promise<Cat> {
    return this.catsService.findByName(name);
  }
}

Please refer to the repository's README.md for more details.

[!NOTE] For low-level SDK usage, see the sections below.


OPA OpenAPI SDK (low-level)

Available Resources and Operations

OpaApiClient SDK

Retries

Some of the endpoints in this SDK support retries. If you use the SDK without any configuration, it will fall back to the default retry strategy provided by the API. However, the default retry strategy can be overridden on a per-operation basis, or across the entire SDK.

To change the default retry strategy for a single API call, simply provide a retryConfig object to the call:

import { OpaApiClient } from "@open-policy-agent/opa";

const opaApiClient = new OpaApiClient();

async function run() {
  const result = await opaApiClient.executeDefaultPolicyWithInput(4963.69, {
    retries: {
      strategy: "backoff",
      backoff: {
        initialInterval: 1,
        maxInterval: 50,
        exponent: 1.1,
        maxElapsedTime: 100,
      },
      retryConnectionErrors: false,
    },
  });

  // Handle the result
  console.log(result);
}

run();

If you'd like to override the default retry strategy for all operations that support retries, you can provide a retryConfig at SDK initialization:

import { OpaApiClient } from "@open-policy-agent/opa";

const opaApiClient = new OpaApiClient({
  retryConfig: {
    strategy: "backoff",
    backoff: {
      initialInterval: 1,
      maxInterval: 50,
      exponent: 1.1,
      maxElapsedTime: 100,
    },
    retryConnectionErrors: false,
  },
});

async function run() {
  const result = await opaApiClient.executeDefaultPolicyWithInput(4963.69);

  // Handle the result
  console.log(result);
}

run();

Authentication

Per-Client Security Schemes

This SDK supports the following security scheme globally:

| Name | Type | Scheme | | ------------ | ---- | ----------- | | bearerAuth | http | HTTP Bearer |

To authenticate with the API the bearerAuth parameter must be set when initializing the SDK client instance. For example:

import { OpaApiClient } from "@open-policy-agent/opa";

const opaApiClient = new OpaApiClient({
  bearerAuth: "<YOUR_BEARER_TOKEN_HERE>",
});

async function run() {
  const result = await opaApiClient.executeDefaultPolicyWithInput(4963.69);

  // Handle the result
  console.log(result);
}

run();

Debugging

You can setup your SDK to emit debug logs for SDK requests and responses.

You can pass a logger that matches console's interface as an SDK option.

[!WARNING] Beware that debug logging will reveal secrets, like API tokens in headers, in log messages printed to a console or files. It's recommended to use this feature only during local development and not in production.

import { OpaApiClient } from "@open-policy-agent/opa";

const sdk = new OpaApiClient({ debugLogger: console });

Standalone functions

All the methods listed above are available as standalone functions. These functions are ideal for use in applications running in the browser, serverless runtimes or other environments where application bundle size is a primary concern. When using a bundler to build your application, all unused functionality will be either excluded from the final bundle or tree-shaken away.

To read more about standalone functions, check FUNCTIONS.md.

Community

If there's something you'd like to have added to the roadmap, either open an issue, or reach out in the community Slack!