The DEFINE FIELD statement allows you to instantiate a named field on a table, enabling you to set the field's data type, set a default value, apply assertions to protect data consistency, and set permissions specifying what operations can be performed on the field.
Requirements
You must be authenticated as a root owner or editor, namespace owner or editor, or database owner or editor before you can use the DEFINE FIELD statement.
In versions of SurrealDB before 3.0.0-beta, COMPUTED fields were implemented using a data type called a future. Please see the page on futures in this case.
A COMPUTED field is one that is not stored but computed every time it is accessed. Such fields have a more limited set of clauses that can be used. Furthermore, a COMPUTED field cannot be defined on the id field of a record, nor any nested fields (i.e. a field metadata can be defined as computed, but not medatata.can_drive).
-- Define individual fields on an array DEFINEFIELDmetadata[0]ONpersonTYPEdatetime; DEFINEFIELDmetadata[1]ONpersonTYPEint;
Defining data types
The DEFINE FIELD statement allows you to set the data type of a field. For a full list of supported data types, see Data types.
From version v2.2.0, when defining nested fields, where both the parent and the nested fields have types defined, it is no longer possible to have mismatching types, to prevent any impossible type issues once the schema is defined.
'Cannot set field `fd.*` with type `number` as it mismatched with field `fd` with type `{ a: string, b: number }`'
Simple data types
-- Set a field to have the string data type DEFINEFIELDemailONTABLEuserTYPEstring;
-- Set a field to have the datetime data type DEFINEFIELDcreatedONTABLEuserTYPEdatetime;
-- Set a field to have the bool data type DEFINEFIELDlockedONTABLEuserTYPEbool;
-- Set a field to have the number data type DEFINEFIELDlogin_attemptsONTABLEuserTYPEnumber;
A | vertical bar can be used to allow a field to be one of a set of types. The following example shows a field that can be a UUID or an int, perhaps for user records that have varying data due to two diffent legacy ID types.
-- Set a field to have either the uuid or int type DEFINEFIELDuser_idONTABLEuserTYPEuuid|int;
Array type
You can also set a field to have the array data type. The array data type can be used to store a list of values. You can also set the data type of the array's contents, as well as the required number of items that it must hold.
-- Set a field to have the array data type DEFINEFIELDrolesONTABLEuserTYPEarray<string>;
-- Set a field to have the array data type, equivalent to `array<any>` DEFINEFIELDpostsONTABLEuserTYPEarray;
-- Set a field to have the array object data type DEFINEFIELDemailsONTABLEuserTYPEarray<object>;
-- Set a field that holds exactly 640 bytes DEFINEFIELDbytesONTABLEdataTYPEarray<int, 640>ASSERT$value.all(|$val|$valIN0..=255);
-- Field for a block in a game showing the possible distinct directions a character can move next. -- The array can contain no more than four directions DEFINEFIELDnext_pathsONTABLEblock TYPEarray<"north" | "east" | "south" | "west"> VALUE$value.distinct() ASSERT$value.len() <=4;
Making a field optional
You can make a field optional by wrapping the inner type in an option, which allows you to store NONE values in the field.
-- A user may enter a biography, but it is not required. -- By using the option type you also allow for NONE values. DEFINEFIELDbiographyONTABLEuserTYPEoption<string>;
The example below shows how to define a field user on a POST table. The field is of type record. This means that the field can store a record<user> or NONE.
Flexible types allow you to have SCHEMALESS functionality on a SCHEMAFULL table. This is necessary for working with nested object types that need to be able to accept fields that have not yet been defined.
Without FLEXIBLE, the metadata field will effectively be a SCHEMAFULL object with only a single defined field.
In versions of SurrealDB before 3.0, the result of the above statement was a record in which the metadata field was only able to populate the user_id field.
As of version 3.0, the statement now returns an error upon finding the first field that was not defined in the schema.
"Found field 'metadata.age', but no such field exists for table 'user'"
With FLEXIBLE, the output will be as expected as the schema now allows any sort of object to be a field on the user table — as long as values for name and metadata.user_id are present.
You can set a default value for a field using the DEFAULT clause. The default value will be used if no value is provided for the field.
-- A user is not locked by default. DEFINEFIELDlockedONTABLEuserTYPEbool -- Set a default value if empty DEFAULTfalse;
Using the DEFAULT and ALWAYS clause
In addition to the DEFAULT clause, you can use the DEFAULT ALWAYS clause to set a default value for a field. The ALWAYS keyword indicates that the DEFAULT clause is used not only on CREATE, but also on UPDATE if the value is empty (NONE).
DEFINETABLEproductSCHEMAFULL; -- Set a default value of 123.456 for the primary field DEFINEFIELDprimaryONproductTYPEnumberDEFAULTALWAYS123.456;
With the above definition, the primary field will be set to 123.456 when a new product is created without a value for the primary field or with a value of NONE, and when an existing product is updated if the value is specified the result will be the new value.
In the case of NULL or a mismatching type, an error will be returned.
-- This will return an error CREATEproduct:testSETprimary=NULL;
-- result "Couldn't coerce value for field `primary` of `product:test`: Expected `number` but found `NULL`"
On the other hand, if a valid number is provided during creation or update, that number will be used instead of the default value. In this case, 123.456.
-- This will set the value of the `primary` field to `123.456` CREATEproduct:test;
-- This will set the value of the `primary` field to `463.456` UPSERTproduct:testSETprimary=463.456;
-- This will set the value of the `primary` field to `123.456` UPSERTproduct:testSETprimary=NONE;
The VALUE clause differs from DEFAULT in that a default value is calculated if no other is indicated, otherwise accepting the value given in a query.
DEFINEFIELDupdatedONTABLEuserDEFAULTtime::now();
-- Set `updated` to the year 1900 CREATEuserSETupdated=d"1900-01-01"; -- Then set to the year 1910 UPDATEuserSETupdated=d"1910-01-01";
A VALUE clause, on the other hand, will ignore attempts to set the field to any other value.
DEFINEFIELDupdatedONTABLEuserVALUEtime::now();
-- Ignores 1900 date, sets `updated` to current time CREATEuserSETupdated=d"1900-01-01"; -- Ignores again, updates to current time UPDATEuserSETupdated=d"1900-01-01";
As the example above shows, a VALUE clause sets the value every time a record is modified (created or updated). However, the value will not be recalculated in a SELECT statement, which simply accesses the current set value.
DEFINEFIELDupdatedONTABLEuserVALUEtime::now();
CREATEuser:one; SELECT * FROMONLYuser:one; -- Sleep for one second SLEEP1s; -- `updated` is still the same SELECT * FROMONLYuser:one;
To create a field that is calculated each time it is accessed, a computed field can be used.
CREATEuser:one; SELECT * FROMONLYuser:one; -- Sleep for one second SLEEP1s; -- `accessed_at` is a different value now SELECT * FROMONLYuser:one;
Altering a passed value
You can alter a passed value using the VALUE clause. This is useful for altering the value of a field before it is stored in the database.
In the example below, the VALUE clause is used to ensure that the email address is always stored in lowercase characters by using the string::lowercase function.
-- Ensure that an email address is always stored in lowercase characters DEFINEFIELDemailONTABLEuserTYPEstring VALUEstring::lowercase($value);
Asserting rules on fields
You can take your field definitions even further by using asserts. Assert can be used to ensure that your data remains consistent. For example you can use asserts to ensure that a field is always a valid email address, or that a number is always positive.
-- Give the user table an email field. Store it in a string DEFINEFIELDemailONTABLEuserTYPEstring -- Check if the value is a properly formatted email address ASSERTstring::is_email($value);
As the ASSERT clause expects an expression that returns a boolean, an assertion with a custom message can be manually created by returning true in one case and using a THROW clause otherwise.
DEFINEFIELDnumONdataTYPEintASSERT{ IF$input % 2 =0{ RETURNtrue }ELSE{ THROW"Tried to make a "+<string>$this+" but `num` field requires an even number" } };
CREATEdata:oneSETnum=11;
Error output
'An error occurred: Tried to make a { id: data:one, num: 11 } but `num` field requires an even number'
Making a field READONLY
The READONLY clause can be used to prevent any updates to a field. This is useful for fields that are automatically updated by the system. To make a field READONLY, add the READONLY clause to the DEFINE FIELD statement. As seen in the example below, the created field is set to READONLY.
The IF NOT EXISTS clause can be used to define a field only if it does not already exist. You should use the IF NOT EXISTS clause when defining a field in SurrealDB if you want to ensure that the field is only created if it does not already exist. If the field already exists, the DEFINE FIELD statement will return an error.
It's particularly useful when you want to safely attempt to define a field without manually checking its existence first.
On the other hand, you should not use the IF NOT EXISTS clause when you want to ensure that the field definition is updated regardless of whether it already exists. In such cases, you might prefer using the OVERWRITE clause, which allows you to define a field and overwrite an existing one if it already exists, ensuring that the latest version of the definition is always in use
-- Create a field if it does not already exist DEFINEFIELDIFNOTEXISTSemailONTABLEuserTYPEstring;
Using OVERWRITE clause
The OVERWRITE clause can be used to define a field and overwrite an existing one if it already exists. You should use the OVERWRITE clause when you want to modify an existing field definition. If the field already exists, the DEFINE FIELD statement will overwrite the existing definition with the new one.
-- Overwrite the current field definition if it already exists DEFINEFIELDOVERWRITEexampleONTABLEuserTYPEstring;
Setting permissions on fields
By default, the permissions on a field will be set to FULL unless otherwise specified.
{ events: {}, fields: { info: 'DEFINE FIELD info ON some_table TYPE string PERMISSIONS FULL' }, indexes: {}, lives: {}, tables: {} }
You can set permissions on fields to control who can perform operations on them using the PERMISSIONS clause. The PERMISSIONS clause can be used to set permissions for SELECT, CREATE, and UPDATE operations. The DELETE operation only relates to records and, as such, is not available for fields.
-- Set permissions for the email field DEFINEFIELDemailONTABLEuser PERMISSIONS FORselectWHEREpublished=trueORuser=$auth.id FORupdateWHEREuser=$auth.idOR$auth.role="admin";
Array with allowed values
By using an Access Control List as an example we can show how we can restrict what values can be stored in an array. In this example we are using an array to store the permissions for a user on a resource. The permissions are restricted to a specific set of values.
-- An ACL can be applied to any kind of resource (record) DEFINEFIELDresourceONTABLEaclTYPErecord; -- We associate the acl with a user using record<user> DEFINEFIELDuserONTABLEaclTYPErecord<user>;
-- The permissions for the user+resource will be stored in an array. DEFINEFIELDpermissionsONTABLEaclTYPEarray -- The array must not be empty because at least one permission is required to make a valid ACL -- The items in the array must also be restricted to specific permissions ASSERT array::len($value) >0 AND$valueALLINSIDE["create", "read", "write", "delete"];
-- SEE IT IN ACTION -- 1: Add users CREATEuser:tobieSETfirstName='Tobie', lastName='Hitchcock', email='Tobie.Hitchcock@surrealdb.com'; CREATEuser:abcSETfirstName='A', lastName='B', email='c@d.com'; CREATEuser:efgSETfirstName='E', lastName='F', email='g@h.com';
-- 2: Create a resource CREATEdocument:SurrealDB_whitepaperSET name="some messaging queue";
-- Test Asserts using failure examples -- A: Create ACL without permissions field CREATEacl:invalidSET user=user:efg, permissions=[], # FAIL - permissions must not be empty resource=document:SurrealDB_whitepaper; -- B: Create acl with invalid permisson CREATEacl:also_invalidSET user=user:efg, permissions=["all"], # FAIL - This value is not allowed in the array resource=document:SurrealDB_whitepaper;
Using RegEX to validate a string
You can use the ASSERT clause to apply a regular expression to a field to ensure that it matches a specific pattern. In the example below, the ASSERT clause is used to ensure that the countrycode field is always a valid ISO-3166 country code.
-- Specify a field on the user table DEFINEFIELDcountrycodeONuserTYPEstring -- Ensure country code is ISO-3166 ASSERT$value=/[A-Z]{3}/ -- Set a default value if empty VALUE$valueOR$beforeOR'GBR' ;
Interacting with other fields of the same record
While a DEFINE TABLE statement represents a template for any subsequent records to be created, a DEFINE FIELD statement pertains to concrete field data of a record. As such, a DEFINE FIELD statement gives access to the record's other fields through their names, as well as the current field through the $value parameter.
As DEFINE FIELD statements are computed in alphabetical order, be sure to keep this in mind when using fields that rely on the values of others.
The following example is identical to the above except that full_name has been chosen for the previous field name. The full_name field will be calculated after first_name, but before last_name.
// Creates a `person` with `full_name` of "bob BOBSON", not "bob bobson" CREATEpersonSETfirst_name="Bob", last_name="BOBSON";
A good rule of thumb is to organize your DEFINE FIELD statements in alphabetical order so that the field definitions show up in the same order as that in which they are computed.
Defining a literal on a field
A field can also be defined as a literal type, by specifying one or more possible values and/or permitted types.
A field that is a record link (type record, option<record>, array<record<person>>, and so on) can be defined as a REFERENCE. If this clause is used, any linked to record will be able to define a field of its own of type references which will be aware of the incoming links.