is_TRAIT
or has_TRAIT
.
For example, if the fact expresses that an
entity is public, a good fact name would be is_public
. If it expresses that an entity has a role, you should name it has_role
.
Fact arguments provide the details that uniquely define the fact. The first
argument is the entity that the fact describes.
The fact is_public(Repository:cool_app)
states that the cool_app
repository (the entity) is public
(the trait).
These conventions will guide how facts are constructed in this guide.
We used the name of the repository here for illustration, but in practice
you’ll usually refer to objects by their ID.
Simple-valued fields
Simple-valued fields (e.g. fields that aren’t foreign keys) describe an attribute of a subject. They can be represented by facts that take the formis_FIELD_NAME
or has_FIELD_NAME
.
Boolean fields
Boolean fields express whether or not the attribute is true. They are the simplest to convert to facts.is_public
example. Suppose a repository
table with the following structure.
is_public
field. The row with ID 1 describes the cool_app
repository, for which the value of is_public
is TRUE
.
If you wanted to determine whether the cool_app
repository is public in SQL, you’d write:
is_public
) and the first
argument is the entity the fact describes (repository ID 1 named cool_app).
Boolean fields are often named is_SOMETHING. If the field name already starts
with
is
or has
do not prepend it again to the fact name.Strings and numbers
Strings and numbers express traits that can be quantified (e.g. amount, weight) or labeled (e.g. role, status). Unlike boolean facts, it is necessary to specify the value of a string or number to define it. For instance, your application may have users who have different roles (e.g.admin
, writer
, read-only
).
You might express this as a string-valued role
field on a User table.
role
field.
Yalice
has the admin
role, bob
has the read-only
role, and charlie
has the writer
role.
If you want to fetch alice
’s role in SQL, you’d use this query:
alice
’s role as a fact, there is more information
needed than the name of the trait and the identity of the entity. That approach works for boolean traits because they only have two possible values: TRUE
and FALSE
. If the fact exists, then the value is TRUE
. If it doesn’t, then the value is FALSE
.
Since string and numeric traits have many possible values, the value of the
trait must be included in the fact. The fact has_role(User:1)
indicates that
alice
has a role. It doesn’t indicate which role alice
has.
The second argument of the fact provides that information: has_role(User:alice, "admin")
.
Roles have special
meaning
in Polar. If roles are defined in a field called something other than
role
,
the associated fact should still be named has_role
.trait_value
is not quoted. You might have a login_count
field to store the number of times a user has logged in.
bob
(ID 2) has a login count of 24
, so the fact expresses the
number of times bob
has logged in as has_login_count(User{"2"}, 24)
.
Foreign keys
A foreign key is a reference to another table in a relational database. These references serve two major purposes:- Lookups: Lookup tables define values that are used repeatedly in other tables. For example, you may want to have a
role
table where each role name is defined a single time rather than repeating the names of the roles directly in youruser
table. - Relationships: The majority of foreign keys define relationships between two entities.
Lookup tables
Rather than repeating the same values for strings that have system meaning like roles and status, many teams will enumerate all the possible values of those strings in a separate table. For example:
alice
’s role now requires a JOIN
statement:
alice
’s role could be expressed in a fact by its ID: has_role(User{"1"}, 1)
, but this is not desirable. This produces authorization rules that depend
on a specific role value. For example, a rule that states that users with the admin
role get the add_user
permission.
If you express roles using their names, the policy would look like the following.
Relationships
Foreign keys express relationships between two entities. This section covers the most common:- One-to-Many
- Recursive
- Many-to-Many
One-to-many relationships
A one-to-many relationship is a relationship where one entity may have a relationship to multiple other entities of a given type. For example, to capture the owner of a repository, a foreign key expresses the relationship between therepository
table and the user
table.

owner_id
field of the repository
table.
alice
is the owner of cool_app
and charlie
is the owner of okay_app
.
This is an example of a one-to-many relationship. Each user will have exactly one record in the user
table. But any given user may be the owner of multiple repositories, so there could be many rows in the repository
table with the same owner_id
.
Because the owner_id
expresses a relationship between the user
and repository
tables, we use the name has_relation
for the trait that this field defines.
Although we could have used the name
has_owner
, relations occur so often
in authorization logic that they have special
meaning to Oso. Whenever a fact defines
a relation between two entities, a good name is has_relation
.This makes it obvious from the policy that the fact represents a foreign key in the source database.trait_name(subject, trait_value)
.
trait_value
for a relation is the name of the relation. More information
is needed to fully define the fact - User is the owner of the repository.
This SQL query returns the owner of the cool_app
repository (user ID 1).
You might wonder why the repository is the subject instead of the user.
This is because the foreign key (
owner_id
) exists on the repository
table,
so the primary key of the row that contains the foreign key identifies a
repository. For one-to-many relationships, it is natural to think of the
primary key as the subject and the foreign key as the object.Recursive (self-referential) relationships
Recursive relationships are a special form of one-to-many relationship where the foreign key refers to a field in its own table, usually the primary key. Folder hierarchies are a common example. Folders may contain folders to arbitrary depths.folder
table that has a parent_id
field that refers back to the id
of another row in the table.

has_relation
facts with the same
battern from earlier in the guide. Since the parent_id
field is the foreign key,
the primary key of the row that contains the parent_id
(the child folder)
is the subject of the fact. The row that the parent_id
refers to is the object
of the fact.
Thus, the fact expresses that folder-2
has the parent folder-1
, or:
In Polar terms, a recursive relationship is expressed by a fact where the
subject and object have the same type.
Many-to-many relationships (JOIN tables)
Applications frequently model many-to-many relationships. In a multitenant applicationi for exxample, users belong to organizations. As given user will have different roles in different organizations, a user will not be associated with a single role. Instead, a role will be associated with the combination of a user and an organization. This can be expressed by creating a JOIN table that defines a many-to-many relationship between users and organizations.
acme
and banjo
. alice
is an admin
in acme
and a member
in banjo
.
The JOIN table could be expressed like the following (assume the same user data
used throughout this guide where alice
is user ID 1).
alice
has on the acme
organization.
alice
.
The value is admin
. The object (the entity on which alice
has the role) is
the acme
organization.
A fact expressing these three elements follows.
Summary
You can visualize the mapping from relational data to facts like this:
- The pattern of a fact should be
trait_name(subject, [trait_value], [object])
- The
trait_name
should take the formis_TRAIT
orhas_TRAIT
- The
- The
subject
is the entity that has the trait (e.g. the user that has the role)- You can also think of this as the row that has the field.
- The
trait_name
should mirror the name of the field that it corresponds to in most cases - Two types of traits have special meaning
- Roles should always be called
has_role
- Relations should always be named
has_relation
- Roles should always be called
- When using lookup tables to store the values of strings that have meaning to
your policy, use the values in
fact definitions, not the IDs.
has_role(User:1, "admin")
instead ofhas_role(User:1, 1)
- The object should be the the record that is referenced by the subject
- The parent folder
- The organization on which the user has a role