> How Pylo turns your data model into an API, entities, relations, and system entities

# 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](https://app.pyloapp.com/p/settings/entities). 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 type  | GraphQL type   |
| ---------- | -------------- |
| `text`     | `String`       |
| `longtext` | `String`       |
| `richtext` | `String`       |
| `int`      | `Int`          |
| `float`    | `Float`        |
| `boolean`  | `Boolean`      |
| `date`     | `String`       |
| `time`     | `String`       |
| `datetime` | `String`       |
| `json`     | `String`       |
| `enum`     | generated enum |

`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.

Every entity also carries system fields:

| Field        | Type     | Description                               |
| ------------ | -------- | ----------------------------------------- |
| `id`         | `ID`     | Unique identifier (UUID)                  |
| `integer_id` | `Int`    | Auto-incrementing ID                      |
| `created_at` | `String` | Creation timestamp (ISO 8601 datetime)    |
| `updated_at` | `String` | Last update timestamp (ISO 8601 datetime) |

## 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`:
**GraphQL**

```graphql filename="set-has-one.graphql"
mutation {
  updateOrder(input: {
    id: "order-uuid"
    customer_set: { id: "customer-uuid" }
  }) {
    data { id }
  }
}
```
**Node**

```typescript filename="set-has-one.ts"
import { pylo } from './lib/pylo';

await pylo.order.upsert({
  id: 'order-uuid',
  customer_set: { id: 'customer-uuid' },
});
```
**Next.js Server**

```typescript filename="set-has-one.ts"
import { pylo } from '@/lib/pylo';

await pylo.order.upsert({
  id: 'order-uuid',
  customer_set: { id: 'customer-uuid' },
});
```
**Next.js Client**

```typescript filename="set-has-one.tsx"
import { pylo } from '@/lib/pylo-hooks';

const { mutateAsync } = pylo.usePyloUpsert('order');

await mutateAsync({
  id: 'order-uuid',
  customer_set: { id: 'customer-uuid' },
});
```

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

```graphql filename="connect-has-many.graphql"
mutation {
  updateCustomer(input: {
    id: "customer-uuid"
    orders_connect: [{ id: "order-uuid" }]
    orders_disconnect: [{ id: "old-order-uuid" }]
  }) {
    data { id }
  }
}
```
**Node**

```typescript filename="connect-has-many.ts"
import { pylo } from './lib/pylo';

await pylo.customer.upsert({
  id: 'customer-uuid',
  orders_connect: [{ id: 'order-uuid' }],
  orders_disconnect: [{ id: 'old-order-uuid' }],
});
```
**Next.js Server**

```typescript filename="connect-has-many.ts"
import { pylo } from '@/lib/pylo';

await pylo.customer.upsert({
  id: 'customer-uuid',
  orders_connect: [{ id: 'order-uuid' }],
  orders_disconnect: [{ id: 'old-order-uuid' }],
});
```
**Next.js Client**

```typescript filename="connect-has-many.tsx"
import { pylo } from '@/lib/pylo-hooks';

const { mutateAsync } = pylo.usePyloUpsert('customer');

await mutateAsync({
  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](/data/mutations#search-values).

Relations point at your own entities or at system entities. A relation to `PyloMedia` attaches
[file uploads](/files/overview) to your records.

## System entities

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

| Entity       | Purpose                            |
| ------------ | ---------------------------------- |
| `PyloUser`   | User accounts and authentication   |
| `PyloMedia`  | Files and media uploads            |
| `PyloApiKey` | API tokens for programmatic access |
| `PyloApp`    | Application configuration          |

Next: [Type Safety](/concepts/type-safety)
