Defining Entities via defineEntity
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.
defineEntity
Use 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.
The defineEntity + class pattern (recommended)
When you extend the auto-generated class and register it via setClass(), you get several benefits:
- Clean hover types: Hovering over a
Bookvariable showsBook— not a complex intersection type with generics and symbols - Better performance: A real named class is more efficient than the dynamically generated anonymous class used by the pure approach
- Custom methods: Add domain logic directly on entity instances without any workarounds
- No property duplication: Properties are defined once in the schema and automatically inherited by the class
const 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.
Pure 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();
Reusing base properties via composition
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);
Property types
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(),
};
MongoDB example
- defineEntity + class
- defineEntity
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 example
Hooks can be registered in two ways:
hooksproperty — pass an object with arrays of hook handlers directly in thedefineEntitycall.addHookmethod — 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.
- defineEntity + class
- defineEntity
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);
BookTagSchema.addHook('beforeCreate', (args: EventArgs<BookTag>) => {
args.entity.version = 1;
});
BookTagSchema.addHook('beforeUpdate', (args: EventArgs<BookTag>) => {
args.entity.version++;
});
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>;
BookTag.addHook('beforeCreate', (args: EventArgs<IBookTag>) => {
args.entity.version = 1;
});
BookTag.addHook('beforeUpdate', (args: EventArgs<IBookTag>) => {
args.entity.version++;
});
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 },
},
});
Using a class with EntitySchema
You 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 },
},
});
Configuration Reference
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.