7.1 Four-Tier Architecture
ADL uses a closed type system organized into four tiers of increasing specialization, plus a content type category for rich UI fields. Every field in a resource MUST declare a type from this system. No custom types can be defined — the type system is fixed by this specification.
The tiers reflect the level of semantic meaning the platform attaches to the stored value:
- Tier 0 — Core: Fundamental data types with direct database mappings. Every application uses these.
- Tier 1 — Extended: Specialized storage types with format-specific behavior. Used when a core type lacks the needed semantics.
- Tier 2 — Specialized: Types with built-in validation or security behavior. Used for specific data concerns.
- Tier 3 — Domain: Types with business-domain semantics and format standards. Used for internationalization, finance, and visual identity.
- Content Types: Types for rich, structured content that requires specialized editing and rendering. Used in CMS, publishing, and UI-heavy domains.
7.2 Tier 0 — Core Types
Ten fundamental types that map directly to database primitives.
| Type | Description | JSON Representation | Validation |
|---|---|---|---|
string | Short text | "hello" | maxLength (default 255), minLength, pattern, trim, lowercase, uppercase |
text | Long text | "long content..." | maxLength (unlimited by default) |
integer | Whole number | 42 | min, max |
decimal | Precise number | "29.99" | precision, scale, min, max. JSON representation is a string to preserve precision. |
boolean | True/false | true | None. Accepts true, false, 1, 0. |
date | Calendar date | "2026-05-17" | ISO 8601 date format. min, max. |
datetime | Date and time | "2026-05-17T14:30:00Z" | ISO 8601 timestamp with timezone. min, max. |
uuid | Universally unique identifier | "550e8400-e29b-41d4-a716-446655440000" | UUID v4 format. Auto-generated on create unless allowUserProvided: true. |
email | Email address | "user@example.com" | RFC 5322 format validation. |
url | Web address | "https://example.com" | URL format validation with scheme. maxLength. |
Usage Notes
string vs text: Use string for short, constrained values (names, titles, codes) where a maximum length is meaningful. Use text for long-form content (descriptions, bodies, notes) where length is unconstrained. Both map to TEXT in SQLite; in PostgreSQL, string maps to VARCHAR(n) and text maps to TEXT.
decimal precision: The decimal type stores values as strings in the JSON API and in SQLite to avoid floating-point precision loss. In PostgreSQL, it uses NUMERIC(p,s). Always specify precision and scale for financial values:
price:
type: decimal
precision: 10
scale: 2
min: 0
uuid auto-generation: By default, the id field is auto-generated as a UUID v4 on create. The client-submitted value is ignored. To allow client-provided IDs (for imports, migrations, or deterministic seeding), set allowUserProvided: true:
id:
type: uuid
primaryKey: true
allowUserProvided: true
7.3 Tier 1 — Extended Types
Six types with format-specific behavior beyond core storage.
| Type | Description | JSON Representation | Behavior |
|---|---|---|---|
float | Floating-point number | 3.14159 | Native float. Use decimal for financial precision. |
timestamp | Unix epoch | 1716000000 | Stored as integer. Converted to/from ISO 8601 in API. |
slug | URL-safe identifier | "my-blog-post" | Auto-generated from a source field. Always unique. |
json | Arbitrary JSON | {"key": "value"} | Stored as-is. No schema validation (use component for typed JSON). |
markdown | Markdown text | "# Title\n\nContent" | Stored as raw markdown string. Rendering is a UI concern. |
autoincrement | Sequential integer | 42 | Auto-assigned, monotonically increasing. Read-only. |
Slug Auto-Generation
A slug field requires a from attribute specifying the source field:
slug:
type: slug
unique: true
from: title
The slug is generated by converting the source field’s value to lowercase, replacing spaces and non-alphanumeric characters with hyphens, and removing consecutive hyphens. The slug is regenerated on create and whenever the source field changes on update.
If the generated slug conflicts with an existing record’s slug (violating the unique constraint), the server appends a numeric suffix (-1, -2, etc.) until the slug is unique.
7.4 Tier 2 — Specialized Types
Five types with built-in validation or security behavior.
| Type | Description | JSON Representation | Behavior |
|---|---|---|---|
phone | Phone number | "+14155551234" | Format validation. Use pattern for E.164: "^\\+[1-9]\\d{1,14}$". |
time | Time of day | "14:30:00" | ISO 8601 time format. |
password | Hashed credential | Never returned in responses | Automatically hashed with Argon2id on write. Implies writeOnly: true. |
token | Opaque token | "abc123def456" | Auto-generated random string. Read-only after creation. |
array | Typed list | ["a", "b", "c"] | Ordered array with item type validation. |
Array Configuration
The array type supports typed items and length constraints:
tags:
type: array
items:
type: string
minItems: 1
maxItems: 10
uniqueItems: true
| Constraint | Type | Description |
|---|---|---|
items.type | string | Type of each array element |
minItems | integer | Minimum number of elements |
maxItems | integer | Maximum number of elements |
uniqueItems | boolean | If true, reject duplicate values |
Arrays are stored as JSON strings in the database.
7.5 Tier 3 — Domain Types
Nine types with business-domain semantics tied to international standards.
| Type | Description | JSON Representation | Standard | Validation |
|---|---|---|---|---|
money | Monetary amount | "1234.5678" | — | Implicit precision 19, scale 4. String in JSON. |
percentage | Percentage value | 75.5 | — | Range validation. Stored as decimal. |
currency | Currency code | "USD" | ISO 4217 | 3-letter uppercase code. |
country | Country code | "US" | ISO 3166-1 alpha-2 | 2-letter uppercase code. |
language | Language code | "en" | ISO 639-1 | 2-letter lowercase code. |
timezone | Timezone identifier | "America/New_York" | IANA | Validated against the IANA timezone database. |
color | Color value | "#FF5733" | — | Hex color format (#RRGGBB). |
bytes | Binary data | Base64 string | — | maxSize in bytes. |
set | Unique value collection | ["admin", "editor"] | — | Like array with uniqueItems: true enforced. |
Money Type
The money type is designed for financial values where floating-point precision loss is unacceptable:
amount:
type: money
min: 0
required: true
Money values are stored as strings in the API (JSON) and as TEXT in SQLite / NUMERIC(19,4) in PostgreSQL. The runtime converts between string and numeric representations for arithmetic operations (computed fields, aggregation).
For currency-aware money fields, pair with a currency field:
price:
type: money
min: 0
currency:
type: currency
default: USD
7.6 Content Types
Seven types for rich, structured content that requires specialized editors and renderers. These types store complex data as JSON in the database.
media — File References
featuredImage:
type: media
accept: [image/jpeg, image/png, image/webp]
maxSize: 5mb
minWidth: 800
minHeight: 600
aspectRatio: "16:9"
| Constraint | Type | Description |
|---|---|---|
accept | string[] | Allowed MIME types. Wildcards supported: image/*. |
maxSize | string | Maximum file size with unit: 5mb, 500kb, 1gb. |
minWidth | integer | Minimum image width in pixels (images only). |
maxWidth | integer | Maximum image width in pixels. |
minHeight | integer | Minimum image height in pixels. |
maxHeight | integer | Maximum image height in pixels. |
aspectRatio | string | Required aspect ratio: "16:9", "1:1", "4:3". |
Media files are uploaded via multipart/form-data and stored in the media library. The field value is a JSON object containing the file metadata and reference:
{
"id": "550e8400-...",
"filename": "hero.jpg",
"mimeType": "image/jpeg",
"size": 245760,
"width": 1920,
"height": 1080,
"url": "/media/550e8400-.../hero.jpg"
}
The media library tracks references from resources to media items, enabling orphan detection (§18.2).
svg — Sanitized Vector Graphics
logo:
type: svg
maxSize: 50kb
SVG content is sanitized on write to remove potentially dangerous elements and attributes (scripts, event handlers, external references). The sanitizer:
- Strips
<script>elements - Strips event handler attributes (
onclick,onload, etc.) - Strips external references (
xlink:hrefto external URLs) - Strips comments and metadata elements
- Preserves structural SVG elements (
<svg>,<path>,<rect>,<circle>,<g>,<text>, etc.)
blocks — Block Editor Content
content:
type: blocks
allowedBlocks: [paragraph, heading, image, code, list, callout]
maxBlocks: 100
| Constraint | Type | Description |
|---|---|---|
allowedBlocks | string[] | Block types permitted in this field. If omitted, all block types are allowed. |
maxBlocks | integer | Maximum number of blocks. |
Blocks content is stored as a JSON array of block objects:
[
{
"type": "paragraph",
"content": "Introduction text."
},
{
"type": "heading",
"content": "Section Title",
"props": { "level": 2 }
},
{
"type": "code",
"content": "const x = 42;",
"props": { "language": "javascript" }
},
{
"type": "callout",
"content": "Important note.",
"props": { "type": "warning", "title": "Caution" }
}
]
Custom block types are defined in the blocks: section of the domain (§18.3).
icon — Icon Set Reference
statusIcon:
type: icon
iconSets: [lucide, custom]
Stores an icon identifier in the format set:name (e.g., "lucide:check-circle"). The iconSets constraint limits which icon sets are valid. Icon sets are configured in the icons: section of the domain.
font — Font Reference
headingFont:
type: font
sources: [google, system]
Stores a font family name (e.g., "Inter"). The sources constraint limits where fonts can be loaded from. Font sources are configured in the fonts: section of the domain.
component — Nested Object
address:
type: component
object:
street: { type: string, required: true }
city: { type: string, required: true }
state: { type: string, maxLength: 2 }
zip: { type: string, pattern: "^[0-9]{5}$" }
country: { type: country, default: "US" }
collapsed: false
A component is a single nested object with typed fields. It is stored as a single JSON column. Components do not have their own id, are not independently queryable, and cannot participate in relationships. Use relationships for data that needs to be shared or queried independently.
The object definition MAY use $ref to reference a shared object:
seo:
type: component
object: { $ref: '#/objects/SEO' }
collapsed: true
| Constraint | Type | Description |
|---|---|---|
object | object | Field definitions for the nested object (inline or $ref). |
collapsed | boolean | UI hint: whether to render collapsed by default. Default false. |
required | boolean | Whether the component itself is required. Individual fields within the component have their own required settings. |
repeater — Ordered Array of Objects
lineItems:
type: repeater
sortable: true
minItems: 1
maxItems: 50
object:
productId: { type: uuid, required: true }
quantity: { type: integer, min: 1, required: true }
unitPrice: { type: decimal, required: true }
notes: { type: text }
A repeater is an ordered array of objects, each conforming to the same field definitions. It is stored as a single JSON array column.
| Constraint | Type | Description |
|---|---|---|
object | object | Field definitions for each array element (inline or $ref). |
sortable | boolean | UI hint: enable drag-and-drop reordering. Default false. |
minItems | integer | Minimum number of entries. |
maxItems | integer | Maximum number of entries. |
7.7 Field Constraints
Constraints restrict the values a field can hold. They are declared alongside the field type and are enforced at the API boundary on every create and update operation.
Universal Constraints
Available on all field types:
| Constraint | Type | Description |
|---|---|---|
required | boolean | If true, the field MUST be present and non-null on create. Default false. |
unique | boolean | If true, no two records may have the same value. Enforced by a database index. Default false. |
default | any | Default value applied when the field is omitted on create. |
immutable | boolean | If true, the field cannot be changed after creation. Update attempts with a different value are rejected with 422. Default false. |
nullable | boolean | If true, the field explicitly accepts null. Default true for non-required fields. |
description | string | Human-readable description. Included in OpenAPI spec. |
String Constraints
| Constraint | Type | Description |
|---|---|---|
maxLength | integer | Maximum character count. |
minLength | integer | Minimum character count. |
pattern | string | Regular expression the value must match. |
trim | boolean | If true, strip leading/trailing whitespace on write. |
lowercase | boolean | If true, convert to lowercase on write. |
uppercase | boolean | If true, convert to uppercase on write. |
enum | string or string[] | Restrict to a fixed set of values (inline list or enum name). |
Numeric Constraints
| Constraint | Type | Description |
|---|---|---|
min | number | Minimum value (inclusive). |
max | number | Maximum value (inclusive). |
precision | integer | Total number of digits (for decimal). |
scale | integer | Number of digits after the decimal point (for decimal). |
Date/Time Constraints
| Constraint | Type | Description |
|---|---|---|
min | string | Earliest allowed value (ISO 8601). |
max | string | Latest allowed value (ISO 8601). |
Enum Constraint
The enum constraint restricts a string field to a fixed set of values. It can be declared inline or by reference:
# Inline enum values
priority:
type: string
enum: [low, medium, high, critical]
default: medium
# Reference to a domain-level enum
status:
type: string
enum: PostStatus
# Or via $ref
status:
$ref: '#/enums/PostStatus'
default: draft
All three forms produce identical runtime behavior. The $ref form is preferred when the enum carries rich metadata (§9).
Field Security
| Constraint | Type | Description |
|---|---|---|
writeOnly | boolean | If true, the field is never included in read responses. Used for secrets. Implied by password type. |
internal | boolean | If true, the field is never exposed via the API (403 on write attempt). Used for system-internal fields. |
hidden | boolean | If true, the field is omitted from responses by default but can be included with ?fields=. |
requestable | boolean | If true and hidden: true, the field can be requested via ?fields=. Default true. |
7.8 Auto-Generated Fields
Several field types have auto-generation behavior managed by the server:
autoNow
The autoNow property automatically sets a datetime field to the current timestamp:
createdAt:
type: datetime
autoNow: create # Set once on creation, never updated
updatedAt:
type: datetime
autoNow: always # Set on every create and update
| Value | Behavior |
|---|---|
create | Set to now() on create. Never modified on update. |
update | Set to now() on every update. Not set on create. |
always | Set to now() on create and on every update. |
Fields with autoNow are auto-managed — client-submitted values for these fields are silently ignored.
Slug Auto-Generation
slug:
type: slug
unique: true
from: title
The from property specifies the source field. The slug is regenerated whenever the source field changes. See §7.3 for the generation algorithm.
UUID Auto-Generation
Fields with type: uuid and primaryKey: true are auto-generated as UUID v4 on create. Client-submitted values are ignored unless allowUserProvided: true:
id:
type: uuid
primaryKey: true
allowUserProvided: true # Accept client-provided UUIDs
Autoincrement
orderNumber:
type: autoincrement
Autoincrement fields are assigned the next sequential integer on create. They are read-only — client-submitted values are ignored. Autoincrement values are never reused, even after deletion.
7.9 Complete Type Reference
| Category | Type | JSON Type | SQLite | PostgreSQL | Constraints |
|---|---|---|---|---|---|
| Tier 0 | string | string | TEXT | VARCHAR(n) | maxLength, minLength, pattern, trim, lowercase, uppercase, enum |
text | string | TEXT | TEXT | maxLength | |
integer | number | INTEGER | INTEGER | min, max | |
decimal | string | TEXT | NUMERIC(p,s) | precision, scale, min, max | |
boolean | boolean | INTEGER | BOOLEAN | — | |
date | string | TEXT | DATE | min, max | |
datetime | string | TEXT | TIMESTAMPTZ | min, max, autoNow | |
uuid | string | TEXT | UUID | primaryKey, allowUserProvided | |
email | string | TEXT | VARCHAR(255) | RFC 5322 validation | |
url | string | TEXT | TEXT | URL format validation, maxLength | |
| Tier 1 | float | number | REAL | DOUBLE PRECISION | min, max |
timestamp | number | INTEGER | BIGINT | min, max | |
slug | string | TEXT | VARCHAR(255) | from, unique | |
json | object | TEXT | JSONB | — | |
markdown | string | TEXT | TEXT | maxLength | |
autoincrement | number | INTEGER | SERIAL | read-only | |
| Tier 2 | phone | string | TEXT | VARCHAR(20) | pattern |
time | string | TEXT | TIME | — | |
password | string | TEXT | TEXT | writeOnly, Argon2id hash | |
token | string | TEXT | TEXT | auto-generated, read-only | |
array | array | TEXT | JSONB | items, minItems, maxItems, uniqueItems | |
| Tier 3 | money | string | TEXT | NUMERIC(19,4) | min, max |
percentage | number | REAL | NUMERIC(7,4) | min, max | |
currency | string | TEXT | VARCHAR(3) | ISO 4217 | |
country | string | TEXT | VARCHAR(2) | ISO 3166-1 alpha-2 | |
language | string | TEXT | VARCHAR(2) | ISO 639-1 | |
timezone | string | TEXT | TEXT | IANA validation | |
color | string | TEXT | VARCHAR(7) | Hex format | |
bytes | string | BLOB | BYTEA | maxSize | |
set | array | TEXT | JSONB | items, uniqueItems enforced | |
| Content | media | object | TEXT | JSONB | accept, maxSize, dimensions, aspectRatio |
svg | string | TEXT | TEXT | maxSize, auto-sanitized | |
blocks | array | TEXT | JSONB | allowedBlocks, maxBlocks | |
icon | string | TEXT | TEXT | iconSets | |
font | string | TEXT | TEXT | sources | |
component | object | TEXT | JSONB | object, collapsed | |
repeater | array | TEXT | JSONB | object, sortable, minItems, maxItems |
Total: 37 types across 5 categories.
Spatial type (v0.3.1): the
geometry/geojsonfield type — GeoJSON values with optionalgeometryType/srid, PostGIS-backed storage, and spatial query operators — is specified in Chapter 41 — GIS & Spatial Data.
End of Chapter 7.
Next: Chapter 8 — Relationships