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 modelPermissions (accessControl/permissions.gadget.ts) - Define role-based access control for your appSettings (settings.gadget.ts) - Configure app-level settings, connections, and authenticationAll metadata file types are exported from gadget-server and can be imported for type safety:
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
Copy 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 Property Type Required Description type"gadget/model-schema/v1" | "gadget/model-schema/v2"Yes Schema version. Use v2 for new models to access searchIndex and filterIndex features. Requires framework version 1.5 or later. storageKeystringYes The storage key addressing this model's data in the database. This is an auto-generated identifier. commentstringNo A 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. fieldsobjectYes An object mapping field API identifiers to field definitions. shopifyobjectNo Configuration 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:
Property Type Description storageKeystringRequired. The storage key addressing this field's data in the database. defaultvaries The 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.
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 },
},
},
},
};
Property Type Description 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.
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" },
},
},
},
};
Property Type Description 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.
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,
},
},
},
};
Property Type Description 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.
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" },
},
},
},
};
Property Type Description 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.
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 },
},
},
},
};
Property Type Description 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.
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,
},
},
},
};
Property Type Description 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).
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,
},
},
},
};
Property Type Description 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.
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 },
},
},
},
};
Property Type Description 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.
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,
},
},
},
};
Property Type Description 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.
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,
},
},
},
};
Property Type Description 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.
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,
},
},
},
};
Property Type Description 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.
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 },
},
},
},
};
Property Type Description 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.
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,
},
},
},
};
Property Type Description 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.
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,
},
},
},
};
Property Type Description 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.
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,
},
},
},
};
Property Type Description 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.
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",
},
},
};
Property Type Description 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.
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" },
},
},
},
};
Property Type Description 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.
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",
},
},
},
};
Property Type Description 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.
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",
},
},
},
};
Property Type Description 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.
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",
},
},
},
};
Property Type Description 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.
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,
},
},
},
};
Property Type Description 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.
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",
],
},
};
Property Type Description shopify.fieldsstring[]An array of Shopify field API identifiers.
Many field types can be connected to Shopify metafields for two-way sync.
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,
},
},
},
};
Property Type Description 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
Copy 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 Property Type Required Description type"gadget/permissions/v1"Yes Schema version identifier. rolesobjectYes An object mapping role keys to role configurations.
Role configuration Each role is defined with the following properties:
Property Type Required Description storageKeystringYes The storage key for this role. defaultobjectNo Default permissions for all models. default.readbooleanNo Default read permission for all models. default.actionbooleanNo Default action permission for all models. modelsobjectNo Per-model permission overrides. actionsobjectNo Permissions for global actions (not associated with a model).
Model permissions For each model, you can specify:
Property Type Description 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
Copy 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
Copy 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 Property Type Required Description type"gadget/settings/v1"Yes Schema version identifier. frameworkVersionstringYes The Gadget framework version. See Framework versions . pluginsobjectYes Configuration 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 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,
},
},
},
};
Property Type Description 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 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 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 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",
},
},
},
};
Property Type Description type"singleClick"BigCommerce app type.
These settings are configured in the Gadget web editor.
See the BigCommerce plugin guide for more information.
ChatGPT connection 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",
},
},
},
};
Property Type Description 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 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: {},
},
},
};
Property Type Description 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 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"],
},
},
},
},
};
Method Type Description emailPasswordbooleanEnable email/password authentication. googleOAuthobjectEnable Google OAuth authentication. googleOAuth.offlineAccessbooleanRequest offline access (refresh tokens). googleOAuth.scopesstring[]OAuth scopes to request from Google.