Skip to content

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 an id 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 or User.
  • cacheFieldName: (optional) the field to use for caching each document.
  • editor: (optional) defines how Netlify should render the document in the visual editor. You can also define editor settings for object and field definitions.
  • fields: an object containing each of the document model’s fields. Learn more about field types.
  • isPage: (optional) set to true to indicate that the document is a page type for the visual editor. Users can also specify this in stackbit.config.ts.
  • localized: (optional) setting this to true 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.

Editor settings for documents

For use with Netlify Visual Editor only.

If you are developing a connector for the visual editor, you can use the editor property on the document to define how Netlify should render the document in the visual editor. There is also the option to define editor settings for object and field definitions.

For a list of available properties, refer to the editor section under define.document() in our reference docs.

In the following example, we specify a custom title and subtitle to use for this page in the visual editor’s preview pane, and add two field groups for the model. Then, when defining the title field on the document, we specify that the field belongs to the General field group.

define.document({
name: `Product`,
editor: {
preview: () => ({
title: `Product Title`,
subtitle: `This is a product`,
}),
fieldGroups: [
{
name: `General`,
label: `General Information`,
icon: `info`,
},
{
name: `SEO`,
label: `Search Engine Optimization`,
}
]
},
fields: {
title: {
type: `String`,
editor: {
group: `General`,
},
},
},
});

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, and fields. For example, this is how we would set the field name updatedAt:
    fields: {
    updatedAt: {
    type: "String",
    required: true,
    },
    }
  • editor: (optional) defines how Netlify should render the field in the visual editor. You can also define editor settings for object and document definitions.
  • type: defines the type of the field. Learn more about field types.
  • required: (optional) set to true to mark a field as required.
  • list: (optional) set to true to indicate the field is a list. To make the list required, set this property to required instead of true. For example, list: required.
  • localized: (optional) setting this to true flags this field as localized. Note that you also have the option to enable document-level localization.

Editor settings for fields

For use with Netlify Visual Editor only.

If you are developing a connector for Netlify Visual Editor, you can use the editor property on the field to define how Netlify should render the field in the visual editor. There is also the option to define editor settings for document and object definitions.

Below we outline some examples of common editor settings. For a full list of available properties, refer to the editor section for fields in the define.document() reference doc.

To specify the UI element to display for a field, use editor.controlType. The element you can select depends on the field type. For example:

const EnumExample = define.enum({
name: `EnumExample`,
options: [`Option 1`, `Option 2`, `Option 3`],
});
// Netlify will display a dropdown picker for the first field
// and a button group picker for the second field in the visual editor
define.document({
name: `Product`,
fields: {
dropdownField: {
type: EnumExample,
editor: {
controlType: `dropdown`,
},
},
buttonGroupField: {
type: EnumExample,
editor: {
controlType: `button-group`,
},
},
},
});

To define how to display a field in the preview pane in the visual editor, use editor.preview. The preview function receives the current document and can render the title, subtitle, and the image based on the current document. For example:

// The function receives args relevant to the type of field the preview is for
define.document({
name: `Product`,
fields: {
title: {
type: `String`,
editor: {
preview: ({ document }) => ({
title: document.fields.title.value,
subtitle: document.fields.content.value.substring(0, 20),
image: document.fields.seo.fields.image.fields.url
})
},
},
},
})

The preview doesn’t need to be a function, it can be an object instead. In this case, the title, subtitle, and image properties should contain the names of the fields used to render the preview. You can use dot notation to specify the path to nested fields.

// When using an object, the title, subtitle, and image properties should
// contain the names of the fields to render. This example assumes the Product
// model has the fields.
define.document({
name: `Product`,
fields: {
title: {
type: `String`,
editor: {
preview: {
title: 'title',
subtitle: 'section.content',
image: 'seo.image.url'
}
},
},
},
})

To define which field group a field belongs to, use editor.group (with the corresponding editor.fieldGroups on the document or object).

// This will place the `title` field in the `General` field group
define.document({
name: `Product`,
editor: {
fieldGroups: [
{
name: `General`,
label: `General Information`,
icon: `info`,
},
{
name: `SEO`,
label: `Search Engine Optimization`,
}
]
},
fields: {
title: {
type: `String`,
editor: {
group: `General`,
},
},
},
})

To specify the minimum, maximum, step, and unit values for number fields, use editor.numberOptions. You can use this with or without the slider control type.

// When the controlType is `slider`, Netlify will display an interactive slider in the Visual
// Editor. If controlType is omitted, an input field will be displayed by default.
define.document({
name: `Product`,
fields: {
price: {
type: "Float",
editor: {
controlType: `slider`,
numberOptions: {
min: 0,
max: 100,
step: 0.01,
unit: `USD`,
},
},
},
},
})

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().

Editor settings for objects

For use with Netlify Visual Editor only.

If you are developing a connector for the visual editor, you can use the editor property on an object to define how Netlify should render the object in the visual editor. There is also the option to define editor settings for document and field definitions.

For a list of available properties, refer to the editor section under define.object() in our reference docs.

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 and visual editor 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, and visual editor users can do the same using stackbit.config.ts. 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 model
  • connectorName: the name of the connector
  • instanceId: 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 model
connector.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 models
connector.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 inline
connector.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",
},
}
});
});

Example for visual editor

Everything included in the example above applies to the visual editor as well but the below example illustrates the unique options you can define for the visual editor.

connector.model(async ({ define }) => {
define.document({
name: `Post`,
editor: {
isPage: true, // sets the Post document as a page type
},
fields: {
imageExample: {
type: `Asset`,
editor: {
controlType: `image`,
},
},
fileExample: {
type: `Asset`,
editor: {
controlType: `file`,
},
},
checkboxExample: {
type: `Boolean`,
required: true,
editor: {
controlType: `checkbox`,
},
},
sliderExample: {
type: `Int`,
required: true,
editor: {
controlType: `slider`,
numberOptions: {
min: 20,
max: 150,
unit: `grugs`,
step: 10,
},
},
},
enumExample: {
type: define.enum({
name: `OneOfThese`,
values: [
{ label: "Yo", value: `Bar` },
{ label: "Hi", value: `Baz` },
],
}),
editor: {
controlType: `dropdown`,
},
required: true,
},
enumExampleList: {
list: true,
type: `OneOfThese`,
required: true,
},
dateTimeFieldExample: {
type: `DateTime`,
required: true,
},
richTextFieldExample: {
type: `richText`,
required: true,
},
inlineObjectExample: {
required: true,
editor: {
label: `Inline Object Example`,
},
type: define.inlineObject({
name: `prefix_example`,
fields: {
hello: {
type: `String`,
required: true,
},
nested: {
required: true,
type: define.inlineEnum({
values: [
{ label: "One", value: `One` },
{ label: "Two", value: `Two` },
],
}),
},
},
}),
},
},
});

Got it!

Your feedback helps us improve our docs.