Gadget metadata files 

Gadget uses TypeScript metadata files to define the structure of your application. These metadata files are the source of truth for your models, permissions, and app settings. There are three types of metadata files:

  • Model schemas (api/models/**/<model-name>/schema.gadget.ts) - Define the fields and structure of each model
  • Permissions (accessControl/permissions.gadget.ts) - Define role-based access control for your app
  • Settings (settings.gadget.ts) - Configure app-level settings, connections, and authentication

All metadata file types are exported from gadget-server and can be imported for type safety:

JavaScript
import type { GadgetModel, GadgetPermissions, GadgetSettings } from "gadget-server";
import type { GadgetModel, GadgetPermissions, GadgetSettings } from "gadget-server";

Metadata files are not visible in the web editor. They are used for source control when you use ggt dev to sync your project locally.

Model schemas 

Each model in your Gadget app has a schema.gadget.ts file that defines its fields and configuration. The schema exports a GadgetModel type that Gadget uses to generate your database tables, API, and type definitions.

api/models/post/schema.gadget.js
JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Ye9OpF2gBjPk", fields: { title: { type: "string", storageKey: "Zf1QrH4iDlRm", validations: { required: true, stringLength: { min: 1, max: 255 }, }, }, body: { type: "richText", storageKey: "Ag3StJ6kFnTo", }, publishedAt: { type: "dateTime", storageKey: "Bh5UvL8mHpVq", includeTime: true, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Ye9OpF2gBjPk", fields: { title: { type: "string", storageKey: "Zf1QrH4iDlRm", validations: { required: true, stringLength: { min: 1, max: 255 }, }, }, body: { type: "richText", storageKey: "Ag3StJ6kFnTo", }, publishedAt: { type: "dateTime", storageKey: "Bh5UvL8mHpVq", includeTime: true, }, }, };

Model-level properties 

PropertyTypeRequiredDescription
type"gadget/model-schema/v1" | "gadget/model-schema/v2"YesSchema version. Use v2 for new models to access searchIndex and filterIndex features. Requires framework version 1.5 or later.
storageKeystringYesThe storage key addressing this model's data in the database. This is an auto-generated identifier.
commentstringNoA comment describing this model to other developers.
searchIndexbooleanNo(v2 only) Whether this model can be searched directly by full text search queries. Defaults to true when omitted. If false, the model can still be searched through related models.
fieldsobjectYesAn object mapping field API identifiers to field definitions.
shopifyobjectNoConfiguration for this model's connection to Shopify. See Shopify configuration.

Field types 

Gadget supports many field types, each with specific properties and validations. Every field must have a type and storageKey property.

Common field properties 

These properties are available on most field types:

PropertyTypeDescription
storageKeystringRequired. The storage key addressing this field's data in the database.
defaultvariesThe default value for newly created records. Type depends on field type.
validationsobjectValidation rules to apply when saving records.
shopifyMetafieldobjectConnect this field to a Shopify metafield. See Shopify metafields.
filterIndexboolean(v2 only) Whether sorting and filtering are enabled for this field. Defaults to true when omitted.
searchIndexboolean(v2 only) Whether this field can be searched by full text search queries. Defaults to true when omitted.

string 

Editor tag: string

Stores strings of UTF-8 characters.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "4wsXHT6K37US", fields: { name: { type: "string", storageKey: "Hs8KqL2mNpRt", default: "Untitled", validations: { required: true, stringLength: { min: 1, max: 500 }, regex: ["^[A-Za-z]+$"], unique: { scopeByField: "category", caseSensitive: true }, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "4wsXHT6K37US", fields: { name: { type: "string", storageKey: "Hs8KqL2mNpRt", default: "Untitled", validations: { required: true, stringLength: { min: 1, max: 500 }, regex: ["^[A-Za-z]+$"], unique: { scopeByField: "category", caseSensitive: true }, }, }, }, };
PropertyTypeDescription
defaultstringDefault value for new records.
validations.requiredbooleanValidate that this field has a value.
validations.stringLength{ min, max }Validate the string length is within range. Both min and max are number | null.
validations.regex(string | null)[]Ensure values match one of the regular expressions.
validations.uniqueboolean | { scopeByField?, caseSensitive? }Ensure values are unique. Can scope by another field and configure case sensitivity.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

number 

Editor tag: number

Stores numeric values with optional decimal precision.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Jw3XvB9cYdFg", fields: { price: { type: "number", storageKey: "Km5ZnD7eAhUi", default: 0, decimals: 2, validations: { required: true, numberRange: { min: 0, max: 10000 }, unique: { scopeByField: "category" }, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Jw3XvB9cYdFg", fields: { price: { type: "number", storageKey: "Km5ZnD7eAhUi", default: 0, decimals: 2, validations: { required: true, numberRange: { min: 0, max: 10000 }, unique: { scopeByField: "category" }, }, }, }, };
PropertyTypeDescription
defaultnumberDefault value for new records.
decimalsnumberNumber of decimal places to store.
validations.requiredbooleanValidate that this field has a value.
validations.numberRange{ min, max }Validate the number is within range.
validations.uniqueboolean | { scopeByField? }Ensure values are unique, optionally scoped by another field.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

boolean 

Editor tag: boolean

Stores true or false values.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Lo9PrF1gCjWk", fields: { isPublished: { type: "boolean", storageKey: "Mq2TsH4iElXm", default: false, validations: { required: true, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Lo9PrF1gCjWk", fields: { isPublished: { type: "boolean", storageKey: "Mq2TsH4iElXm", default: false, validations: { required: true, }, }, }, };
PropertyTypeDescription
defaultbooleanDefault value for new records.
validations.requiredbooleanValidate that this field has a value.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

dateTime 

Editor tag: date / time

Stores timestamps with millisecond precision in UTC, or dates with day precision if includeTime is false.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Nr6UvJ8kGnYo", fields: { publishedAt: { type: "dateTime", storageKey: "Ot4VwL0mIpZq", includeTime: true, default: "2024-01-01T00:00:00.000Z", validations: { required: true, unique: { scopeByField: "category" }, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Nr6UvJ8kGnYo", fields: { publishedAt: { type: "dateTime", storageKey: "Ot4VwL0mIpZq", includeTime: true, default: "2024-01-01T00:00:00.000Z", validations: { required: true, unique: { scopeByField: "category" }, }, }, }, };
PropertyTypeDescription
includeTimebooleanWhether this field includes time. When false, stores only the date.
defaultstringDefault value as an ISO 8601 string.
validations.requiredbooleanValidate that this field has a value.
validations.uniqueboolean | { scopeByField? }Ensure values are unique.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

email 

Editor tag: email

Stores a well-formatted email address as a string. Gadget automatically validates email format.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Pu8XyN2oKrAs", fields: { email: { type: "email", storageKey: "Qv0ZaP4qMtBu", validations: { required: true, unique: { caseSensitive: false }, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Pu8XyN2oKrAs", fields: { email: { type: "email", storageKey: "Qv0ZaP4qMtBu", validations: { required: true, unique: { caseSensitive: false }, }, }, }, };
PropertyTypeDescription
defaultstringDefault value for new records.
validations.requiredbooleanValidate that this field has a value.
validations.stringLength{ min, max }Validate the length is within range.
validations.regex(string | null)[]Additional regex validation.
validations.uniqueboolean | { scopeByField?, caseSensitive? }Ensure values are unique.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

url 

Editor tag: url

Stores a well-formatted URL as a string. Gadget automatically validates URL format.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Rw3AbR6sOvCw", fields: { website: { type: "url", storageKey: "Sx5CdT8uQxDy", default: "https://example.com", validations: { required: true, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Rw3AbR6sOvCw", fields: { website: { type: "url", storageKey: "Sx5CdT8uQxDy", default: "https://example.com", validations: { required: true, }, }, }, };
PropertyTypeDescription
defaultstringDefault value for new records.
validations.requiredbooleanValidate that this field has a value.
validations.stringLength{ min, max }Validate the length is within range.
validations.regex(string | null)[]Additional regex validation.
validations.uniqueboolean | { scopeByField?, caseSensitive? }Ensure values are unique.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

color 

Editor tag: color

Stores a well-formatted hex color as a string (for example, #FF5733).

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Ty7EfV0wSzFa", fields: { brandColor: { type: "color", storageKey: "Ua9GhX2yUbHc", default: "#000000", validations: { required: true, color: true, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Ty7EfV0wSzFa", fields: { brandColor: { type: "color", storageKey: "Ua9GhX2yUbHc", default: "#000000", validations: { required: true, color: true, }, }, }, };
PropertyTypeDescription
defaultstringDefault hex color value.
validations.requiredbooleanValidate that this field has a value.
validations.stringLength{ min, max }Validate the length is within range.
validations.regex(string | null)[]Additional regex validation.
validations.colorbooleanValidate that the value is a valid hex color string.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

richText 

Editor tag: rich text

Stores markdown content for human consumption.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Vb1IjZ4aWdJe", fields: { content: { type: "richText", storageKey: "Wc3KlB6cYfLg", default: { markdown: "# Welcome\n\nStart writing here." }, validations: { required: true, stringLength: { min: null, max: 50000 }, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Vb1IjZ4aWdJe", fields: { content: { type: "richText", storageKey: "Wc3KlB6cYfLg", default: { markdown: "# Welcome\n\nStart writing here." }, validations: { required: true, stringLength: { min: null, max: 50000 }, }, }, }, };
PropertyTypeDescription
default{ markdown: string }Default markdown content.
validations.requiredbooleanValidate that this field has a value.
validations.stringLength{ min, max }Validate the markdown length is within range.
validations.regex(string | null)[]Ensure values match a regular expression.
validations.uniqueboolean | { scopeByField?, caseSensitive? }Ensure values are unique.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

json 

Editor tag: json

Stores arbitrary JSON, including objects, arrays, and primitive values.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Xd5MnD8eZhNi", fields: { metadata: { type: "json", storageKey: "Ye7OpF0gBjPk", default: { tags: [], settings: {} }, validations: { required: true, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Xd5MnD8eZhNi", fields: { metadata: { type: "json", storageKey: "Ye7OpF0gBjPk", default: { tags: [], settings: {} }, validations: { required: true, }, }, }, };
PropertyTypeDescription
defaultanyDefault JSON value for new records.
defaultAsStringstringAlternative to default: the default value as a JSON string. Mutually exclusive with default.
validations.requiredbooleanValidate that this field has a value.
validations.uniqueboolean | { scopeByField? }Ensure values are unique.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

enum 

Editor tag: enum

Stores a single string or an array of strings constrained to a list of valid options.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Zf9QrH2iDlRm", fields: { status: { type: "enum", storageKey: "Ag1StJ4kFnTo", options: ["draft", "published", "archived"], default: "draft", acceptMultipleSelections: false, acceptUnlistedOptions: false, validations: { required: true, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Zf9QrH2iDlRm", fields: { status: { type: "enum", storageKey: "Ag1StJ4kFnTo", options: ["draft", "published", "archived"], default: "draft", acceptMultipleSelections: false, acceptUnlistedOptions: false, validations: { required: true, }, }, }, };
PropertyTypeDescription
optionsstring[]Required. The list of available options for this field.
defaultstring | string[]Default value(s) for new records.
acceptMultipleSelectionsbooleanWhether this field stores an array or a single string selection.
acceptUnlistedOptionsbooleanWhether this field accepts any option or only the listed options.
validations.requiredbooleanValidate that this field has a value.
validations.uniqueboolean | { scopeByField? }Ensure values are unique.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

vector 

Editor tag: vector

Stores a list of floats suitable for vector similarity operations, commonly used for AI embeddings.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Bh3UvL6mHpVq", fields: { embedding: { type: "vector", storageKey: "Ci5WxN8oJrXs", validations: { required: true, dimensionCount: 1536, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Bh3UvL6mHpVq", fields: { embedding: { type: "vector", storageKey: "Ci5WxN8oJrXs", validations: { required: true, dimensionCount: 1536, }, }, }, };
PropertyTypeDescription
validations.requiredbooleanValidate that this field has a value.
validations.dimensionCountnumber | nullValidate vectors have a specific number of dimensions.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

Vector fields are commonly used with AI embeddings. The dimensionCount should match your embedding model's output dimensions (for example, 1536 for OpenAI's text-embedding-ada-002).


file 

Editor tag: file

Stores a reference to a single file uploaded to cloud storage.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Dj7YzP0qLtZu", fields: { avatar: { type: "file", storageKey: "Ek9AbR2sNvBw", allowPublicAccess: true, validations: { required: true, fileSizeRange: { min: null, max: 5242880 }, imagesOnly: { allowAnimatedImages: true }, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Dj7YzP0qLtZu", fields: { avatar: { type: "file", storageKey: "Ek9AbR2sNvBw", allowPublicAccess: true, validations: { required: true, fileSizeRange: { min: null, max: 5242880 }, imagesOnly: { allowAnimatedImages: true }, }, }, }, };
PropertyTypeDescription
allowPublicAccessbooleanWhether this field allows public access to uploaded files.
validations.requiredbooleanValidate that this field has a value.
validations.fileSizeRange{ min, max }Validate file size in bytes.
validations.imagesOnlyboolean | { allowAnimatedImages }Restrict uploads to image files only.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

File fields do not support filterIndex or searchIndex.


encryptedString 

Editor tag: encrypted string

Stores a string that is encrypted at rest. Use this for sensitive data that needs to be retrieved in its original form.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Fl1CdT4uPxDy", fields: { apiKey: { type: "encryptedString", storageKey: "Gm3EfV6wRzFa", validations: { required: true, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Fl1CdT4uPxDy", fields: { apiKey: { type: "encryptedString", storageKey: "Gm3EfV6wRzFa", validations: { required: true, }, }, }, };
PropertyTypeDescription
validations.requiredbooleanValidate that this field has a value.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

Encrypted string fields cannot be filtered, sorted, or searched because the values are encrypted.


password 

Editor tag: password

Stores a hashed and salted bcrypt string. Values cannot be retrieved; they can only be verified.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Hn5GhX8yTbHc", fields: { password: { type: "password", storageKey: "Io7IjZ0aVdJe", validations: { required: true, strongPassword: true, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Hn5GhX8yTbHc", fields: { password: { type: "password", storageKey: "Io7IjZ0aVdJe", validations: { required: true, strongPassword: true, }, }, }, };
PropertyTypeDescription
validations.requiredbooleanValidate that this field has a value.
validations.strongPasswordbooleanValidate that the password meets strength requirements.

Password fields are one-way hashed. You cannot retrieve the original value, only verify it using the verifyPassword helper.


roleList 

Editor tag: role list

Stores a list of role names from your app's role list. Commonly used on User models for role-based access control.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Jp9KlB2cXfLg", fields: { roles: { type: "roleList", storageKey: "Kq1MnD4eZhNi", default: ["signed-in"], validations: { required: true, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Jp9KlB2cXfLg", fields: { roles: { type: "roleList", storageKey: "Kq1MnD4eZhNi", default: ["signed-in"], validations: { required: true, }, }, }, };
PropertyTypeDescription
defaultstring[]Default roles for new records.
validations.requiredbooleanValidate that this field has a value.
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

computed 

Stores a value computed using a Gelly expression. Computed fields are read-only and automatically updated.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Lr3OpF6gBjPk", fields: { fullName: { type: "computed", storageKey: "Ms5QrH8iDlRm", sourceFile: "api/models/user/fields/fullName.gelly", }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Lr3OpF6gBjPk", fields: { fullName: { type: "computed", storageKey: "Ms5QrH8iDlRm", sourceFile: "api/models/user/fields/fullName.gelly", }, }, };
PropertyTypeDescription
sourceFilestring | nullPath to the Gelly file containing the computation expression.

Computed fields do not accept validations or default values since their values are derived from other fields.


Relationship fields 

Gadget supports four types of relationship fields to connect models together.

belongsTo 

Editor tag: belongs to

Stores an ID pointing to a record of the parent model. This is the "many" side of a one-to-many relationship.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Nt7StJ0kFnTo", fields: { author: { type: "belongsTo", storageKey: "Ou9UvL2mHpVq", parent: { model: "user" }, validations: { required: true, unique: { scopeByField: "organization" }, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Nt7StJ0kFnTo", fields: { author: { type: "belongsTo", storageKey: "Ou9UvL2mHpVq", parent: { model: "user" }, validations: { required: true, unique: { scopeByField: "organization" }, }, }, }, };
PropertyTypeDescription
parent.modelstring | nullThe API identifier of the parent model.
validations.requiredbooleanValidate that this field has a value.
validations.uniqueboolean | { scopeByField? }Ensure values are unique (creates a one-to-one relationship).
validations.run(string | null)[]Paths to TypeScript/JavaScript files that implement custom validations.

hasOne 

Editor tag: has one

Fetches a single record from a child model, powered by a belongsTo field on the child model with a unique validation.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Pv1WxN4oJrXs", fields: { profile: { type: "hasOne", storageKey: "Qw3YzP6qLtZu", child: { model: "profile", belongsToField: "user", }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Pv1WxN4oJrXs", fields: { profile: { type: "hasOne", storageKey: "Qw3YzP6qLtZu", child: { model: "profile", belongsToField: "user", }, }, }, };
PropertyTypeDescription
child.modelstring | nullThe API identifier of the child model.
child.belongsToFieldstring | nullThe API identifier of the belongsTo field on the child model that powers this relationship.

hasMany 

Editor tag: has many

Fetches a list of records from a child model, powered by a belongsTo field on the child model.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Rx5AbR8sNvBw", fields: { posts: { type: "hasMany", storageKey: "Sy7CdT0uPxDy", children: { model: "post", belongsToField: "author", }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Rx5AbR8sNvBw", fields: { posts: { type: "hasMany", storageKey: "Sy7CdT0uPxDy", children: { model: "post", belongsToField: "author", }, }, }, };
PropertyTypeDescription
children.modelstring | nullThe API identifier of the child model.
children.belongsToFieldstring | nullThe API identifier of the belongsTo field on the child model that powers this relationship.

hasManyThrough 

Editor tag: has many through

Fetches a list of sibling records through an intermediate join model. This creates a many-to-many relationship.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Tz9EfV2wRzFa", fields: { tags: { type: "hasManyThrough", storageKey: "Ua1GhX4yTbHc", sibling: { model: "tag", relatedField: "posts", }, join: { model: "postTag", belongsToSelfField: "post", belongsToSiblingField: "tag", }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Tz9EfV2wRzFa", fields: { tags: { type: "hasManyThrough", storageKey: "Ua1GhX4yTbHc", sibling: { model: "tag", relatedField: "posts", }, join: { model: "postTag", belongsToSelfField: "post", belongsToSiblingField: "tag", }, }, }, };
PropertyTypeDescription
sibling.modelstring | nullThe API identifier of the sibling model.
sibling.relatedFieldstring | nullThe API identifier of the inverse hasManyThrough field on the sibling model.
join.modelstring | nullThe API identifier of the intermediate join model.
join.belongsToSelfFieldstring | nullThe API identifier of the belongsTo field on the join model pointing to this model.
join.belongsToSiblingFieldstring | nullThe API identifier of the belongsTo field on the join model pointing to the sibling model.

hasOne, hasMany, and hasManyThrough fields do not store data themselves. They are virtual fields that fetch related records based on other fields.


Shopify configuration 

If your model is connected to Shopify, you can configure which Shopify fields to sync.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "DataModel-Shopify-Product", fields: {}, shopify: { fields: { title: true, handle: true, description: { filterIndex: true, searchIndex: true, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "DataModel-Shopify-Product", fields: {}, shopify: { fields: { title: true, handle: true, description: { filterIndex: true, searchIndex: true, }, }, }, };
PropertyTypeDescription
shopify.fieldsobjectAn object mapping Shopify field names to true (use defaults) or an object with filterIndex and searchIndex options.

Shopify configuration gadget/model-schema/v1 

Shopify configuration on apps on framework versions prior to framework version v1.5.0 have a list of Shopify field API identifiers, excluding the id field.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v1", storageKey: "DataModel-Shopify-Product", fields: {}, shopify: { fields: [ "body", "category", "compareAtPriceRange", "handle", "images", "productCategory", "productType", "publishedAt", "shop", "shopifyCreatedAt", "shopifyUpdatedAt", "status", "tags", "templateSuffix", "title", "vendor", ], }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v1", storageKey: "DataModel-Shopify-Product", fields: {}, shopify: { fields: [ "body", "category", "compareAtPriceRange", "handle", "images", "productCategory", "productType", "publishedAt", "shop", "shopifyCreatedAt", "shopifyUpdatedAt", "status", "tags", "templateSuffix", "title", "vendor", ], }, };
PropertyTypeDescription
shopify.fieldsstring[]An array of Shopify field API identifiers.

Shopify metafields 

Many field types can be connected to Shopify metafields for two-way sync.

JavaScript
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Wc5KlB8cXfLg", fields: { customLabel: { type: "string", storageKey: "Xd7MnD0eZhNi", shopifyMetafield: { privateMetafield: false, namespace: "custom", key: "label", metafieldType: "single_line_text_field", allowMultipleEntries: false, }, }, }, };
import type { GadgetModel } from "gadget-server"; export const schema: GadgetModel = { type: "gadget/model-schema/v2", storageKey: "Wc5KlB8cXfLg", fields: { customLabel: { type: "string", storageKey: "Xd7MnD0eZhNi", shopifyMetafield: { privateMetafield: false, namespace: "custom", key: "label", metafieldType: "single_line_text_field", allowMultipleEntries: false, }, }, }, };
PropertyTypeDescription
privateMetafieldbooleanWhether this metafield is private (only accessible by your app).
namespacestringThe namespace of the metafield.
keystringThe key of the metafield.
metafieldTypestringThe Shopify metafield type.
allowMultipleEntriesbooleanWhether this metafield allows multiple entries (list metafields).

Permissions 

The accessControl/permissions.gadget.ts file defines role-based access control for your entire application. It specifies which roles can read records and run actions on each model.

accessControl/permissions.gadget.js
JavaScript
import type { GadgetPermissions } from "gadget-server"; export const permissions: GadgetPermissions = { type: "gadget/permissions/v1", roles: { unauthenticated: { storageKey: "unauthenticated", default: { read: false, action: false, }, models: { post: { read: true, actions: { create: false, update: false, delete: false, }, }, }, }, "signed-in": { storageKey: "signed-in", default: { read: true, action: true, }, models: { user: { read: { filter: "accessControl/filters/user/tenant.gelly", }, actions: { update: { filter: "accessControl/filters/user/tenant.gelly", }, delete: { filter: "accessControl/filters/user/tenant.gelly", }, }, }, }, actions: { sendNewsletter: true, }, }, }, };
import type { GadgetPermissions } from "gadget-server"; export const permissions: GadgetPermissions = { type: "gadget/permissions/v1", roles: { unauthenticated: { storageKey: "unauthenticated", default: { read: false, action: false, }, models: { post: { read: true, actions: { create: false, update: false, delete: false, }, }, }, }, "signed-in": { storageKey: "signed-in", default: { read: true, action: true, }, models: { user: { read: { filter: "accessControl/filters/user/tenant.gelly", }, actions: { update: { filter: "accessControl/filters/user/tenant.gelly", }, delete: { filter: "accessControl/filters/user/tenant.gelly", }, }, }, }, actions: { sendNewsletter: true, }, }, }, };

Permissions structure 

PropertyTypeRequiredDescription
type"gadget/permissions/v1"YesSchema version identifier.
rolesobjectYesAn object mapping role keys to role configurations.

Role configuration 

Each role is defined with the following properties:

PropertyTypeRequiredDescription
storageKeystringYesThe storage key for this role.
defaultobjectNoDefault permissions for all models.
default.readbooleanNoDefault read permission for all models.
default.actionbooleanNoDefault action permission for all models.
modelsobjectNoPer-model permission overrides.
actionsobjectNoPermissions for global actions (not associated with a model).

Model permissions 

For each model, you can specify:

PropertyTypeDescription
readboolean | { filter: string | null }Read permission. Use true/false for simple access, or provide a filter file path for row-level security.
actionsobjectAn object mapping action names to permissions.
actions.[actionName]boolean | { filter: string | null }Permission for a specific action. Use a filter for row-level security.

Filter files 

Filters are Gelly expressions that restrict which records a role can access. They are stored in separate files and referenced by path.

accessControl/filters/user/tenant.gelly
gelly
filter ($session: Session) on User [ where id == $session.userId ]

Filters provide row-level security by limiting which records a role can read or act upon. The filter expression runs in the context of the current session.


Settings 

The settings.gadget.ts file at the root of your api directory configures app-level settings including the framework version, connections, and authentication.

settings.gadget.js
JavaScript
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { connections: { shopify: { apiVersion: "2025-01", enabledModels: ["shopifyShop", "shopifyProduct", "shopifyCollection"], type: "partner", scopes: ["read_products", "write_products"], }, openai: true, sentry: true, }, authentications: { settings: { redirectOnSignIn: "/", signInPath: "/sign-in", unauthorizedUserRedirect: "signInPath", defaultSignedInRoles: ["signed-in"], }, methods: { emailPassword: true, googleOAuth: { offlineAccess: false, scopes: ["email", "profile"], }, }, }, }, };
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { connections: { shopify: { apiVersion: "2025-01", enabledModels: ["shopifyShop", "shopifyProduct", "shopifyCollection"], type: "partner", scopes: ["read_products", "write_products"], }, openai: true, sentry: true, }, authentications: { settings: { redirectOnSignIn: "/", signInPath: "/sign-in", unauthorizedUserRedirect: "signInPath", defaultSignedInRoles: ["signed-in"], }, methods: { emailPassword: true, googleOAuth: { offlineAccess: false, scopes: ["email", "profile"], }, }, }, }, };

Settings structure 

PropertyTypeRequiredDescription
type"gadget/settings/v1"YesSchema version identifier.
frameworkVersionstringYesThe Gadget framework version. See Framework versions.
pluginsobjectYesConfiguration for connections and authentication.

Framework versions 

The frameworkVersion determines which Gadget features are available. See the framework version changelog for more information.

Connections 

The plugins.connections object configures third-party service integrations.

We do not recommend editing connection configuration manually. Instead, use the web editor to configure your connections.

Shopify connection 

JavaScript
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { connections: { shopify: { apiVersion: "2025-01", enabledModels: ["shopifyShop", "shopifyProduct"], type: "partner", scopes: ["read_products", "write_products"], customerAuthenticationEnabled: true, }, }, }, };
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { connections: { shopify: { apiVersion: "2025-01", enabledModels: ["shopifyShop", "shopifyProduct"], type: "partner", scopes: ["read_products", "write_products"], customerAuthenticationEnabled: true, }, }, }, };
PropertyTypeDescription
apiVersionstringShopify API version (for example, "2025-01").
enabledModelsstring[]List of Shopify models to sync.
type"partner" | "admin"Connection type. Use "partner" for public apps with OAuth, "admin" for private/custom apps. "admin" connections have been deprecated by Shopify, all new apps will use "partner" connections.
scopesstring[](Partner only) OAuth scopes to request.
customerAuthenticationEnabledboolean(Partner only) Enable Shopify customer authentication.

These settings are configured in the Gadget web editor.

See the Shopify plugin guide for more information.

OpenAI connection 

JavaScript
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { connections: { openai: true, }, }, };
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { connections: { openai: true, }, }, };

Set to true to enable the OpenAI connection. Configure API keys in the Gadget web editor.

See the OpenAI plugin guide for more information.

Sentry connection 

JavaScript
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { connections: { sentry: true, }, }, };
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { connections: { sentry: true, }, }, };

Set to true to enable Sentry error tracking. Configure the Sentry DSN (Data Source Name) in the Gadget web editor.

See the Sentry plugin guide for more information.

BigCommerce connection 

JavaScript
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { connections: { bigcommerce: { type: "singleClick", }, }, }, };
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { connections: { bigcommerce: { type: "singleClick", }, }, }, };
PropertyTypeDescription
type"singleClick"BigCommerce app type.

These settings are configured in the Gadget web editor.

See the BigCommerce plugin guide for more information.

ChatGPT connection 

JavaScript
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { connections: { chatgpt: { authorizationPath: "/chatgpt/authorize", }, }, }, };
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { connections: { chatgpt: { authorizationPath: "/chatgpt/authorize", }, }, }, };
PropertyTypeDescription
authorizationPathstringThe path for ChatGPT authorization flow.

See the ChatGPT connection guide for more information.

Authentication 

The plugins.authentications object configures user authentication for your app.

Authentication settings 

JavaScript
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { authentications: { settings: { redirectOnSignIn: "/dashboard", signInPath: "/sign-in", unauthorizedUserRedirect: "signInPath", accessControlForSignedInUsers: ["admin", "editor"], defaultSignedInRoles: ["signed-in"], }, methods: {}, }, }, };
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { authentications: { settings: { redirectOnSignIn: "/dashboard", signInPath: "/sign-in", unauthorizedUserRedirect: "signInPath", accessControlForSignedInUsers: ["admin", "editor"], defaultSignedInRoles: ["signed-in"], }, methods: {}, }, }, };
PropertyTypeDescription
redirectOnSignInstringURL to redirect to after successful sign-in.
signInPathstringPath to the sign-in page.
unauthorizedUserRedirect"redirect" | "signInPath" | "show-403-error" | "403Error"Behavior when an unauthorized user accesses a protected route.
accessControlForSignedInUsersstring[]Roles required for signed-in access (optional).
defaultSignedInRolesstring[]Roles automatically assigned to new users (optional).

Authentication methods 

JavaScript
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { authentications: { settings: { redirectOnSignIn: "/", signInPath: "/sign-in", unauthorizedUserRedirect: "signInPath", }, methods: { emailPassword: true, googleOAuth: { offlineAccess: true, scopes: ["email", "profile", "https://www.googleapis.com/auth/calendar"], }, }, }, }, };
import type { GadgetSettings } from "gadget-server"; export const settings: GadgetSettings = { type: "gadget/settings/v1", frameworkVersion: "v1.5.0", plugins: { authentications: { settings: { redirectOnSignIn: "/", signInPath: "/sign-in", unauthorizedUserRedirect: "signInPath", }, methods: { emailPassword: true, googleOAuth: { offlineAccess: true, scopes: ["email", "profile", "https://www.googleapis.com/auth/calendar"], }, }, }, }, };
MethodTypeDescription
emailPasswordbooleanEnable email/password authentication.
googleOAuthobjectEnable Google OAuth authentication.
googleOAuth.offlineAccessbooleanRequest offline access (refresh tokens).
googleOAuth.scopesstring[]OAuth scopes to request from Google.

Was this page helpful?