Properties — ASL
Properties
Section titled “Properties”Properties map to columns.
type User { required email: str; # NOT NULL column name: str; # nullable column required property age: int32; # "property" keyword is optional}Required
Section titled “Required”required maps to NOT NULL.
Defaults
Section titled “Defaults”active: bool { default := true };name: str { default := 'anonymous'; };score: int32 { default := 0; };
# Functionsid: uuid { default := gen_uuid(); };created_at: datetime { default := datetime_current(); };A default runs once, on INSERT. To re-assign a field on later updates, see
Rewrites.
Declaring a default also matters when you add a required field to a table that already has rows:
with one, Axel backfills existing rows itself; without one, the migration leaves a seam you must
fill in. See axel diff.
Multi (array) properties
Section titled “Multi (array) properties”multi on a property declares a PostgreSQL array column — one column, not a junction table.
(multi on a link means something else entirely; see Links.)
type User { multi roles: UserType; # TEXT[] with a containment check multi images: str; # TEXT[] multi scores: float64; # DOUBLE PRECISION[]}"roles" TEXT[] CONSTRAINT "chk_user_roles_enum" CHECK ("roles" <@ ARRAY['Admin', 'Runner', 'Vendor']::TEXT[]),"images" TEXT[],"scores" DOUBLE PRECISION[]An enum array is stored as TEXT[] guarded by a containment check rather than as a Postgres enum
array.
Two consequences worth knowing:
-
Membership compiles to
= ANY(...), notIN— PostgresINtakes a parenthesised list and rejects an array operand.select User { id } filter UserType.Admin in .roles; # → 'Admin' = ANY(u.roles) -
Generated clients type it as an array, with the nullability outside the array:
roles?: UserType[] | nullin TypeScript,[]UserTypein Go — in the models and in query result rows alike.