TypeScript setup
The graph-specific options — labels on an entity, relationship on an entity or a relation
property — are not invented by a custom decorator. They are added to MikroORM's own option
types by declaration merging, so they appear in autocomplete beside nullable and ref, and no
as any is ever needed.
@ManyToOne(() => Author, { ref: true, relationship: { type: 'WROTE', direction: 'IN' } })
// ^ autocompletes, type-checked
author!: Ref<Author>;
@Rel() decorator?Reusing MikroORM's PropertyOptions keeps the options inside the lifecycle the ORM already runs:
discovery, metadata caching, schema generation and the reflection providers all see them. A custom
decorator would have to be scanned separately, which is exactly where reflection-based extraction
gets brittle.
Which package the decorators come from
MikroORM 7 moved the classic decorators out of @mikro-orm/core:
import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/decorators/legacy';
Everything else — Collection, Ref, wrap, the exception classes, defineEntity — comes from
mikro-orm-neo4j, which re-exports core.
Property-level options
The property option lives on interfaces (PropertyOptions, ReferenceOptions), and TypeScript
merges interfaces across modules. If your project does not pick the option up — a strict types
array in tsconfig.json is the usual reason — one file restores it:
import '@mikro-orm/core';
declare module '@mikro-orm/core' {
interface PropertyOptions<Owner> {
relationship?: { type?: string; direction?: 'IN' | 'OUT' };
}
}
Entity-level options
labels and the entity-level relationship sit on EntityOptions, which core declares as a type
alias rather than an interface — and a type alias cannot be merged into from another module. Two
routes work today, and both are exact:
import { defineEntity } from 'mikro-orm-neo4j';
// 1. defineEntity — the graph options are part of its own signature.
export const MovieSchema = defineEntity({
name: 'Movie',
labels: ['Movie', 'Production'],
properties: (p) => ({ id: p.uuid().primary(), title: p.string() }),
});
import type { EntityOptions } from '@mikro-orm/core';
import { Entity } from '@mikro-orm/decorators/legacy';
// 2. Decorators — assert the options object.
@Entity({ labels: ['Movie', 'Production'] } as EntityOptions<typeof Movie>)
export class Movie {
/* … */
}
The assertion is a compiler formality, not a cast away from safety: the driver reads labels off the
metadata either way, and the object is still checked against every other field of EntityOptions.
Compiler options
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"skipLibCheck": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
experimentalDecorators and emitDecoratorMetadata are only needed for the decorator route (with
import 'reflect-metadata' at your entry point). defineEntity needs neither, which is one reason
to prefer it in a new codebase — the other being that its entity types are inferred rather than
reflected.