Skip to main content
The Data Modeling Language extension (DML) offers a simplified way of building data models in CDF based on the GraphQL Schema Definition Language. A data model in the context of DML is a collection of related types representing the domain specific concepts you want to model for a given use-case. DML functions as an abstraction layer on top of the Data Modeling Service (DMS) API, when you define a data model using DML and publish it, the types and properties in your DML data model are converted into the relevant data models, views, and containers in DMS.
The data modeling REST API (DMS) in combination with the Cognite Python SDK offers more power and flexibility than the DML extension. They’re the preferred tools for managing industrial knowledge graphs in Cognite Data Fusion (CDF).

Types

Types are the basic building block of your data model and represent use-case specific concepts. Each type is associated with one or more properties which are each associated with another type. Below is a simple type definition in DML modeling an Equipment type with two properties: title and pages:
type Equipment {
  title: String
  pages: Int
}

Extending types via inheritance

Using GraphQL inheritance rules, you can create types that extend parent types and inherit all their properties.
  • Define a type as an interface to make it extendable.
  • Use the implements keyword to extend this base type from another type.
  • Extend multiple interfaces by separating them with an ampersand (&).
In most implementations of the GraphQL Schema Definition Language, interfaces are treated differently from types; they cannot be queried directly and serve only as blueprints to be implemented by other types.In DML, interfaces function as both types and interfaces. This means that when you change a type to an interface, the only functional difference is that it can now serve as a parent type for others. You can still query the interface via the CDF APIs as if it were still a standalone type in your data model.
Below is an example of inheritance in DML:
interface Equipment {
  name: String
  installationDate: Timestamp
}
type Pipe implements Equipment {
  name: String
  installationDate: Timestamp
  diameter: Float
  length: Float
}
In this example, the parent type (Equipment) is implemented by the Pipe type, which inherits all parent properties and adds specific properties relevant only to the child type. There are some caveats with fields that are imported from inheritance, learn more in the advanced section.

Properties

A type consists of a collection of properties. Each property can be a scalar, an enum, or a reference to a custom type. When a property references a custom type, it represents a relation between instances in your data model.

List properties

By default a property can only contain a single value:
type Book {
  # A single book can only have a single tag
  tag: String
}
To make a list property that can contain multiple values, add brackets ([]) around the type of the property:
type Book {
  # A single book can have multiple tags
  tag: [String]
}

Limiting number of values on list properties

By default list properties can have maximum of between 100 and 1000 values depending on the property type. To override the default maximum values on a list property, use the @limits directive:
type Book {
  # A single book can have up to 3 tags
  tag: [String] @limits(maxListSize: 3)
}

Scalar properties

Scalar properties represent primitive data.
DML type nameDMS nameDescription
Stringtext
Int or Int32int3232-bit integer number
Int64int6464-bit integer number
Float or Float64float6464-bit number encoded in IEEE 754
Float32float3232-bit number encoded in IEEE 754
TimestamptimestampTimestamp encoded in ISO 8601
JSONObjectjsonJSON encoded data
DatedateDate encoded in ISO 8601, but without time
Booleanboolean

Enum properties

Enum properties are special scalars that must match one of several predefined, distinct values. To create one, declare an enum and then use that enum as the type of another property:
enum StatusEnum {
  ACTIVE
  INACTIVE
}

type Equipment {
  name: String!
  status: StatusEnum
}

CDF resource type properties

Properties can also reference non-data-modeling resource types in CDF. The value of these properties is a string reference to the external ID of the underlying resource.
GraphQL typeCDF resource
Filefiles
TimeSeriestimeseries
Sequencesequences
Using these native types enables specialized querying capabilities via the GraphQL API and some of our SDKs. Below is an example of a type having CDF native resource properties:
type Equipment {
  name: String
  documentation: File
  temperature: TimeSeries
  voltage: TimeSeries
  performanceCurve: Sequence
}

Relation properties

Relations allow types to reference other types, forming connections within your data model. These can point to single or multiple target instances. You create relation properties in DML by defining a property that uses a custom type as its type:
type Author {
  name: String
  birthDate: Timestamp
}

type Book {
  name: String
  isbn: String
  author: Author
}
This example models a type representing a book with a relation connecting it to a single author. In DMS there are three types of relation properties:
  1. Direct relations: A container property storing a reference to another node.
  2. Edges: A connection property that can be used to describe one-to-many or many-to-many relationships.
  3. Reverse direct relation: A connection property representing a direct relation in another type going in the opposite direction.
You can read more about these in the direct relations vs. edges and the reverse direct relations sections in the data modeling documentation. The type of relation property used for a property in DML is determined by several factors described below.

Direct relations

A relation with a single target instance defaults to a direct relation:
type Asset {
    # This will become a direct relation property in DMS
    documentation: Document
}
To model a list of references using direct relations, you must add the @directRelation directive. Without this, list property will default to being modelled as an edge:
type Asset {
    # This will become a direct relation property in DMS
    documentation: [Document] @directRelation
}

Reverse direct relations

A reverse direct relation looks “backward” through an existing direct relation on the target type. You must specify the throughProperty on the target type for the graph traversal.
type Document {
    # The "throughProperty" must match the name of the direct relation on the target type
    referencingAssets: Asset @reverseDirectRelation(throughProperty: "documentation")
}

type Asset {
    documentation: Document
}

Edges

Since single-target relations default to direct relations, add the @relation directive to define a single-target edge relation:
type Asset {
    # This will become an edge connection property in DMS
    documentation: Document @relation
}
A multiple target relation property defaults to edge so in this case no @relation directive is necessary to model it as an edge:
type Asset {
    # This will become an edge connection property in DMS
    documentation: [Document]
}
Properties on edges
Edges can possess their own properties. To define these on an edge relation, create a type annotated with the @edge directive and link it using edgeSource in the @relation directive:
# The @edge directive specifies that this type represents the properties of an edge relation property
type PowerCable @edge {
    ratedVoltage: Float
    installationDate: Timestamp
}

type Asset {
    # Represents a connection between two assets via a power cable edge that has its own properties
    connectedTo: Asset @relation(edgeSource: "PowerCable")
}
Overriding edge type
The edge type used to identify an instance of an edge defaults to {your_typename}.{your_fieldname}. However, you can override this behavior and manually specify the edge type using the type field within the @relation directive.
type Asset {
    # Instances of this edge will be identified by the `power_cable` node in the `my_types` space
    connectedTo: Asset @relation(type: { externalId: "power_cable", space: "my_types" })
}
Overriding edge direction
The relational direction for edges defined in DML is OUTWARDS by default. To create a relation that points inwards (referencing incoming edges), add the direction argument to INWARDS on the @relation directive.
type Asset {
    # All power connections from this asset to another asset (Default: OUTWARDS)
    connectedTo: Asset @relation(type: { externalId: "power_cable", space: "type_space" }, direction: OUTWARDS)
    
    # All power connections to this asset from some other asset
    connectedFrom: Asset @relation(type: { externalId: "power_cable", space: "type_space" }, direction: INWARDS)
}

Descriptions

GraphQL descriptions are supported and will be converted to the corresponding description property on view, container and property definitions. To set the name of a view, container, or field, start a line in the description with @name followed by the name value. Example of GraphQL descriptions:
"""
This is a common interface for common models
@name Common View
"""
interface Common {
  name: String
}
type Equipment implements Common {
  """
  This is the description for the property Equipment.name
  @name Name
  """
  name: String
}

Specification of directives

The following is a list of the directive definitions, with descriptions.
"""
Specifies that a type is a view type.

* space: Overrides the space, which by default is the same as the data model.
* version: Overrides the version of the view, which by default is the same as the data model version.
* rawFilter: Sets a filter on the view, accepts a JSON string.

"""
directive @view(
  space: String
  version: String
  rawFilter: JSON
) on OBJECT | INTERFACE

"""
Specifies that a type is an edge type.
The view can then be used to expand an edge node with a node instance with properties as specified in the view with the @edge directive.
The edge should be annotated with `@relation(edgeSource: "name_of_edge_view")`.
"""
directive @edge ON OBJECT | INTERFACE

"""
Specifies that a type is importing another view and its backing container.
Currently, this annotation must be combined with @view to specify the view and view version to import. In order to extend the view in your data
model, specify it as an interface and then create a new data type that implements the interface and adds additional properties.
In the future, this directive will be extended to simplify importing.
"""
directive @import ON OBJECT | INTERFACE

"""
Overrides the mapping of a field. Can only be used in a view type and can not be used on imported fields.

* space: Overrides the space, which by default is the same as the data model space.
* container: Overrides the container externalId, which by default is the same as the externalId of the view postfixed with 'Container'.
* property: Overrides the container property identifier being mapped.
"""
directive @mapping(
  space: String
  container: String
  property: String
) on FIELD_DEFINITION

"""
Defines the relation field's details

* type: The reference to the edge type this relation is mapped to.
* name: Overrides the name property of the relation definition. This is merely metadata, and should not be confused with the
  property identifier!
* edgeSource: The name of the edge view to use for expanding the edge node with a node instance.
* direction: The direction to follow the edges filtered by 'type'.
* type: Specifies the edge type, namespaced by 'space', where the 'externalId' corresponds to the edge type name.
"""
directive @relation(
  type: _DirectRelationRef
  name: String
  edgeSource: String
  direction: _RelationDirection
) on FIELD_DEFINITION

"""
Configures the backing container of the view.
It can be used to configure indexes or constraints for the properties of a container.
"""
directive @container(
  constraints: [_ConstraintDefinition!]
  indexes: [_IndexDefinition!]
) on OBJECT | INTERFACE

"""
Sets the default value of a field.

* value: Specifies the default value. The input is always in string format, however all dms supported default value types are also supported.

"""
directive @default(
  value: String
) on FIELD_DEFINITION

"""
Sets the unit on a field.

* externalId: specifies a unit by using an external ID value. `externalId` can only be assigned to the types `Float`, `Float32`, `Float64`, or to an array of listed floating types and must match a unit in the unit catalog.
* sourceUnit: a free text field to store arbitrary unit information like unit name in a source system.

"""
directive @unit(
  externalId: String,
  sourceUnit: String
) on FIELD_DEFINITION

"""
Represents an index definition.
* identifier: A unique identifier for the index
* indexType: Is the type of index
* fields: List of field names to define the index across. Only supported for non-inherited or mapped fields. The order of the fields matters.
* cursorable: With cursorable index we can efficiently query with custom sort options, and queries will emit cursors that can be used to paginate through the results. Only supported for BTREE indexType.
"""
input _IndexDefinition {
  identifier: String!
  indexType: _IndexType
  fields: [String!]!
  cursorable: Boolean
}

"""
Represents a constraint definition. The constraint definition can either be an uniqueness constraint or requires.
In case 'constraintType' is set to 'REQUIRES', the 'require' argument must be set.
And if it's set to 'UNIQUENESS', the 'fields' argument must be set.

* identifier: A unique identifier for the constraint
* constraintType: Is the type of constraint
* fields: List of field names to define the unique constraint across. Only supported for non-inherited or mapped fields. The order of the fields matters.
* require: Specify a container (by space and externalId), which is required to exist on the node, if the current container is to be used.
"""
input _ConstraintDefinition {
  identifier: String!
  constraintType: _ConstraintType
  require: _DirectRelationRef
  fields: [String!]
}

"""
Overrides the constraints on the possible values of a property

* maxTextSize: If set overrides the max number of characters allowed on a string property
* maxListSize: If set overrides the max number of values on a list property
"""
directive @limits(
  maxTextSize: Int,
  maxListSize: Int
) on FIELD_DEFINITION

enum _ConstraintType {
  UNIQUENESS
  REQUIRES
}

enum _IndexType {
  BTREE
}

input _DirectRelationRef {
  space: String!
  externalId: String!
}

enum _RelationDirection {
  INWARDS
  OUTWARDS
}

Advanced: Types and their relation to DMS views and containers

Types in the DML data model are abstractions of DMS schema resources (views, containers, and data models). When you publish a DML data model in your CDF project, it’s persisted in DMS as a collection of schema objects (containers, views, and data models). Similarly, when you open an existing data model in DML, the DML schema is regenerated from the published DMS schema objects.

Containers and views

Each type in a DML data model has an equivalent view and container. The identifiers for these resources are determined by the following rules: Container identification (by space and external ID):
  • External ID: Uses the @container directive’s external ID if present, otherwise defaults to the type name.
  • Space: Uses the @container directive’s space if present, otherwise defaults to the data model’s space.
View identification (by space, external ID, and version):
  • External ID: Uses the @view directive’s external ID if present, otherwise defaults to the type name.
  • Space: Uses the @view directive’s space if present, otherwise defaults to the data model’s space.
  • Version: Uses the @view directive’s version if present, otherwise generates a version identifier based on the type definition (ensuring any changes to the type definition result in a new view version).

View and container properties

Once a type has been mapped to a view and container, its properties are also mapped to equivalent view and container properties. Each type property always has an equivalent view property in the view mapped to the type. However, which container property the view property maps to is determined by the following rules (in order of precedence):
  1. Explicit mapping: If the type property has a @mapping directive, the directive determines which container property to use.
  2. Inherited mapping: If the property is inherited from a parent type, the view property maps to the same container property as the equivalent property in the parent type.
  3. Default mapping: Otherwise, the view property maps to a container property with the same name in the container mapped to the type.
When you use the @mapping directive, the system doesn’t auto-create the referenced container property if it doesn’t exist. Only use @mapping to reference container properties that already exist.

Example DML to DMS mapping

interface Equipment {
  name: String
}

type Valve implements Equipment @view(externalId: "Valve2") {
  name: String
  diameter: Number
  material: String
}
When you publish this DML data model, it creates the following DMS resources: Equipment interface:
  • View: Created with external ID Equipment in the data model’s space (no @view directive, so it uses the type name)
  • Container: Created with external ID Equipment in the data model’s space (no @container directive, so it uses the type name)
  • Property name:
    • View property name in the Equipment view
    • Maps to container property name in the Equipment container (no @mapping directive and not inherited, so a new container property is created with the same name)
Valve type:
  • View: Created with external ID Valve2 in the data model’s space (explicitly set via @view directive)
  • Container: Created with external ID Valve in the data model’s space (no @container directive, so it uses the type name)
  • Property name:
    • View property name in the Valve2 view
    • Maps to container property name in the Equipment container (inherited from the parent Equipment interface, so it reuses the same container property as the parent)
  • Property diameter:
    • View property diameter in the Valve2 view
    • Maps to container property diameter in the Valve container (no @mapping directive and not inherited, so a new container property is created)
  • Property material:
    • View property material in the Valve2 view
    • Maps to container property material in the Valve container (no @mapping directive and not inherited, so a new container property is created)
The Valve type’s name property references the Equipment container rather than its own Valve container. This is because inherited properties always map to the same container property as their parent type.

Spaces and naming collisions

When you create multiple data models in the same space with identical type names, they will interfere with each other and cause errors. Consider this scenario where you have two data models in the same space: Data model A:
type Foo {
    bar: String!
}
Data model B (causes collision):
type Foo {
    foo: String
}
When you try to publish data model B, you’ll see an error like this:
Error: could not update data model
An error has occurred. Data model was not published.

[Line: 28] The view (space: 'test', externalId: 'Foo', version: '9317f4591a0e6c') is not mapping a required property 'bar' of the referenced container (space: 'test', externalId: 'Foo').
This error is likely caused by deleting a field which was non-nullable. Please add this field back or add '@readonly' to 'Foo'
This happens because these two data models will have view properties that map to the same container property but with different requiredness. To prevent naming conflicts:
  • Use one data model per space - This eliminates the possibility of collisions.
  • Use unique type names - If you need multiple data models in the same space, ensure all type names are unique across models.
Last modified on January 21, 2026