Property Types
xNet provides 15 property types organized into five categories.
Basic types
Section titled “Basic types”A string property with optional length and pattern constraints.
import { text } from '@xnetjs/data'
title: text({ required: true, maxLength: 500 })draft: falsedescription: text({ placeholder: 'Enter a description...' })slug: text({ pattern: /^[a-z0-9-]+$/ })| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | Reject null/undefined values. |
minLength | number | — | Minimum string length. |
maxLength | number | — | Maximum string length. |
pattern | RegExp | — | Regex pattern to match. |
placeholder | string | — | UI hint (not enforced). |
Infers to: string
Coercion: String(value). Returns null for null/undefined.
number
Section titled “number”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 })| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | |
min | number | — | Minimum value. |
max | number | — | Maximum value. |
integer | boolean | false | Round to nearest integer. |
Infers to: number
Coercion: Number(value). If integer, applies Math.round(). Returns null for NaN.
checkbox
Section titled “checkbox”A boolean property with an optional default.
import { checkbox } from '@xnetjs/data'
completed: checkbox({ default: false })isPublic: checkbox({ default: true })| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | |
default | boolean | — | Default value for null/undefined. |
Infers to: boolean
Coercion: 'true'/1 to true, 'false'/0 to false, else Boolean(value).
Temporal types
Section titled “Temporal types”A timestamp stored as Unix milliseconds.
import { date } from '@xnetjs/data'
dueDate: date()scheduledAt: date({ includeTime: true })| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | |
includeTime | boolean | false | UI 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).
dateRange
Section titled “dateRange”A range with a start and optional end date.
import { dateRange } from '@xnetjs/data'
timeline: dateRange()sprint: dateRange({ includeTime: true })| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | |
includeTime | boolean | false | UI hint. |
Infers to: { start: string; end?: string } (ISO 8601 strings)
Coercion: Parses start and end through new Date() and converts to ISO strings.
Selection types
Section titled “Selection types”select
Section titled “select”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'})| Option | Type | Default | Description |
|---|---|---|---|
options | SelectOption[] | — | Available choices. Each has id, name, optional color. |
required | boolean | false | |
default | string | — | Default 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.
multiSelect
Section titled “multiSelect”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})| Option | Type | Default | Description |
|---|---|---|---|
options | SelectOption[] | — | Available choices. |
required | boolean | false | |
default | string[] | — | 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.
Reference types
Section titled “Reference types”relation
Section titled “relation”A reference to another node by ID.
import { relation } from '@xnetjs/data'
project: relation({ target: 'xnet://my-app/Project' })children: relation({ multiple: true })| Option | Type | Default | Description |
|---|---|---|---|
target | SchemaIRI | — | Constrain to a specific schema (optional). |
required | boolean | false | |
multiple | boolean | false | Allow 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.
person
Section titled “person”A reference to a user by DID.
import { person } from '@xnetjs/data'
assignee: person()collaborators: person({ multiple: true })| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | |
multiple | boolean | false | Allow 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.
Rich types
Section titled “Rich types”A validated URL string.
import { url } from '@xnetjs/data'
website: url()repository: url({ placeholder: 'https://github.com/...' })| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | |
placeholder | string | — | UI 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()| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | |
placeholder | string | — | UI 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()| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | |
placeholder | string | — | UI 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 })| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | |
multiple | boolean | false | Allow array of files. |
accept | string[] | — | Allowed MIME types (e.g., ['image/*', 'application/pdf']). |
maxSize | number | — | Maximum 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.
Auto-populated types
Section titled “Auto-populated types”These are read-only fields populated automatically by the system.
created
Section titled “created”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.
updated
Section titled “updated”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.
createdBy
Section titled “createdBy”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.
Summary table
Section titled “Summary table”| Type | Infers to | Validates | Notable |
|---|---|---|---|
text | string | length, pattern | |
number | number | min, max, integer | |
checkbox | boolean | has default | |
date | number | 0 to year 3000 | stored as ms timestamp |
dateRange | { start, end? } | valid dates | ISO 8601 strings |
select | literal union | option IDs | use as const |
multiSelect | literal union[] | option IDs | use as const |
relation | string | string[] | non-empty | supports temp IDs |
person | string | string[] | DID pattern | did:*:* |
url | string | http(s) | auto-prepends https |
email | string | email pattern | lowercased |
phone | string | phone pattern | 7+ digits |
file | FileRef | FileRef[] | cid, name, mimeType, size | content-addressed |
created | number | auto, read-only | |
updated | number | auto, read-only | |
createdBy | string (DID) | auto, read-only |
Related
Section titled “Related”defineSchema— using these types in a schema- Relations — patterns for linking nodes
- Type Inference — how types flow to hooks