Interactive React app for exploring and editing Ory Keto permission graphs. Works in two modes:
- Offline — loads bundled example data, fully editable, no Keto instance needed
- Live — connects to your Ory Network project, fetches real tuples and runs live permission checks
Select a use case and a user to see their permission graph: which entities they're connected to, through what relations, and what permissions they have on each resource.
- Nodes are color-coded by namespace (User, Role, Customer, etc.)
- Edges show relation names (members, parent_lob, allowed_roles, etc.)
- The sidebar shows direct relations and permission results (ALLOWED/DENIED)
In offline mode, a Schema panel at the bottom of the graph shows the OPL (Ory Permission Language) TypeScript source for the selected example:
- Expand the panel to read the full namespace definitions (
relatedblocks andpermitslogic) - Edit the schema directly in the textarea — useful for exploring what-if changes locally
- An "edited" badge marks when the local copy differs from the original
- Reset to original restores the bundled schema
- Switching examples automatically loads the new schema and discards local edits
- Local only — to push changes to a live Ory instance, run
ory update opl --file namespace.ts
The Keto API does not expose OPL at runtime, so the Schema Editor is unavailable in Live mode.
In offline mode, a Relationships panel at the bottom of the graph lets you edit the tuple data live:
- Expand the panel to see all relation tuples in a scrollable table
- Delete any tuple — the graph updates immediately
- Edit any tuple inline: hover a row to reveal the ✏ button, which turns the row into editable inputs; press Enter to save or Escape to cancel. Editing a base tuple saves it as a custom row (marked with a blue border)
- Subject format in the edit field: plain
alicefor a direct subject ID, orNamespace:object/Namespace:object#relationfor a subject set - Add new tuples via a form: pick namespace, object, relation, and a subject (User ID or Subject Set)
- Newly added
subject_idvalues appear in the user dropdown straight away - An "edited" badge marks when changes are active
- Reset to default restores the original example data
- Node.js 20+
- An Ory Network project with OPL and tuples loaded (optional — the app works offline with bundled examples)
cd permission-visualizer
make devOpen http://localhost:5173 — the app detects that no API is configured and uses bundled offline data.
- Copy the env file and fill in your credentials:
cd permission-visualizer
cp .env.example .env
# Edit .env with your ORY_SDK_URL and ORY_ACCESS_TOKEN- Seed an example into Keto:
make seed EXAMPLE=b2b-hierarchy # or any example from examples/- Start the dev server:
make devOpen http://localhost:5173. The Vite dev server proxies /api/* requests to your Ory project, injecting the access token so the browser never sees the PAT.
make docker-build
make docker-run # reads .env for ORY_SDK_URL and ORY_ACCESS_TOKENThe production container uses a zero-dependency Node.js server (server.js) that serves the built SPA and proxies /api/* to Ory. Runs on port 3000 by default.
Browser (React + Cytoscape.js)
|
| /api/relation-tuples
| /api/relation-tuples/check
| /api/namespaces
▼
Vite Dev Proxy (:5173/api/*) — or — server.js (:3000/api/*)
|
| + Authorization: Bearer <PAT>
▼
Ory Network API (ORY_SDK_URL)
- Fetch namespaces — gets the list of entity types from the current OPL
- Fetch all tuples — paginated fetch across all namespaces
- Derive users — extracts all
subject_idvalues from tuples - Build graph — when a user is selected, traces their connections through the tuple graph
- Check permissions — runs permission checks for all relevant resources and displays ALLOWED/DENIED badges
If no API is reachable, the app falls back to bundled offline data generated from examples/.
Environment variables are read from permission-visualizer/.env:
| Variable | Default | Description |
|---|---|---|
ORY_ACCESS_TOKEN |
(required) | PAT injected into proxy requests |
ORY_TUNNEL_URL |
http://localhost:4000 |
Where the ory tunnel is running |
ORY_SDK_URL |
— | Your Ory project URL (e.g. https://your-project.projects.oryapis.com) |
ORY_ACCESS_TOKEN |
— | Ory PAT, injected into proxy requests |
All 7 examples live in permission-visualizer/examples/ and are available in the app dropdown:
| Use Case | Interesting Users to Try |
|---|---|
| RBAC App Access | alice (admin — all access), eve (viewer — limited) |
| RBAC Bank Accounts | john-smith (owner), james (teller), kevin (branch_admin) |
| RAG Document Access | alice (owner + team), oscar (no access) |
| B2B Hierarchy | ceo-pat (sees everything), mgr-retail-lisa (one LOB), rep-saas-yara (one customer) |
| SaaS Feature Gating | alice (enterprise), eve (free tier) |
| Healthcare Records | dr-jones (multi-patient), dr-garcia (emergency access), dr-specialist-lee (consented) |
| Content Publishing | writer-alice (drafts), editor-diana (review), publisher-frank (publish) |
Run all targets from permission-visualizer/:
| Target | Description |
|---|---|
make dev |
Start the Vite dev server |
make build |
Install deps and create the production bundle |
make seed EXAMPLE=<name> |
Seed a use case into Ory Keto |
make generate-offline |
Regenerate offline data from examples/ |
make docker-build |
Build the Docker image |
make docker-run |
Run the container (reads .env) |
make clean |
Remove dist/ and node_modules/ |
- React + Vite
- Cytoscape.js with dagre layout for directed graph rendering
- Ory Keto REST API (via dev proxy or production server)
cd permission-visualizer/app
npx playwright install chromium
npx playwright test| Test file | Requires |
|---|---|
tests/relationship-editor.spec.js |
Nothing — uses offline bundled data (18 tests) |
tests/schema-editor.spec.js |
Nothing — uses offline bundled data (10 tests) |
tests/visualizer.spec.js |
Live Ory tunnel on port 4000 (11 tests) |