Map your Oso Cloud Polar policies to WorkOS FGA resource types, roles, and permissions.
This guide helps you migrate from Oso Cloud to WorkOS FGA. Oso Cloud uses the Polar language to define authorization policies with explicit fact storage. WorkOS FGA takes a different approach: hierarchical role-based access control with automatic permission inheritance configured through a Dashboard.
| Oso Cloud Concept | WorkOS FGA Equivalent |
|---|---|
resource blocks | Resource Types |
roles array | Roles |
permissions array | Permissions |
relations | Parent-child hierarchy |
has_role facts | Role Assignments |
has_relation facts | Resource registration with parent |
actor User {} | Organization Memberships |
| Local Authorization | App-side traversal (see below) |
| Polar DSL | Dashboard configuration |
Oso Cloud requires you to write Polar policies and manage facts. WorkOS FGA simplifies this:
Unlike standalone authorization systems, WorkOS FGA integrates natively with the WorkOS identity platform (although it can be used standalone):
Key patterns in Oso Polar:
roles = [...] – Define available roles on a resourcepermissions = [...] – Define available permissionsrelations = {...} – Define relationships to other resources"permission" if "role" – Grant permission to role"role" if "role" – Role inheritancerole if role on "relation" – Inherit roles from related resourceExtract resource blocks from your Polar policy. These become resource types in WorkOS FGA.
Create resource types for:
Exclude:
actor User {} – Use Organization Memberships as subjects insteadactor Group {} – User groups are coming soon; for now, assign roles directly to users
# Oso Cloud actor User {} resource Organization {} resource Workspace {} resource Project {} # WorkOS FGA Resource Types organization (built-in) └── workspace └── project
Navigate to Authorization > Resource Types in the Dashboard to create your hierarchy.
Map Oso relations to WorkOS FGA parent-child resource type relationships.
Create a workspace resource type with organization as its parent. Create a project resource type with workspace as its parent. The parent relationship is defined at the resource type level.
When you register individual project resources instances via the API, you specify the parent workspace. Permissions flow down this hierarchy without explicit facts.
Oso roles and permissions arrays map directly to WorkOS FGA roles and permissions.
resource Project { roles = ["viewer", "editor", "admin"]; permissions = ["read", "write", "manage"]; "read" if "viewer"; "write" if "editor"; "manage" if "admin"; "viewer" if "editor"; "editor" if "admin"; }
Create roles on the project resource type:
| Role | Permissions |
|---|---|
| viewer | project:read |
| editor | project:read, project:write |
| admin | project:read, project:write, project:manage |
The role inheritance ("viewer" if "editor") becomes permissions bundled into roles. Higher-privilege roles include all permissions from lower-privilege roles.
Permission slug convention: Permission slugs are arbitrary text, but we recommend the pattern {resource-type}:{action} for clarity. Each permission must be explicitly scoped to a resource type in the Dashboard – see more about permissions. When a role includes permissions scoped to child resource types (like project:read on a workspace role), it grants that permission on all child resources of that type.
Oso’s role if role on "relation" pattern is replaced by native hierarchical inheritance.
resource Workspace { roles = ["viewer", "editor"]; } resource Project { permissions = ["read", "write"]; relations = { workspace: Workspace }; role if role on "workspace"; }
Create a workspace resource type with roles that include child-type permissions:
| Role (on workspace) | Permissions |
|---|---|
| viewer | workspace:read, project:read |
| editor | workspace:read, workspace:write, project:read, project:write |
When you assign workspace:viewer to a user, they automatically get project:read on all projects within that workspace. No explicit per-project facts needed.
| Oso Cloud Pattern | WorkOS FGA Equivalent |
|---|---|
"permission" if "role" | Permission included in role |
"role" if "role" | Higher role includes lower role’s permissions |
role if role on "relation" | Native inheritance (automatic) |
"permission" if "role" on "relation" | Include permission in parent role |
| Custom Polar rules | Check conditions in app code |
and expressions | Check multiple conditions in app code |
not expressions | Permission exclusions (coming soon) |
Oso’s Local Authorization generates SQL queries that you run against your database. WorkOS FGA takes a different approach: keep high-cardinality data in your database and traverse to FGA-managed resources in your application code.
Instead of configuring Local Authorization mappings, look up the parent resource and check access there:
async function canUserAccessFile( organizationMembershipId: string, fileId: string, ): Promise<boolean> { // 1. Look up the file to find its parent project const file = await db.files.findUnique({ where: { id: fileId } }); if (!file) return false; // 2. Check access at the project level (FGA-managed) const { authorized } = await workos.authorization.check({ organizationMembershipId, permissionSlug: 'project:view', resourceTypeSlug: 'project', resourceExternalId: file.projectId, }); return authorized; }
This replaces Oso’s Local Authorization YAML configuration:
# Oso Local Authorization config (no longer needed) facts: has_relation(File:_, parent, Project:_): query: SELECT id, project_id FROM files has_role(User:_, String:_, Project:_): query: SELECT user_id, role, project_id FROM project_memberships sql_types: File: UUID Project: UUID
With this approach, traversal logic lives in your application code where it’s easier to test, debug, and version alongside your business logic.
Not everything belongs in FGA. We recommend using FGA for lower-cardinality resources (organizations, workspaces, projects) and handling high-cardinality entities (files, messages, comments) in your application.
Syncing millions of entities into FGA creates reconciliation overhead, race conditions, and consistency challenges. Instead, check access at the parent container level and filter entities in your application.
For detailed guidance on this pattern, see High-Cardinality Entities.
read, write, manage)has_role to role assignments, has_relation to resource registrationcheck API callsOso Cloud authorize (JavaScript):
const authorized = await oso.authorize({ type: 'User', id: 'alice' }, 'read', { type: 'Project', id: 'proj_123', });
WorkOS FGA Check (JavaScript):
const { authorized } = await workos.authorization.check({ organizationMembershipId: 'om_01HXYZ', // available in a session token or via the API permissionSlug: 'project:read', resourceTypeSlug: 'project', resourceExternalId: 'proj_123', });
actor User {} resource Organization { roles = ["admin", "member"]; permissions = ["manage", "read"]; "read" if "member"; "manage" if "admin"; "member" if "admin"; } resource Workspace { roles = ["viewer", "editor"]; permissions = ["read", "write"]; relations = { organization: Organization }; role if role on "organization"; "viewer" if "member" on "organization"; "editor" if "admin" on "organization"; "read" if "viewer"; "write" if "editor"; } resource Project { relations = { workspace: Workspace }; "read" if "viewer" on "workspace"; "write" if "editor" on "workspace"; }
Resource type hierarchy:
organization (built-in) └── workspace └── project
Roles for organization:
| Role | Permissions |
|---|---|
| member | organization:read, workspace:read, project:read |
| admin | All member permissions + organization:manage, workspace:write, project:write |
Roles for workspace:
| Role | Permissions |
|---|---|
| viewer | workspace:read, project:read |
| editor | workspace:read, workspace:write, project:read, project:write |
Key insights:
role if role on "organization" – Replaced by org roles including workspace/project permissions"viewer" if "member" on "organization" – Org member role includes workspace:read