> Every UQL docs page, as Markdown: https://uql-orm.dev/llms.txt
> The same docs over MCP: https://uql-orm.dev/mcp
> Before writing UQL code, read the skill: https://uql-orm.dev/.well-known/agent-skills/uql-orm/SKILL.md

# defineEntity

> Declare an entity as data with defineEntity, its row type derived from the fields, with no decorators.

Source: https://uql-orm.dev/entities/define-entity

`defineEntity` declares an entity as data and returns its class. The row type is derived from the fields, so nothing is written twice, and no decorator is involved. Its options are the [decorators’](https://uql-orm.dev/entities/basic.md), so the two styles mix freely in one project.

```ts
import { f, defineEntity, sql } from 'uql-orm';
import { v7 as uuidv7 } from 'uuid';
import { z } from 'zod';

// Any Standard Schema types the payload: Zod here, or Valibot, ArkType, or one written by hand.
const Settings = z.object({ theme: z.string(), bio: z.string().optional() });

export const User = defineEntity({
  name: 'User',
  fields: {
    id: f.uuid().id().onInsert(uuidv7),
    email: f.string().nullable().unique(),
    settings: f.json(Settings).nullable(),
  },
}).relations((rel) => ({
  posts: () => rel.oneToMany(Post, { mappedBy: (post) => post.author }),
}));

// One name for the entity and its rows, as a class has: a function takes `user: User`.
export type User = InstanceType<typeof User>;

export const Post = defineEntity({
  name: 'Post',
  fields: {
    id: f.number().autoIncrement(),
    title: f.string(),
    status: f.enum(['draft', 'published']).default('draft'),
    authorId: f.uuid().nullable(),
    publishedAt: f.date().nullable(),
  },
})
  .computed((post) => ({
    slug: f
      .string()
      .nullable()
      .as(sql`lower(${post.title})`),
  }))
  .indexes((post, index) => [
    index([post.title, post.authorId], { unique: true }),
  ])
  .filters({
    published: { where: { publishedAt: { $ne: null } }, default: false },
  })
  .hooks({ beforeInsert: [(post) => void (post.title = post.title.trim())] })
  .relations((rel) => ({
    author: () => rel.manyToOne(User, { references: (post) => post.authorId }),
  }));

export type Post = InstanceType<typeof Post>;
```

`Post` reads `title` and `status` as always present, `status` as `'draft' | 'published'`, and `authorId` as `string | null`. An insert may leave `status` out, since it has a default, and `id`, which the database assigns.

## Decorators or defineEntity

Both give the same queries and the same types, and you can mix them entity by entity.

**Decorators** suit you if you think in classes. You get a named type on hover and in errors, and a foreign key right on its column, so entities can point at each other in any order. It is also what TypeORM and MikroORM users already know. They are a little stricter, too:

- A relation’s `mappedBy` must name a relation, or a foreign key holding this entity’s key, at compile time. `defineEntity` takes any member’s name there and checks the rest when the entity is first read.
- Two entities reading each other never trip TypeScript’s inference, which `defineEntity` can when an eager read crosses them (TS7022).

**`defineEntity`** suits you if you’d rather write each field once. The row type comes from the builders, so entities run 19 to 30% shorter and cost the compiler about 45% less. It needs no decorator transform, which helps when:

- Your transformer does not implement decorators, like Oxc (Vite 8’s own).
- You are in NestJS, which keeps `experimentalDecorators` on for its own DI, and one config cannot compile both decorator specs.
- You run the CLI on plain `node`: type stripping runs no decorators, so decorated entities need Bun or `tsx`.
- You write JavaScript, or build entities at run time, like a CMS content type.

A column narrower than its builder is a [type argument](https://uql-orm.dev/entities/basic.md#branded-types): a branded id, `f.uuid<UserId>()`, or a JSON payload with no runtime schema, `f.json<Settings>()`. In return, relations come last as thunks, a foreign key is a relation rather than a column option, and each entity needs a `type Post = InstanceType<typeof Post>` line. The rest is in [what defineEntity asks for](#what-defineentity-asks-for).

Both forms share structure. A decorated base class carries its fields, relations, hooks and filters to every entity extending it, and `defineEntity` does the same with [`extends`](https://uql-orm.dev/entities/inheritance.md#a-base-for-defineentity). Its fields are a plain object too, so a shared set spreads in: `fields: { ...stamped, title: f.string() }`.

## Options

The object names the entity and its columns:

| Key | Decorator | Notes |
| - | - | - |
| `name` | The class name | A [naming strategy](https://uql-orm.dev/naming-strategy.md) derives the table from it, as from a decorated class’s name. |
| `table` | `@Entity({ name })` | The table, exactly as written. |
| `schema` | `@Entity({ schema })` | See [multiple schemas](https://uql-orm.dev/multiple-schemas.md). |
| `fields` | `@Field` | [Column builders](https://uql-orm.dev/entities/basic.md#field-options); a key is `.id()`, as a decorated field declares it. |
| `extends` | `class Child extends Base` | The class it inherits from, see [Inheritance](https://uql-orm.dev/entities/inheritance.md#a-base-for-defineentity). |

Everything else is a section chained after it, each called once, in any order but `.relations()`, which comes last; only a `.filters()` reading through the relations may follow it:

| Section | Decorator | Notes |
| - | - | - |
| `.computed()` | `@Computed` | Columns the database computes, read off the stored ones: ``(post) => ({ slug: f.string().as(sql`...`) })``. |
| `.indexes()` | `@Entity({ indexes })` | `(post, index) => [index([post.title], 'by_title')]`, see [Indexes](https://uql-orm.dev/entities/indexes.md). |
| `.checks()` | `@Entity({ checks })` | [Check constraints](https://uql-orm.dev/entities/enum-checks.md#check-constraints). |
| `.triggers()` | `@Entity({ triggers })` | [Trigger options](https://uql-orm.dev/entities/triggers.md). |
| `.filters()` | `@Entity({ filters })` | [Filter options](https://uql-orm.dev/querying/filters.md#defining-a-filter). |
| `.hooks()` | Hook decorators | Each [lifecycle event](https://uql-orm.dev/entities/lifecycle-hooks.md) and its functions of the row: `{ beforeInsert: [(post, ctx) => ...] }`. |
| `.relations()` | `@ManyToOne` and the others | `(rel) => ({ author: () => rel.manyToOne(User, { ... }) })`, each taking its [relation options](https://uql-orm.dev/entities/relations.md). |

## What defineEntity asks for

- Each relation is a thunk, `author: () => rel.manyToOne(...)`. TypeScript infers two entities that point at each other only when each reads the other lazily, so either can be declared first, in either file.
- `.computed()`, `.indexes()`, `.checks()`, `.triggers()` and `.hooks()` read the columns, not the relations. `.filters()` reads them too, or, declared after `.relations()`, the relations at any depth.
- A JSON column’s payload is a [Standard Schema](https://standardschema.dev), `f.json(schema)`, typed by its output: any library’s schema (Zod, Valibot, ArkType), or one written by hand. Paths into it are typed in indexes and queries; without one, any path reads.
- Behaviour on a row is a function of it, `label(post: Post)`: rows read back as plain objects, so a method would never be there.
- A value over a to-many reads as a column declared in `.relations()`’ second callback, as a thunk of its aggregate, `(author) => ({ bookCount: () => author.books.count(), topStock: () => author.books.max((book) => book.stock) })`. The same callback reads the entity’s own columns, so one expression can combine them.
- Each relation takes the options its cardinality joins by: a `manyToOne` names its `references`, a `oneToMany` its `mappedBy`, `through` or `references` pairs, or nothing, where its target holds one relation pointing back. A to-one’s `references` column is checked against its target’s key type at compile time; `mappedBy` is checked when the entity is first read.

## An entity defined at runtime

When the shape is data, such as a CMS content type an admin creates, the fields are built from it and `sync({ entity })` gives the entity a table. See [Runtime entities](https://uql-orm.dev/entities/runtime.md).
