Skip to content

Property Types

xNet provides 15 property types organized into five categories.

A string property with optional length and pattern constraints.

import { text } from '@xnetjs/data'
title: text({ required: true, maxLength: 500 })
draft: false
description: text({ placeholder: 'Enter a description...' })
slug: text({ pattern: /^[a-z0-9-]+$/ })
OptionTypeDefaultDescription
requiredbooleanfalseReject null/undefined values.
minLengthnumberMinimum string length.
maxLengthnumberMaximum string length.
patternRegExpRegex pattern to match.
placeholderstringUI hint (not enforced).

Infers to: string Coercion: String(value). Returns null for null/undefined.


A numeric property with optional range and integer constraints.

import { number } from '@xnetjs/data'
priority: number({ min: 1, max: 5 })
score: number({ min: 0, integer: true })
OptionTypeDefaultDescription
requiredbooleanfalse
minnumberMinimum value.
maxnumberMaximum value.
integerbooleanfalseRound to nearest integer.

Infers to: number Coercion: Number(value). If integer, applies Math.round(). Returns null for NaN.


A boolean property with an optional default.

import { checkbox } from '@xnetjs/data'
completed: checkbox({ default: false })
isPublic: checkbox({ default: true })
OptionTypeDefaultDescription
requiredbooleanfalse
defaultbooleanDefault value for null/undefined.

Infers to: boolean Coercion: 'true'/1 to true, 'false'/0 to false, else Boolean(value).


A timestamp stored as Unix milliseconds.

import { date } from '@xnetjs/data'
dueDate: date()
scheduledAt: date({ includeTime: true })
OptionTypeDefaultDescription
requiredbooleanfalse
includeTimebooleanfalseUI hint for showing time picker.

Infers to: number (Unix timestamp in ms) Coercion: Accepts number, Date objects (.getTime()), ISO strings (Date.parse()). Returns null if unparseable. Validation: Must be between 0 and year 3000 (32503680000000).


A range with a start and optional end date.

import { dateRange } from '@xnetjs/data'
timeline: dateRange()
sprint: dateRange({ includeTime: true })
OptionTypeDefaultDescription
requiredbooleanfalse
includeTimebooleanfalseUI hint.

Infers to: { start: string; end?: string } (ISO 8601 strings) Coercion: Parses start and end through new Date() and converts to ISO strings.


A single choice from a predefined list.

import { select } from '@xnetjs/data'
status: select({
options: [
{ id: 'todo', name: 'To Do', color: '#6366f1' },
{ id: 'doing', name: 'Doing', color: '#f59e0b' },
{ id: 'done', name: 'Done', color: '#10b981' }
] as const,
default: 'todo'
})
OptionTypeDefaultDescription
optionsSelectOption[]Available choices. Each has id, name, optional color.
requiredbooleanfalse
defaultstringDefault option ID.

Infers to: Union of option IDs (e.g., 'todo' | 'doing' | 'done'). Requires as const on the options array. Coercion: Returns default for null/undefined. Tries case-insensitive name matching as fallback.


Multiple choices from a predefined list.

import { multiSelect } from '@xnetjs/data'
tags: multiSelect({
options: [
{ id: 'bug', name: 'Bug', color: '#ef4444' },
{ id: 'feature', name: 'Feature', color: '#3b82f6' },
{ id: 'docs', name: 'Docs', color: '#8b5cf6' }
] as const
})
OptionTypeDefaultDescription
optionsSelectOption[]Available choices.
requiredbooleanfalse
defaultstring[]Default option IDs.

Infers to: Union of option IDs as array (e.g., ('bug' | 'feature' | 'docs')[]) Coercion: Returns default ?? [] for null/undefined. Coerces single string to [value]. Filters invalid entries.


A reference to another node by ID.

import { relation } from '@xnetjs/data'
project: relation({ target: 'xnet://my-app/Project' })
children: relation({ multiple: true })
OptionTypeDefaultDescription
targetSchemaIRIConstrain to a specific schema (optional).
requiredbooleanfalse
multiplebooleanfalseAllow array of references.

Infers to: string (single) or string[] (multiple) Coercion: Returns null/[] for null/undefined. Filters empty strings.

See Relations for patterns and temp ID resolution in transactions.


A reference to a user by DID.

import { person } from '@xnetjs/data'
assignee: person()
collaborators: person({ multiple: true })
OptionTypeDefaultDescription
requiredbooleanfalse
multiplebooleanfalseAllow array of DIDs.

Infers to: string (DID) or string[] (multiple DIDs) Validation: Must match DID pattern: /^did:[a-z]+:[a-zA-Z0-9._:-]+$/ Coercion: Returns null/[] for null/undefined. Filters invalid DIDs.


A validated URL string.

import { url } from '@xnetjs/data'
website: url()
repository: url({ placeholder: 'https://github.com/...' })
OptionTypeDefaultDescription
requiredbooleanfalse
placeholderstringUI hint.

Infers to: string Validation: Must match /^https?:\/\/.+/i Coercion: Trims whitespace. Auto-prepends https:// if no protocol present.


A validated email string.

import { email } from '@xnetjs/data'
contactEmail: email()
OptionTypeDefaultDescription
requiredbooleanfalse
placeholderstringUI hint.

Infers to: string Validation: Must match /^[^\s@]+@[^\s@]+\.[^\s@]+$/ Coercion: Trims and lowercases.


A validated phone number string.

import { phone } from '@xnetjs/data'
contactPhone: phone()
OptionTypeDefaultDescription
requiredbooleanfalse
placeholderstringUI hint.

Infers to: string Validation: Must match /^[+]?[(]?[0-9]{1,4}[)]?[-\s./0-9]*$/ with at least 7 digits. Coercion: Trims, collapses whitespace. Preserves formatting.


A content-addressed file reference.

import { file } from '@xnetjs/data'
avatar: file({ accept: ['image/*'], maxSize: 5 * 1024 * 1024 })
attachments: file({ multiple: true })
OptionTypeDefaultDescription
requiredbooleanfalse
multiplebooleanfalseAllow array of files.
acceptstring[]Allowed MIME types (e.g., ['image/*', 'application/pdf']).
maxSizenumberMaximum file size in bytes.

Infers to: FileRef or FileRef[]

interface FileRef {
cid: string // Content-addressed ID
name: string // Original filename
mimeType: string // MIME type
size: number // File size in bytes
width?: number // Intrinsic width, images and video
height?: number // Intrinsic height, images and video
thumbCid?: string // Small preview, stored as its own blob
}

In a database, a file field renders each attachment as a chip — a thumbnail for images, an icon otherwise — and clicking one opens a full-screen viewer with paging and download. Set multiple to keep several files in one cell.

Only the FileRef travels through the change log; the bytes are stored content-addressed and moved separately, uploaded to the hub in the background after attaching and fetched by peers on first view. An attachment whose bytes haven’t reached this device yet reads “on another device” rather than appearing broken.


These are read-only fields populated automatically by the system.

Timestamp of when the node was created.

import { created } from '@xnetjs/data'
createdAt: created()

Infers to: number (Unix timestamp in ms) Behavior: Set to Date.now() at creation time. Read-only afterward.


Timestamp of the last modification.

import { updated } from '@xnetjs/data'
updatedAt: updated()

Infers to: number (Unix timestamp in ms) Behavior: Set to Date.now() on every update. Read-only.


DID of the user who created the node.

import { createdBy } from '@xnetjs/data'
author: createdBy()

Infers to: string (DID) Behavior: Set to the author’s DID at creation time. Read-only afterward.


TypeInfers toValidatesNotable
textstringlength, pattern
numbernumbermin, max, integer
checkboxbooleanhas default
datenumber0 to year 3000stored as ms timestamp
dateRange{ start, end? }valid datesISO 8601 strings
selectliteral unionoption IDsuse as const
multiSelectliteral union[]option IDsuse as const
relationstring | string[]non-emptysupports temp IDs
personstring | string[]DID patterndid:*:*
urlstringhttp(s)auto-prepends https
emailstringemail patternlowercased
phonestringphone pattern7+ digits
fileFileRef | FileRef[]cid, name, mimeType, sizecontent-addressed
creatednumberauto, read-only
updatednumberauto, read-only
createdBystring (DID)auto, read-only