Fields
What is a field?
A field refers to one specific attribute of each record stored for a model. Like columns in traditional databases, a field records a characteristic or property about the model's subject matter. Fields define the overall structure of the model's storage, deciding the names, datatypes, and validations on all the data a model stores.
For example, take a model representing each customer of an ecommerce system. The customer
model captures the information related to each person who bought something, such as their name and date of birth. In Gadget, each attribute becomes a field, which then stores a value for each record. For our example customer
model, we might create these fields:
name
(field type: string ) - stores the customer's full name.dateOfBirth
(field type: date / time ) - stores the customer's birth date.
You can think of each field as a column in a spreadsheet, and each record as a row.
Field defaults
In Gadget, you can set a default value for most field types in the schema editor. This default value will be used when creating a record if no specific value is provided. You can modify the default value anytime, but the change will only affect new records created afterward.
Types of fields
Field type | Description |
---|---|
string | Stores an arbitrary length string with UTF-8 encoding. |
number | Stores a numerical value, optionally with a certain precision. Used to store both integers, floats, and arbitrary precision values. |
id | Stores a unique identifier for each record in this model. IDs are unique within the model, but not across models. |
enum | Stores one string or a list of strings picked from a global list of allowed values. Can also be configured to accept any string as a valid input. Similar to an enum in traditional SQL databases. |
rich text | Stores an enhanced-Markdown formatted string. Can produce formatted HTML for the markdown. |
boolean | Stores a true or false value. |
date / time | Stores a date or date and time value with a timezone. Accepts and serves data as an ISO-8601 string. Fields configured to not include time will ignore the time / timezone portion of input strings. |
Stores an email as a string. | |
url | Stores a URL as a string. |
file | Stores a file, like an image or a PDF, in cloud storage. See storing files for more information. |
color | Stores a hexadecimal representation of a color. |
vector | Stores a vector of floats. Useful for storing embeddings and performing vector operations like cosine similarity. |
json | Stores any JSON value. Any valid JSON is accepted. |
password | Stores a bcrypt hashed password. One way encrypts the value, so the unencrypted text can't later be retrieved. Suitable for storing passwords and other secrets you might need to compare against but not reveal. |
encrypted string | Stores a string that is encrypted at rest and decrypted when accessed for extra security. Suitable for storing secrets whose contents you need later. |
role list | Stores a list of roles. |
computed | Dynamically generates values based on Gelly snippets involving other fields. See computed fields for more information. |
Working with a record state field
The shopifyShop
model includes a record state field that stores a specific status for each record. While using record state for implementing business logic is optional, it can be a useful way to determine permissions and actions.
Rather than relying on generic operations like create, update, and delete, Gadget allows you to define specific and meaningful actions such as publish, fulfill, or complete. Each action can transition a record from one state to another. For example, publishing a record may move it from the Unpublished state to the Published state, while completing a task can move it from Not Started or Doing to Done.
The record state field holds the current state of each record. The field configuration panel defines the available states for this field.
Default system fields
Each model in Gadget has three fields managed by default. These fields are:
id
: This field captures a unique ID for each of your records within a table
Gadget will automatically handle ID assignments. To ensure performance and stability, Gadget auto-increments IDs on every new mutation regardless of the status of the mutation. As a result, your saved records will always have a unique ID, but the IDs are not guaranteed to be sequential. As an example, your IDs may increment from 11 to 13, as the mutation to create the 12th record failed.
createdAt
: This field captures the timestamp at record creationupdatedAt
: This field captures the timestamp of the last event which changed the record
System fields cannot be given a different name / API identifier.
Computed fields
Produces a value according to a Gelly code snippet using other field values.
For more information, see our computed fields guide
Extending fields with validations
In Gadget, validation is an extension of the field component of a model that helps prevent the entry of malformed or inconsistent data into your system. When modifying records, Gadget verifies that any new data satisfies the validations defined for each field. If an action attempts to write invalid data, it will be unsuccessful, and the invalid data will not be saved.
When making API calls to write data, Gadget provides structured error messages that indicate what data is invalid. These error messages can be presented directly to users, helping guide them in correcting the problematic data.
Validations play a crucial role in maintaining data integrity and ensuring that only valid and coherent data is stored in your system. They serve as a safeguard against incorrect or incompatible data and facilitate a smooth user experience by providing clear feedback on data entry errors.
Applicable validations to field types
Gadget offers a selection of common validations for each field type. These vary from one field type to another:
Field type | Applicable Validations |
---|---|
string | Required , Uniqueness , String Length Range , Run Code Snippet , RegExp Pattern |
number | Required , Uniqueness , Number Min/Max Range , Run Code Snippet |
id | No applicable validations |
enum | Required , Uniqueness , Run Code Snippet |
rich text | Required , Uniqueness , String Length Range , Run Code Snippet , RegExp Pattern |
boolean | Required , Uniqueness , Run Code Snippet |
date / time | Required , Uniqueness , Run Code Snippet , RegExp Pattern , |
Required , Uniqueness , String Length Range , Run Code Snippet , RegExp Pattern | |
url | Required , Uniqueness , String Length Range , Run Code Snippet , RegExp Pattern |
file | Required , File Size Range , Image File , Run Code Snippet |
color | Required , Run Code Snippet , Color |
json | Required , Uniqueness , Run Code Snippet |
vector | Required , Uniqueness , Vector Dimensions , Run Code Snippet |
password | No applicable validations |
encrypted string | No applicable validations |
role list | No applicable validations |
computed | No applicable validations |
Required validation
A required validation refers to the process of ensuring that a particular input or data field is not empty or null when it is required to have a value. It is a form of data validation that checks whether a user has provided the necessary information before proceeding with a certain operation or action.
Uniqueness validation
The Uniqueness
validation in Gadget ensures that each value for a field occurs only once in the database. It is commonly used for fields like email or username to guarantee that each value is unique within the system.
Underlying database constraints are leveraged to enforce the uniqueness validation reliably, even in transactional contexts and under load.
If a Uniqueness
validation is added to a model that already contains data, the setup may fail if there are duplicate values for the field. In such cases, the uniqueness constraint is not enforced until the duplicate data is corrected by either deleting records or modifying the field values to ensure uniqueness. Once the data is made unique, the validation can be re-added and verified to succeed.
The Uniqueness
validation can be configured as case-sensitive or case-insensitive by toggling the Case Sensitive option. When the option is disabled, values are considered duplicates regardless of letter casing. This is useful for scenarios like URL slugs or emails, where users may enter values with different casing but uniqueness should be maintained regardless of casing.
Number Min/Max range validation
Number min/max range validation ensures whether a numeric value falls within a specified range of minimum and maximum values. It is commonly used to ensure that numeric input or data meets certain constraints or requirements.
String length range validation
String length range validation is a type of data validation that ensures whether a string has a specific length or falls within a certain range of lengths. It is commonly used to enforce length restrictions or requirements on user input or data fields
Image validation
The Image File
validation ensures that uploaded files meet three criteria:
- They have an
image
based MIME type or subtype — see MDN's reference. - They are in one of the following image file formats:
gif
,jpg
,png
,svg
, orwebp
(common web formats);apng
,bmp
,bpg
,cr2
,cur
,dcm
,flif
,heic
,ico
,jp2
,jpm
,jpx
,jxr
,psd
, ortif
(additional formats). - They do not contain an animation if the Allow animated images option is disabled.
File size range validation
File size range validation ensures the size of a file falls within a specified range of acceptable sizes. It is commonly used when handling file uploads or processing files in applications. By validating the file size, you can control resource consumption, prevent server overload, and maintain data integrity.
Color Validation
Color validation ensures whether a given value represents a valid color. It is typically used to ensure that color values provided by users or within a system adhere to a specific format or range of valid colors.
Vector dimension validation
The Vector dimensions validation in Gadget ensures that all vectors stored in a field have the same count of dimensions.
It's important to note that Gadget restricts the sorting or filtering of vectors unless all the stored vectors and input vectors have the same dimension count. This is because many vector operations used in Gadget are not defined for vectors with different dimensions.
To enforce the consistency of vector dimensions, you can add a Vector dimensions validation to a field. This validation ensures that all vectors stored in the field have the same number of dimensions.
For more details about this validation and any related errors, you can refer to the GGT_DIFFERENT_VECTOR_DIMENSIONS error in the Gadget documentation.
RegEx pattern validation
RegExp also known as regular expression pattern validation, is used to validate strings or data against a specific pattern or format using regular expressions. Regular expressions (RegEx) are sequences of characters that define a search pattern, allowing for powerful and flexible pattern matching and validation.
Gadget uses the RegExp engine built into node.js to run RegExp Pattern validations. For information on how to write JavaScript regular expressions, please refer to MDN's reference.
Adding custom code validation
In Gadget, you can add your custom validations written in JavaScript. The custom validation runs a JS function each time a record is created or updated to check if that change should succeed. If the function reports no errors, then the save will be complete, and if the data is somehow invalid, the validation can add structured error messages to the different fields of the record being changed.
Each custom validation file must export a function as the default export function from the module. The function can be marked async
or return a Promise
. The function shouldn't return anything and, instead, should add errors to any fields determined to be invalid using the passed-in errors
object, which holds a structured set of errors keyed by a field.
Validation functions get passed one big context object and not individual arguments. This is because there are a lot of arguments
you may or may not need. Most Gadget users use JavaScript destructuring to pull out just the context keys that they need. The most common
keys used from the context are the record itself at the record
key, the api
object, which is an already set up instance of the
JavaScript client for the application at api
, and the Errors
object at the errors
key.
Here's a custom validation that tests if a record's full name is not null and its last name is also not null:
1import type { FooBarFieldValidationContext } from "gadget-server";23export default async ({4 api,5 record,6 errors,7 logger,8 field,9}: FooBarFieldValidationContext) => {10 if (record.firstName && !record.lastName) {11 errors.add("lastName", "must be set if the first name is set");12 }13};
1import type { FooBarFieldValidationContext } from "gadget-server";23export default async ({4 api,5 record,6 errors,7 logger,8 field,9}: FooBarFieldValidationContext) => {10 if (record.firstName && !record.lastName) {11 errors.add("lastName", "must be set if the first name is set");12 }13};
Here's a validation that tests if a record's phone number is valid:
1import PhoneNumber from "awesome-phonenumber";2import type { FooBarFieldValidationContext } from "gadget-server";34export default async ({5 api,6 record,7 errors,8 logger,9 field,10}: FooBarFieldValidationContext) => {11 const number = new PhoneNumber(record["customerPhoneNumber"]);12 if (!number.isValid()) {13 errors.add("customerPhoneNumber", "is not a valid phone number");14 }15};
1import PhoneNumber from "awesome-phonenumber";2import type { FooBarFieldValidationContext } from "gadget-server";34export default async ({5 api,6 record,7 errors,8 logger,9 field,10}: FooBarFieldValidationContext) => {11 const number = new PhoneNumber(record["customerPhoneNumber"]);12 if (!number.isValid()) {13 errors.add("customerPhoneNumber", "is not a valid phone number");14 }15};
If a validation function throws an error while executing, the action validating the current record will fail, and if the action is transactional, any operations performed in the run
function will be rolled back.