TypeDB Fundamentals Lecture Series: from Nov 16 to Jan 9


All queries to a TypeDB database are written in TypeQL.

TypeQL is a declarative query language. Rather than writing an algorithm of how the data should be retrieved, we declare requirements, and the TypeDB query processor will take care of finding an optimal way to retrieve and process the data.

TypeQL is both a Data Definition Language (DDL) and Data Manipulation Language (DML).

As a DDL, it lets us define a schema of a database.

As a DML, it lets us query (read&write) data of a database.

The complete set of the TypeQL grammar can be seen in the ANTLR v.4 specification file: TypeQL.g4.

Query structure

A TypeQL query consists of clauses (one, two or three). There are the following types of clauses:

  • define

  • undefine

  • match

  • fetch

  • get

  • insert

  • delete

All clauses start with the keyword, the same as the clause’s name, followed by:

  • A list of variables and optional modifiers at the end (for a get clause);

  • A list of variables or subqueries (for a fetch clause);

  • A TypeQL pattern (for all other clauses).

A pattern consists of one or more statements.

TypeQL query structure
  • Query

    • Clause

      • Pattern

        • Statement

See a simple query example
match $p isa person;
get $p;
limit 1;

In the example above we can see a single Get query with two clauses: match and get. The query also has a modifier to limit the number of results returned to exactly one.

A string or a file with multiple TypeQL queries is a script.

See a TypeQL script example
$p isa person,
    has full-name "Masako Holley",
    has email "masako.holley@vaticle.com";

$p isa person,
    has full-name "Pearle Goodman",
    has email "pearle.goodman@vaticle.com";

$p isa person,
    has full-name "Kevin Morrison",
    has email "kevin.morrison@vaticle.com";

The above example contains three insert queries without a match clause.

To rewrite it as a single query we need to remove the second and third insert keyword and change variables, so that they are not overlapping (otherwise, we will insert a single person entity with three full-names and three emails):

$p1 isa person,
    has full-name "Masako Holley",
    has email "masako.holley@vaticle.com";

$p2 isa person,
    has full-name "Pearle Goodman",
    has email "pearle.goodman@vaticle.com";

$p3 isa person,
    has full-name "Kevin Morrison",
    has email "kevin.morrison@vaticle.com";

For more information on patterns, see the Pattern matching essentials and the Advanced patterns & queries pages.


TypeDB schema is like a blueprint of a database.

A database schema contains all user-defined types and rules used in a database.

Every instance of data inserted in a TypeDB database must be assigned a type and be validated against constraints.

Database schemas written in TypeQL resemble logical data models of relational databases. TypeDB removes the need to create a separate physical data model and allows developers to create schemas that mirror their object model, simplifying design and development.

As a result, TypeDB schemas look like entity-relationship (ER) diagrams with entities, relations, and attributes connected by roles of relations and ownerships of attributes, but also subtyping (see inheritance below).


A type represents a set of constraints on the interpretation of data.

A type for a data instance is like a class for an object in OOP.

A definition of a type in a schema of a database sets:

  • type hierarchy — every user-defined type has only one parent or supertype.

  • constraints — what a type can do: what role to play, what attributes it can own, what value type it has (attribute types only), and what roles it relates (relation types only).

A type can be addressed by its label (name). A type label is unique in a schema of a database.

We can define a new type only as a subtype of an existing one.

A new empty database has a set of built-in types. These built-in types are called root types because all user-defined types will be subtypes (direct or non-direct) of those root types.

Table 1. Root (built-in) types
Root type label A subtype of the root type An instance of data of a subtype


Entity type

Instance of entity type


Relation type

Instance of relation type


Attribute type

Instance of attribute type

All root types are abstract types.

That’s why when we say entity type, we usually mean a subtype of the entity root type. In the same way, we can address relation type or attribute type instead of mentioning subtypes of the root types.

To avoid ambiguity when using type labels, we should try to provide context or specify what exactly we mean:

  • the type and all its subtypes (the default variant),

  • the exact type, without any of its subtypes,

  • or instances of data of this particular type and instances of data of all its subtypes.


A type can subtype another type. As a result, the subtype inherits all the attributes owned and roles played by its supertype.

Roles can be inherited and even overridden as a part of relation inheritance.

Type can only have a single supertype.

Types can be subtypes of other subtypes, resulting in a type hierarchy.

See simple example

For example, business-unit subtypes user-group, which subtypes subject, which subtypes entity root type. Also, person subtypes user, that subtypes subject:

  • entity

    • subject

      • user group

        • business unit

      • user

        • person

In TypeQL we can define these types with the following query:


subject sub entity;
user-group sub subject;
business-unit sub user-group;
user sub subject;
person sub user;

There is a strict hierarchy of types, so the whole typing system of a TypeDB database can always be represented by three independent trees with one of the root types at the top of each tree.

See hierarchy trees example

For example, a schema with the following types:

  • entity

    • person

    • vehicle

      • car

      • motorcycle

      • bicycle

  • relation

    • owning

    • using

      • driving

      • traveling

  • attribute

    • model

    • name

      • full-name

      • nickname

can be visualized as the following type hierarchy:

root types trees

Abstract types

An abstract type can’t be instantiated (we can’t insert an instance of data of this type). All we can do with an abstract type is to subtype it.

All root types are abstract types.

The opposite of an abstract type is a concrete type.

All user-defined types are concrete types by default.

Thing type

An internal type called thing can be used to address all types (both built-in and user-defined) or instances of all types (effectively — all data).

All types are subtypes of the thing, including the root types.

Types hierarchy

The thing internal type will be deprecated in one of the upcoming versions and deleted in TypeDB version 3.0.

Consider using entity, attribute, or relation built-in types instead.

For example:

Data retrieval example
    $s isa $t;
    {$t type entity;} or {$t type relation;} or {$t type attribute;};

Entity types

Entity types (or subtypes of the entity root type) represent the classification of independent objects in the data model of our business domain.

Instance of data of an entity type represents a standalone object that exists in our data model independently.

Instance of an entity type doesn’t have a value. It is usually addressed by its ownership over attribute instances and/or roles played in relation instances.

An object modeled with an entity type might practically require other entities to exist, such as a car that cannot exist without its parts, but can be conceptualized without reference to those other entities: a car can be imagined without considering its parts.

See example

Given the schema:


name sub attribute, value string;

person sub entity,
    owns name,
    plays marriage:spouse;

marriage sub relation, relates spouse;

An instance of the person type can be inserted in a database:

  • without owning any instances of the name attribute type, nor playing any roles,

  • owning exactly one instance of the name attribute type with some value and playing a role of a spouse in a marriage relation,

  • owning multiple names and/or playing the role spouse in multiple instances of the marriage relation type.

To define a new entity type, we need to set its label and what type it’s a subtype of.

To set a property of an entity (like a name of a person), we need to define ownership by this entity of an instance of an attribute type with the required value.

To define a relationship between an entity and some other user-defined types, we need to define a relation with roles and the ability of the involved types to play those roles.

Entity types and instances example

For example, there could be entity types like company and person.

Given the company entity type defined in a database schema, we can insert instances of data of this type in such a database. Every instance of the company type inserted into the database will represent a company, that can be addressed by whatever attributes it has (e.g., name, registration number), or by roles played in relations (e.g., employer for the particular instance of person entity type in an employment relation type).

instances example

On the above image two instances of company type are called Company #1 and Company #2, while in real life scenario in a TypeDB database there is almost no way to differentiate between those two instances if they have no attributes and do not participate in any relations. The only information we can get from existence of two instances is that there are two distinct objects. But its really hard to tell which one is which without any additional related data inserted.

For more information on how to define an entity type, see the Define entity types section on the Define types page.

Relation types

Relation types (or subtypes of the relation root type) represent relationships between types. Relation types have roles.

Other types can play roles in relations if it’s mentioned in their definition. An instance of another type can be a role player for a role in the instance of a relation.

An instance of a relation can be uniquely addressed by a combination of its type, owned attributes, and role players.

A relation type must specify at least one role. A relation cannot be conceptualized without at least some of its role players.

See the group-membership example

For example, given the schema:


group-membership sub relation,
    relates user-group,
    relates group-member;

user-group sub entity, plays group-membership:user-group;

subject sub entity, plays group-membership:group-member;
user sub subject;

group-membership is a relation type that defines user-group and group-member roles. The user-group role is to be played by a user-group entity type whereas the group-member role is to be played by a subject type and all its subtypes entities.


Roles are special internal types used by relations. We can’t create an instance of a role in a database. But we can set an instance of another type (role player) to play a role in a particular instance of a relation type.

Roles allow a schema to enforce logical constraints on types of role players.

See the example of a role player’s type constraint

For example, given the schema below, a file type entity can’t play any role in a group-membership relation, and a user type entity can play the group-member role of a group-membership relation because it inherits it from the subject type.


group-membership sub relation,
    relates user-group,
    relates group-member;

user-group sub entity, plays group-membership:user-group;

subject sub entity, plays group-membership:group-member;
user sub subject;

file sub entity;

Abstract roles

Since TypeDB v.2.22.0, all roles are concrete, even in abstract relations.

See the example of a concrete role in abstract type

Given the following schema:


membership sub relation,
    relates member;

group-membership sub membership;

The concrete relation type group-membership inherits the member role from the abstract relation type membership.

Before version 2.22.0 of TypeDB, roles of abstract relations are also abstract. It would make the role member abstract. We have to override an abstract role with a new concrete role to use it for data.

Since version 2.22.0 of TypeDB, all roles are concrete. We can use a role inherited from an abstract relation directly without the need to override it with a new role:

(member: $x) isa group-membership;

Databases created in versions of TypeDB prior to 2.22.0 will continue to use abstract roles for abstract relation, defined in schema prior to update for version 2.22.0 or newer.

The best way to switch to the new behavior is to install the TypeDB v.2.22.0+, create a new database, define the same schema, and load the same data into it.

See the alternative way to manually upgrade database schema

The alternative way to migrate to the new behaviour is to reset (unset and set) the abstractness of each abstract relation type via the Driver API methods:

Abstract methods
Method TypeDB Java Driver TypeDB Python Driver TypeDB Node.js Driver

Is abstract




Set abstract




Unset abstract




Pseudocode example
for rel_type in relation_types:
  if rel_type.is_abstract():

For more information on how to define a relation subtype, see the Define relation types section on the Define types page.

Attribute types

Attribute types (or subtypes of the attribute root type) represent properties that other types can own.

Attribute types have a value type, and instances of attribute types have a value. This value is fixed and unique for every given instance of the attribute type.

Other types can own an attribute type. That means that instances of these other types can own an instance of this attribute type. This usually means that an object in our domain has a property with the matching value.

An instance of an attribute type can be uniquely addressed by its type and value.

There can’t be a second instance of the same type with the same value.

Multiple types can own the same attribute type — and different instances of the same type or different types can share ownership of the same attribute instance.

For more information on the types of values, attributes can have: see the list of value types on the Define types page.

See example

Given the schema:


name sub attribute, value string;

person sub entity, owns name;

An instance of an attribute type name with a value "Bob" can be owned by:

  • no one (no instance of the person type owns the instance of the name type with value "Bob"),

  • one particular person (there is one person with such a name),

  • or multiple people (there are multiple people with the name Bob. All of the pesron type instances have ownership of the same instance of the name type with the value "Bob").

The feature of an attribute type owning another attribute type will be deprecated in one of the upcoming versions and deleted in TypeDB version 3.0.

For more information on how to define an attribute subtype, see the Define attribute types section on the Define types page.


Rules are a part of a schema and define embedded logic.

The reasoning engine uses rules as a set of logic to infer new data.

Rules can dramatically shorten complex queries, perform explainable knowledge discovery, and implement business logic at the database level.

A rule consists of a condition and a conclusion.

A condition is a pattern to look for in data.

A conclusion is a pattern to insert virtual (inferred) data for every result matched with a pattern from the condition.

Inference can only be used in a read transaction.

Rules can’t change persisted data in a database.

All reasoning is done within a dataset of a transaction.

The rules syntax uses when and then keywords for condition and conclusion, respectively.

Rule syntax
rule rule-label:
when {
    ## The condition
} then {
    ## The conclusion

The conclusion can be used to create a single virtual instance of data: a relation or ownership of an attribute.

Queries use rules for Inferring new data only in read transactions and only if the inference option is enabled.

See an example of a rule
rule add-view-permission: when {
    $modify isa action, has name "modify_file";
    $view isa action, has name "view_file";
    $ac_modify (object: $obj, action: $modify) isa access;
    $ac_view (object: $obj, action: $view) isa access;
    (subject: $subj, access: $ac_modify) isa permission;
} then {
    (subject: $subj, access: $ac_view) isa permission;

See the explanation.

We can use computation operations and functions in the condition pattern. And we can use value variables in the conclusion of a rule.

It is possible to create a recursive logic in the line of n = n +1 by assigning attribute ownership with the value of a value variable. If triggered, such a rule can run indefinitely while the transaction lasts and can cause an out-of-memory error.

For more information on how to create rules in a schema, see the Define rules page.


Every piece of data stored in a TypeDB database must be an instance of a type defined in the schema of the database.

In other words, to insert data into a TypeDB database, we must use the types defined in the schema of the database.

The schema defines the vocabulary we use to query our data. See the example below.

For more information about reading and writing data, see the following pages:


Given the following schema:


person sub entity,
    owns name,
    owns age,
    owns certified-fortune-teller;

name sub attribute, value string;
age sub attribute, value long;
certified-fortune-teller sub attribute, value boolean;

The simplest Insert query can look like this:

insert $p isa person;

By inserting information, that some unbound variable isa <type> we create an instance of the type. The person is the type label in this example.

But what is the use of an instance of an entity type without any attributes owned or roles played in relations?

A more useful example of an insert query can look as the following:

$p isa person,
    has name "Bob",
    has age 31,
    has certified-fortune-teller false;

That creates an entity and assigns an ownership of three attributes of different types to the newly created entity.

If any of the attributes with the given value didn’t exist prior to this query — TypeDB will create an instance of an attribute with the required value automatically.

Now, to read that data, we can use the Get query:

match $p isa person;

This should return to us all instances of the person type. But yet again — what is the use for entities without their properties? Let’s query for all attributes owned by the person type instances:

$p isa person, has $a;

The response should consist of pairs of the person type instance with an instance of an attribute type owned by it, including our Bob with the age of 31.

The result of the above query will not return us the first instance of the person type we inserted. It has no attributes. Hence, it doesn’t fit in the match clause pattern: it doesn’t match the has $a part.

Let’s query specifically for Bob, age 31, and retrieve the value of the boolean attribute certified-fortune-teller:

    $p isa person, has name `Bob`, has age 31, has certified-fortune-teller $cft;
get $p, $cft;

The above query will find every person entity that has ownership over the instance of the attribute type name with the value of Bob, ownership of the age with the value of 31 and the ownership of the certified-fortune-teller attribute with any value.

With the get clause, we filter the results to get the person instances and the corresponding certified-fortune-teller attribute (represented by the $cft variable in the pattern) for every matched result in a database.

For more information why the get clause in the above example needs $p see the Get query page or the example explanation.



Any indentation in TypeQL will be ignored and serves are only for visual aid.

See an example indentation

The following three queries are identical in their result:

insert $p isa person, has full-name "Masako Holley", has email "masako.holley@vaticle.com";

$p isa person,
    has full-name "Masako Holley",
    has email "masako.holley@vaticle.com";

"Masako Holley"


TypeQL uses in-line comments only. To make a comment use the # symbol.

See a comment example
# This is the first query
insert # this is the insert query without a match clause
$p isa person; # this line inserts a person entity
$p has full-name "Bob"; # we add a constraint to the $p variable used in a previous line

# This is the second query
$p isa person, has full-name "Alex"; # In this query we insert another person

Learn more

Learn more essential information about different queries in TypeQL with the Queries page.

Or skip it and go straight to the Schema and Data sections of this documentation for more in-depth look on every query type.

Use the TypeQL grammar reference page for factual information about syntax of TypeQL.

Provide Feedback