Command Palette

Search for a command to run...

Entities & Relations

Your data model in Pylo is a set of entities with typed fields and relations between them. You define both in the admin panel under Settings > Entities, or through the API (see Schema changes). Pylo generates the API from this model: every entity gets a list query, a by-ID query, and create/update/delete mutations.

Fields

Every field has a data type. The API represents each one as a GraphQL scalar:

Data typeGraphQL type
textString
longtextString
richtextString
intInt
floatFloat
booleanBoolean
dateString
timeString
datetimeString
jsonString
enumgenerated enum
sequenceString

date, time, and datetime values are ISO 8601 strings. Pylo serializes json values as strings. An enum field generates a GraphQL enum from its configured values. A sequence field gets its value from a number range when the record is created, for example INV-2026-0001.

Every entity also carries system fields:

FieldTypeDescription
idIDUnique identifier (UUID)
integer_idIntAuto-incrementing ID
created_atStringCreation timestamp (ISO 8601 datetime)
updated_atStringLast update timestamp (ISO 8601 datetime)

A float field with a formula is computed from other fields and related records on every read and cannot be written. See Formulas.

Form types

A field's form_type tells clients what its value means. The stored value keeps its data type.

Form typeData typeStored valueIn the admin panel
CURRENCYint, floatThe amount, 1234.561.234,56 €, currency from format
PERCENTint, floatA fraction, 0.19 for 19 %19 %
DURATIONint, floatHours, 2.5 for 2 h 30 min2,5 h or 2:30 h, see durationStyle
EMAILtextThe addressA mailto link
PHONEtextThe numberA tel link
WEBSITEtextThe URLA link

The admin panel picks its input and cell by form type: a currency field shows the currency symbol, a percent field is edited as 19 and saved as 0.19, and email, phone and website values become clickable links. Your own frontend can read form_type the same way. DURATION is rejected on other data types.

The form type itself validates nothing. When you pick EMAIL, PHONE or WEBSITE in the admin panel, it also writes a regex rule into the field's validation_string, followed by required or nullable. Set the same rule when you create such a field through the API:

EMAIL regex:/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ PHONE regex:/^(?:(?:\+|00)[1-9][0-9]{0,2}|0)[0-9\s\/()-]{4,}$/ WEBSITE regex:/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,255}(\.[a-zA-Z0-9()]{1,6})?(:[0-9]{1,5})?([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)$/

Number formatting

format holds display options for number fields. Pylo stores it as given; the admin panel reads these keys:

KeyValuesDefault
decimals0 to 20as many as the value has
groupingtrue, falsetrue, thousands separators
currencyISO 4217 code, "USD""EUR", only read with CURRENCY
localeBCP 47 tag, "en-US""de-DE"
durationStyle"DECIMAL", "CLOCK""DECIMAL" (2,5 h); "CLOCK" (2:30 h)

A currency without the CURRENCY form type shows a plain number.

Relations

A relation connects two entities. In the relation settings you give each side a field name, and an entity only exposes the relation if its side has one. A relation between order and customer with both field names set adds customer to orders and orders to customers. Leave one side empty and that entity does not show the relation at all.

The cardinality determines the query shape:

  • hasOne (ManyToOne, OneToOne): { data: { ... } } | null
  • hasMany (OneToMany, ManyToMany): { data: [...], pagination }

Relation writes are fields on the entity's update input. In the SDKs, _set, _connect, and _disconnect go through the same upsert call as any field change, so you can combine them with field updates in one mutation. Set a hasOne relation with _set:

set-has-one.ts
1 2 3 4 5 6 import { pylo } from './lib/pylo'; await pylo.order.upsert({ id: 'order-uuid', customer_set: { id: 'customer-uuid' }, });

For hasMany relations, _connect adds records, _disconnect removes them, and _set replaces the whole list:

connect-has-many.ts
1 2 3 4 5 6 7 import { pylo } from './lib/pylo'; await pylo.customer.upsert({ id: 'customer-uuid', orders_connect: [{ id: 'order-uuid' }], orders_disconnect: [{ id: 'old-order-uuid' }], });

Relations to system entities (for example PyloMedia) use _add / _remove instead of _connect / _disconnect. Besides ids, _search_value targets records by field, including inside nested relation upserts. See Mutations.

Relations point at your own entities or at system entities. A relation to PyloMedia attaches file uploads to your records.

System entities

Pylo ships built-in entities that follow the same API patterns as your own:

EntityPurpose
PyloUserUser accounts and authentication
PyloMediaFiles and media uploads
PyloApiKeyAPI tokens for programmatic access
PyloAppApplication configuration