Skip to content

Decorating entities

Use @Seed() on any entity property to describe how it should be populated. Plain column properties (scalars) take a factory function; relation properties take the bare decorator (or a count option for collections).

ts
import { Entity, PrimaryKey, Property, OneToMany, ManyToOne } from '@mikro-orm/decorators/legacy'
import { faker } from '@faker-js/faker'
import { Seed } from '@joakimbugge/mikroorm-seeder'

@Entity()
class Author {
  @PrimaryKey()
  id!: number

  @Seed(() => faker.person.fullName())
  @Property()
  name!: string

  @Seed({ count: 3 })
  @OneToMany(() => Book, (b) => b.author)
  books!: Book[]
}

@Entity()
class Book {
  @PrimaryKey()
  id!: number

  @Seed(() => faker.lorem.words(4))
  @Property()
  title!: string

  @Seed()
  @ManyToOne(() => Author)
  author!: Author
}

Passing an EntityManager to factories

If a factory needs to query the database during seeding, the em you provide in options is forwarded to every factory via the context's first argument:

ts
@Seed(async ({ em }) => {
  const role = await em!.findOneOrFail(Role, { name: 'admin' })
  return role
})
@ManyToOne(() => Role)
role!: Role

This is useful for lookups — finding an existing record and reusing it rather than creating a new one. For more complex logic — such as picking a random element from a result set or conditional branching — consider using the values option instead. It keeps that logic in the call site rather than the entity decorator.

Depending on earlier instances in a batch

When using createMany or saveMany, each factory receives a previous map on the context. ctx.previous.get(EntityClass) returns a snapshot of all instances of that type created so far in the current batch — so instance i sees instances 0..i-1:

ts
@Entity()
class Booking {
  @Seed((ctx) => {
    const last = (ctx.previous?.get(Booking) as Booking[] | undefined)?.at(-1)
    return last ? last.to.plus({ days: 1 }) : DateTime.now()
  })
  @Property()
  from!: DateTime

  @Seed((_, self: Booking) => self.from.plus({ days: faker.number.int({ min: 2, max: 14 }) }))
  @Property()
  to!: DateTime
}

const bookings = await seed(Booking).createMany(5)
// bookings[0].from → now
// bookings[1].from → bookings[0].to + 1 day
// bookings[2].from → bookings[1].to + 1 day  … and so on

previous is a Map keyed by entity class, so when a child entity is created as part of a relation it can also read the parent's batch:

ts
@Entity()
class Comment {
  @Seed((ctx) => {
    // How many Posts have already been created in this batch?
    const posts = ctx.previous?.get(Post) as Post[] | undefined
    return posts?.at(-1)?.id ?? null
  })
  @Property({ nullable: true })
  latestPostId!: number | null
}

Each createMany call starts with an empty entry for the type being batched, so Books created for Author[0] and Books created for Author[1] each see only their own siblings — never each other's.

Depending on earlier properties

Properties are seeded in declaration order. Each factory receives the partially-built entity as its second argument (self), so a property can read any value that was seeded above it:

ts
@Entity()
class Event {
  @Seed(() => faker.date.past())
  @Property()
  beginDate!: Date

  @Seed((_, self: Event) => faker.date.future({ refDate: self.beginDate }))
  @Property()
  endDate!: Date
}

Annotating self with the entity class (self: Event above) gives full type inference.

Properties declared below the current property are not yet set and will be undefined on self at that point.

Using the sequence index

Every factory receives a zero-based index as its third argument. When called from createMany or saveMany, the index counts up across the batch — useful for generating unique sequential values:

ts
@Seed((_, __, i) => `user-${i}@example.com`)
@Property()
email!: string

When called from create or save (single entity), the index is always 0.

Circular and bidirectional relations

When seeding an entity with relations, those related entities are seeded too. @Seed() on a relation property always creates a new entity instance — it does not select an existing one from the database. If you need to link to a specific existing entity, use the values option.

If a related entity has a back-reference to the parent (a bidirectional relation), you have a cycle:

ts
@Entity()
class Author {
  @Seed()
  @OneToMany(() => Book, (b) => b.author)
  books!: Book[]
}

@Entity()
class Book {
  @Seed()
  @ManyToOne(() => Author)
  author!: Author  // back-reference creates a cycle
}

Without intervention, this would loop forever: seeding Author seeds its books, each book seeds its author, which seeds more books, and so on.

How cycles are broken

mikroorm-seeder breaks cycles at the point where a type would re-enter itself:

  • Seeding Author → author.books are seeded → each book's author is not seeded (cycle detected)
  • Seeding Book directly → book.author is seeded fully → book.author.books are not seeded (cycle detected)

The property is left undefined when the cycle is detected. If you need a value there, use the values option to inject it after creation:

ts
const books = await seed(Book).createMany(5, {
  values: { author } // inject a specific Author across all books
})

When relations are disabled

Cycles are only a concern when seeding relations. If you pass relations: false, no related entities are seeded at all — the cycle is avoided entirely:

ts
const author = await seed(Author).create({ relations: false })
// author.books → undefined (no relations seeded)

const book = await seed(Book).create({ relations: false })
// book.author → null in the database (no relation seeded)

Embedded types

@Embedded properties are seeded automatically. Annotate the embedded class's properties with @Seed() just like a regular entity:

ts
import { Embeddable, Embedded, Entity, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'

@Embeddable()
class Address {
  @Seed(() => faker.location.streetAddress())
  @Property()
  street!: string

  @Seed(() => faker.location.city())
  @Property()
  city!: string
}

@Entity()
class User {
  @PrimaryKey()
  id!: number

  @Seed()
  @Embedded(() => Address)
  address!: Address
}

Calling create(User) builds a User with a fully populated Address instance on user.address.

MikroORM defineEntity()

@Seed() is designed for the decorator-based entity style (@Entity(), @Property(), etc.) and is not compatible with defineEntity().

MikroORM's schema-first approach looks like this:

ts
import { defineEntity, p } from '@mikro-orm/core'

const BookSchema = defineEntity({
  name: 'Book',
  properties: {
    title: p.string(),
    author: () => p.manyToOne(Author),
  },
})

export class Book extends BookSchema.class {}
BookSchema.setClass(Book)

Even in this hybrid form, MikroORM never runs @Entity() on the class, so the seeder cannot resolve its property metadata at runtime. @Seed() applied to Book properties will not be picked up.

Use standard MikroORM decorators (@Entity(), @Property(), @ManyToOne(), etc.) to take full advantage of mikroorm-seeder.

Released under the MIT License.