Define the data model
Your connector must specify the shape of the data stored in your data source type by defining a data model using model()
. Model definitions are used by Netlify to build a GraphQL schema for your data source, which includes the types and relationships of your data.
The data model should include document models for each type of record stored in your database, and the fields and types stored on each one. The following sections outline the properties available for document models and their fields.
Using Connect and need access to data in real time? Or need data from another API or database?
The following documentation outlines how to build a static connector, which syncs data from a data source and stores it in the data layer database. If you need to build a connector for Connect that requires access to data in real time, you may want to build a dynamic connector instead.
Define document models
You can think of a document as a single unit of information from your data source, such as a post, article, user, product, etc. For each type of information in your data model, you need to define a document model that describes the shape of that data. To define a document model, use define.document()
.
It’s important to define anything that you want to store, uniquely identify, and query later in a list or by ID as a document model.
A document model includes the following properties:
id
: defined by default, this is the unique ID within your document model type. You don’t need to define anid
property manually in your model, but you will need to set the value when you sync documents.name
: the name of the document model. For example,Post
orUser
.cacheFieldName
: (optional) the field to use for caching each document.fields
: an object containing each of the document model’s fields. Learn more about field types.localized
: (optional) setting this totrue
flags this model as document-localized. Note that you also have the option to enable field-level localization.
For example, this is how to define a document model called Post
that has a title
field and a required updatedAt
field. The updatedAt
field is used for caching. Note that an id
isn’t explicitly defined here because Netlify includes one automatically.
connector.model(async ({ define }) => { define.document({ name: "Post", cacheFieldName: "updatedAt", fields: { title: { type: "String", }, updatedAt: { type: "String", required: true, }, }, });});
The following sections outline some of the properties available to you when you define your model. We’ve also included a detailed example that you can refer to.
Cache field name
To allow Netlify to optimize GraphQL queries for your users, we recommend using cache fields. Netlify uses cache fields to determine what data to re-insert and to allow for GraphQL query caching. Documents are only updated when the value of the cache field changes.
If you don’t set a cache field, Netlify will recreate documents of that type every time models.[ModelName].insert()
is called.
When defining a document model, you can specify a top-level model field to use for caching each document.
For example:
connector.model(async ({ define }) => { define.document({ name: "Post", cacheFieldName: "updatedAt", fields: { title: { type: "String", }, updatedAt: { type: "String", required: true }, }, });});
connector.sync(({ models }) => { models.Post.insert({ id: "1", title: "Hello world" updatedAt: "1689021849725" })})
In this example, cacheFieldName
is set to the updatedAt
field. The Post
document will only update if the updatedAt
value has changed since the last time models.Post.insert()
was called with the same document ID.
Fields
When you define fields for a document model or an object type, you can set the following properties:
- field name: defined using the object property name for that field. You can use any field name except for
internal
,id
, andfields
. For example, this is how we would set the field nameupdatedAt
:fields: {updatedAt: {type: "String",required: true,},} type
: defines the type of the field. Learn more about field types.required
: (optional) set totrue
to mark a field as required.list
: (optional) set totrue
to indicate the field is a list. To make the list required, set this property torequired
instead oftrue
. For example,list: required
.localized
: (optional) setting this totrue
flags this field as localized. Note that you also have the option to enable document-level localization.
Field types
Along with the built-in field types, you can use object, enum, and union types that you define yourself. You can also use a document model as a type.
Define an object type
If you have a complex field type on your documents, you can define an object type using define.object()
. You define the fields on your object the same way you do on a document model, as documented under fields. You also have the option of specifying editor
settings on the object.
Note that object types don’t have insert
and delete
methods, only document models do.
Once you have defined an object and stored it in a variable, you can use that object type on your document model.
For example:
connector.model(async ({ define }) => { // this defines an object type that we store in a variable // called Content const Content = define.object({ name: "Content", fields: { title: { type: "String", }, }, });
// this defines a document model `Post` and its `content` field is // of the `Content` object type defined above define.document({ name: "Post", fields: { title: { type: "String", required: true, }, content: { type: Content, }, }, });});
For unnamed object types, you can also define the models inline using define.inlineObject()
.
Define an enum type
Enumerated (enum) types are a specific set of strings that you can query or use in filters in a GraphQL API. You can define an enum using define.enum()
.
For example:
connector.model(async ({ define }) => { define.enum({ name: "ExampleEnumStoplight", values: [ { label: "Green light", // used as the GraphQL description value: "GREEN", // the actual enum member value }, { label: "Yellow light", value: "YELLOW", }, { label: "Red light", value: "RED", }, ], });});
For unnamed enum types, you can also define the models inline using define.inlineEnum()
.
Define a union type
Union types are combined types that include different object types and/or different document models. You define a union type using define.union()
.
For example:
const Content = define.union({ name: "ExampleContentUnion", types: ["Post", "News"]})
connector.model(async ({ define }) => { const UserModel = define.document({ name: "User", fields: { posts: { type: Content, list: true }, mostPopularPost: { type: Content } } })
define.document({ name: "News" fields: { title: { type: "String" } } })
define.document({ name: "Post", fields: { author: { type: UserModel } } })})
If relationship fields are union types, they are required to have the ID and type of the relationship when you insert documents. Learn more about creating documents that have relationship fields.
For unnamed union types, you can also define the models inline using define.inlineUnion()
.
Inline model definitions
For situations where your type is unnamed in your data source, you may want to define objects, unions, or enums without a name. For example, when a CMS supports non-global type definitions.
To support this, there are inline definition helpers for each of these types:
define.inlineObject()
define.inlineUnion()
define.inlineEnum()
For example:
connector.model(async ({ define }) => { define.document({ name: "Post", fields: { content: { type: define.inlineUnion({ types: ["Post", "News"], }), }, }, });});
The inline model definitions will still have globally unique names that use the hash of the model definition itself.
So, while non-inline definitions will error if you define them with a duplicate name, you may re-define inline model definitions as many times as you’d like. Since the model name is a hash of the definition, we can share the same model definition in all the places it was re-defined.
Relationship fields
A relationship field is a field that has another document model as its type. It allows you to link from one document to another document by document ID. For example, you may have an authors
field on the Post
document model that is a list of User
documents.
To create a relationship field, set the type of the field to a reference to a document model definition variable, or to the name of a document model.
Learn more about creating documents that have relationship fields.
Cross-reference fields
If your data source supports cross-references, you can define cross-reference types and fields in your connector. Cross-reference fields allow you to specify reference fields across different connectors or across different instances of the same connector. These are cross-references that your data source can resolve.
Cross-references defined by users
Netlify also offers the ability for Connect users to define cross-references across different types of data sources. For example, from Drupal to Contentstack. These cross-references are resolved by Netlify, instead of the individual data source. Connect users can define cross-references in the Netlify UI. Make sure your connector includes an instanceID
option to support user-defined cross-references.
The cross-reference type should include the following properties for what this cross-reference is for:
modelName
: the name of the modelconnectorName
: the name of the connectorinstanceID
: the instance ID of the connector
You can define cross-reference types that reference a single model or multiple models.
For example:
// Reference a single modelconnector.model(async ({ define }) => { const myCrossReference = define.crossReference({ to: { modelName: `ExampleDocument`, connectorName: `example-connector`, instanceID: `1234`, }, });
// Then define a field as a cross-reference using the type define.document({ name: "Post", fields: { bonusContent: { type: myCrossReference }, }, });});
// Reference multiple modelsconnector.model(async ({ define }) => { const myCrossReference = define.crossReference({ to: [ { modelName: `ExampleDocument`, connectorName: `example-connector`, instanceID: `1234`, }, { modelName: `AnotherDocument`, connectorName: `another-connector`, instanceID: `5678`, }, ], });});
You can also define the cross-reference type inline when you define the field. This example defines the bonusContent
field as a cross-reference to the AnotherDocument
model.
// Define the cross-reference inlineconnector.model(({ define })=>{ define.document({ name: `ExampleDocument`, fields: { bonusContent: { type: define.crossReference({ to: { modelName: `AnotherDocument`, connectorName: `another-connector`, instanceID: `5678`, }, }), }, }, })})
Example document model definitions
These detailed examples demonstrate how to define a document model and various types of fields on it. A subset of these options are available to object types also.
Example for Connect
connector.model(async ({ define }) => {
define.document({ name: "Post", // name of the document model cacheFieldName: "updatedAt", // cache fields only apply to document models fields: { updatedAt: { // updatedAt is the field name type: "String", required: true, // this is a required field. }, title: { type: "String", }, postContent: { type: Content, // this object type, `Content`, is defined below }, author: { type: "User", // this is a relationship field because `User` is a document model, defined below list: true, } categories: { type: "String", list: true, // Post.categories is a list of strings }, languages: { type: "String", list: required, // In this example all Post documents must include a languages list but the list can be empty. // for example, models.Post.insert({languages: []}) }, tags: { type: "String", required: true, list: required, // In this example all Post documents must include a list of tags and the list must include values. }, }, });
// defines a Content object type const Content = define.object({ name: "Content", fields: { title: { type: "String", }, } });
// defines a User document model define.document({ name: "User", fields: { name: { type: "String", }, } });});