docs/versioned_docs/version-7.0/define-entity.md
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
The defineEntity helper is the recommended way to define entities programmatically without decorators. It is built on top of EntitySchema, leveraging TypeScript's type inference to generate entity types automatically.
defineEntityUse defineEntity to declare your entities. It returns an EntitySchema instance with full type information.
import { defineEntity, p } from '@mikro-orm/core';
const BookSchema = defineEntity({
name: 'Book',
properties: {
id: p.integer().primary(),
title: p.string(),
author: () => p.manyToOne(Author).inversedBy('books'),
tags: () => p.manyToMany(BookTag).inversedBy('books').fixedOrder(),
},
});
export class Book extends BookSchema.class {}
BookSchema.setClass(Book);
The p shortcut is also available as defineEntity.properties.
defineEntity + class pattern (recommended)When you extend the auto-generated class and register it via setClass(), you get several benefits:
Book variable shows Book — not a complex intersection type with generics and symbolsconst AuthorSchema = defineEntity({
name: 'Author',
properties: {
id: p.integer().primary(),
firstName: p.string(),
lastName: p.string(),
books: () => p.oneToMany(Book).mappedBy('author'),
},
});
class Author extends AuthorSchema.class {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
AuthorSchema.setClass(Author);
// Usage:
const author = em.create(Author, { firstName: 'John', lastName: 'Doe' });
console.log(author.fullName()); // "John Doe"
Important:
setClass()must be called before the ORM discovery process runs (i.e., beforeMikroORM.init()). Make sure to call it at module load time, right after defining the extended class.
defineEntity (without a class)You can also use defineEntity without extending a class. This is more compact for simple entities but produces complex computed types on hover.
import { type InferEntity, defineEntity, p } from '@mikro-orm/core';
export const Book = defineEntity({
name: 'Book',
properties: {
id: p.integer().primary(),
title: p.string(),
author: () => p.manyToOne(Author).inversedBy('books'),
tags: () => p.manyToMany(BookTag).inversedBy('books').fixedOrder(),
},
});
// Use InferEntity to extract the entity type
export type IBook = InferEntity<typeof Book>;
When creating new entity instances without a class, use em.create() which will create an instance of the internally generated class:
const book = em.create(Book, { title: 'My Book', author });
await em.flush();
With defineEntity, use composition (shared property objects) instead of class inheritance for base properties:
const p = defineEntity.properties;
const baseProperties = {
id: p.integer().primary(),
createdAt: p.datetime().onCreate(() => new Date()),
updatedAt: p.datetime()
.onCreate(() => new Date())
.onUpdate(() => new Date()),
};
const BookSchema = defineEntity({
name: 'Book',
properties: {
...baseProperties,
title: p.string(),
author: () => p.manyToOne(Author),
},
});
export class Book extends BookSchema.class {}
BookSchema.setClass(Book);
defineEntity.properties (aliased as p) provides all MikroORM built-in types. To use custom types, use p.type():
const properties = {
string: p.string(),
float: p.float(),
boolean: p.boolean(),
json: p.json<{ foo: string; bar: number }>().nullable(),
stringArray: p.type(ArrayType<string>).nullable(),
numericArray: p.type(new ArrayType(i => +i)).nullable(),
point: p.type(PointType).nullable(),
};
<Tabs groupId="entity-schema-def" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, ]}
<TabItem value="define-entity-class">
const BookTagSchema = defineEntity({
name: 'BookTag',
properties: {
_id: p.type(ObjectId).primary(),
id: p.string().serializedPrimaryKey(),
name: p.string(),
books: () => p.manyToMany(Book).mappedBy('tags'),
},
});
export class BookTag extends BookTagSchema.class {}
BookTagSchema.setClass(BookTag);
export const BookTag = defineEntity({
name: 'BookTag',
properties: {
_id: p.type(ObjectId).primary(),
id: p.string().serializedPrimaryKey(),
name: p.string(),
books: () => p.manyToMany(Book).mappedBy('tags'),
},
});
export type IBookTag = InferEntity<typeof BookTag>;
Hooks can be registered in two ways:
hooks property — pass an object with arrays of hook handlers directly in the defineEntity call.addHook method — register hook handlers after the entity is defined.Both approaches accept functions (arrow functions, named functions, async functions). The entity instance is available via args.entity. See Events and Lifecycle Hooks for the full list of available hooks and EventArgs details.
<Tabs groupId="entity-schema-hooks" defaultValue="define-entity-class" values={[ {label: 'defineEntity + class', value: 'define-entity-class'}, {label: 'defineEntity', value: 'define-entity'}, ]}
<TabItem value="define-entity-class">
Use addHook after the class is defined for full type safety:
const BookTagSchema = defineEntity({
name: 'BookTag',
properties: {
_id: p.type(ObjectId).primary(),
id: p.string().serializedPrimaryKey(),
name: p.string(),
version: p.integer(),
books: () => p.manyToMany(Book).mappedBy('tags'),
},
});
export class BookTag extends BookTagSchema.class {}
BookTagSchema.setClass(BookTag);
// highlight-start
BookTagSchema.addHook('beforeCreate', (args: EventArgs<BookTag>) => {
args.entity.version = 1;
});
BookTagSchema.addHook('beforeUpdate', (args: EventArgs<BookTag>) => {
args.entity.version++;
});
// highlight-end
</TabItem> <TabItem value="define-entity">You can also pass hooks inline via the
hooksproperty, butargs.entitywill be typed asanythere because the entity type is not yet known. Explicitly typing the parameter (e.g.EventArgs<BookTag>) won't work either, as it would create a circular reference. UseaddHookafter the class is defined to get full type safety.
Use addHook after the entity is defined for full type safety:
export const BookTag = defineEntity({
name: 'BookTag',
properties: {
_id: p.type(ObjectId).primary(),
id: p.string().serializedPrimaryKey(),
name: p.string(),
version: p.integer(),
books: () => p.manyToMany(Book).mappedBy('tags'),
},
});
export type IBookTag = InferEntity<typeof BookTag>;
// highlight-start
BookTag.addHook('beforeCreate', (args: EventArgs<IBookTag>) => {
args.entity.version = 1;
});
BookTag.addHook('beforeUpdate', (args: EventArgs<IBookTag>) => {
args.entity.version++;
});
// highlight-end
</TabItem> </Tabs>You can also pass hooks inline via the
hooksproperty, butargs.entitywill be typed asanythere because the entity type is not yet known. Explicitly typing the parameter (e.g.EventArgs<IBookTag>) won't work either, as it would create a circular reference. UseaddHookafter the entity and its type alias are defined to get full type safety.
EntitySchema (low-level API)defineEntity returns an EntitySchema instance — the same class you can instantiate directly. Using EntitySchema directly is generally not necessary, as defineEntity provides a more ergonomic API with full type inference. However, it's available for advanced use cases or vanilla JavaScript projects.
export interface IBook {
title: string;
author: Author;
publisher: Publisher;
tags: Collection<BookTag>;
}
export const BookSchema = new EntitySchema<IBook>({
name: 'Book',
extends: CustomBaseEntitySchema,
properties: {
title: { type: 'string' },
author: { kind: 'm:1', entity: () => Author, inversedBy: 'books' },
publisher: { kind: 'm:1', entity: () => Publisher, inversedBy: 'books' },
tags: { kind: 'm:n', entity: () => BookTag, inversedBy: 'books', fixedOrder: true },
},
});
EntitySchemaYou can pass a class option instead of name:
export class Author extends CustomBaseEntity {
name: string;
email: string;
constructor(name: string, email: string) {
super();
this.name = name;
this.email = email;
}
}
export const AuthorSchema = new EntitySchema({
class: Author,
extends: CustomBaseEntitySchema,
properties: {
name: { type: 'string' },
email: { type: 'string', unique: true },
},
});
The parameter of EntitySchema requires either name or class. When using class, extends will be automatically inferred. Additional parameters:
name: string;
class: Constructor<T>;
extends: string;
tableName: string; // alias for `collection: string`
properties: { [K in keyof T & string]: EntityProperty<T[K]> };
indexes: { properties: string | string[]; name?: string; type?: string }[];
uniques: { properties: string | string[]; name?: string }[];
repository: () => Constructor<EntityRepository<T>>;
hooks: Partial<Record<keyof typeof EventType, ((string & keyof T) | NonNullable<EventSubscriber[keyof EventSubscriber]>)[]>>;
abstract: boolean;
orderBy: QueryOrderMap<T> | QueryOrderMap<T>[]; // default ordering for the entity
As a value for
typeyou can also use one ofString/Number/Boolean/Date.