Upgrading from v6 to v7
Following sections describe (hopefully) all breaking changes, most of them might be not valid for you, like if you do not use custom
NamingStrategyimplementation, you do not care about the interface being changed.
ECMAScript modules
MikroORM v7 is a native ESM package now. It can be still consumed from a CJS project, as long as you use TypeScript and Node.js version that supports require(esm).
Node 22.17+ required
Support for older node versions was dropped.
TypeScript 5.8+ required
Support for older TypeScript versions was dropped. Older versions might work too, but only if your project is also ESM.
Decorators moved to @mikro-orm/decorators package
The decorators are now available in the @mikro-orm/decorators package, so you need to install it explicitly:
- npm
- Yarn
- pnpm
- Bun
npm install @mikro-orm/decorators
yarn add @mikro-orm/decorators
pnpm add @mikro-orm/decorators
bun add @mikro-orm/decorators
Moreover, there are now both legacy and ES spec decorator definitions available.
To use legacy decorators, import them from @mikro-orm/decorators/legacy:
- import { Entity, PrimaryKey, Property, MikroORM } from '@mikro-orm/sqlite';
+ import { Entity, PrimaryKey, Property } from '@mikro-orm/decorators/legacy';
+ import { MikroORM } from '@mikro-orm/sqlite';
To use ES spec decorators, import them from @mikro-orm/decorators/es:
import { Entity, PrimaryKey, Property } from '@mikro-orm/decorators/es';
ReflectMetadataProvider no longer the default
The ReflectMetadataProvider has been moved to the @mikro-orm/decorators/legacy package, just like all the legacy decorators. It is no longer the default, you need to use it explicitly if you want to keep using legacy decorators with metadata reflection. You also need to install the reflect-metadata package for that.
import { ReflectMetadataProvider } from '@mikro-orm/decorators/legacy';
import { defineConfig } from '@mikro-orm/sqlite';
export default defineConfig({
metadataProvider: ReflectMetadataProvider,
// ...
});
knex replaced with kysely as query runner
@mikro-orm/knexpackage is renamed to@mikro-orm/sql@mikro-orm/knex-compatpackage is introduced, with arawhelper version which supports knex queries@mikro-orm/better-sqlitedriver is removed@mikro-orm/sqlitedriver usesbetter-sqlite3internally@mikro-orm/mariadbdriver usesmysql2internally (but still comes with MariaDB specific JSON and schema handling)em.getKnex()is replaced withem.getKysely()- support for
qb.getKnexQuery()is removed completely, the ORM now builds queries internally
persistAndFlush and removeAndFlush methods removed
Use em.persist(entity).flush() and em.remove(entity).flush() instead.
String references are no longer supported
Previously, it was possible to use string references in many places, e.g. when calling em.find('User'). This is no longer supported, use class reference, or EntitySchema reference.
-em.find('User');
+em.find(User);
This applies to entity definition too.
-@ManyToOne('User')
+@ManyToOne(() => User)
Also, the checkDuplicateEntities discovery option is removed, since it is no longer relevant. As long as your entities have unique table names, they are valid.
TypeScript support in CLI
TypeScript support was previously provided by ts-node. In v7, the CLI supports various TS loaders:
swcvia@swc-node/register, supports metadata reflectiontsxjititsimp
The default is auto, which means it goes through all those options sequentially and picks the first one available in the project dependencies.
To pick a loader explicitly, use the tsLoader setting in your package.json:
"mikro-orm": {
"tsLoader": "jiti"
}
Or override it via MIKRO_ORM_CLI_TS_LOADER env var.
-npm install ts-node
+npm install @swc-node/register
Other relevant changes:
tsNodeconfig option removed in favor ofpreferTsuseTsNodeis renamed topreferTsalwaysAllowTsoption andMIKRO_ORM_CLI_ALWAYS_ALLOW_TSenv var are removedMIKRO_ORM_CLI_USE_TS_NODEenv var is renamed toMIKRO_ORM_CLI_PREFER_TS
mikro-orm-esm CLI script is removed
Thanks to the switch to SWC this is no longer needed, use the standard mikro-orm CLI script.
QueryBuilder is no longer awaitable
Use qb.execute() or qb.getResult() instead of awaiting it directly.
Default loading strategy is balanced
This strategy should provide a good compromise between query and hydration performance. It uses joins for to-one relations, while issuing separate queries for to-many relations.
Mixed orderBy with string and raw keys no longer allowed
The implementation of raw SQL fragments changed, they are now represented by a symbol instance rather than a string. This means the previous limitations around reusing of raw fragments are no longer valid. On the other hand, this means mixing of raw and regular string keys is no longer allowed, because there is no way for the ORM to preserve the order of object keys.
-em.findAll(User, { orderBy: { email: 'asc', [raw('...')]: 'asc' } });
+em.findAll(User, { orderBy: [{ email: 'asc' }, { [raw('...')]: 'asc' }] });
@CreateRequestContext requires async function
The decorator converts the function to be async, now it will trigger a validation error if the function it is applied to is not async, since it could result in an ignored promise.
Transaction events are fired for nested transactions
All transaction events are now invoked for child transactions, too. You can distinguish them based on the args.transaction.savepointName, or for the beforeTransactionStart based on the presence of args.transaction which is the parent transaction (so undefined means a root transaction).
Embedded properties use relative prefix mode by default
The prefixMode added in v6.4 allows controlling the strategy for computing column names of nested embedded properties. Its default was based on the previous behavior, which was ignoring the parent embedded property prefix if the child embedded property had an explicit prefix option provided. The default has changed to relative now.
You can read more about this option here.
Array properties in object embeddables are considered as JSON by default
Previously, array properties inside object embeddables were not automatically considered as JSON properties—instead, they mapped to the ArrayType as a regular top level property would have. This means that the value in the database was stored as a comma separated list (or an array literal in postgres). In v7, such properties are considered as JSON arrays automatically.
If you want to preserve the previous behavior, set the type explicitly:
-@Property()
+@Property({ type: ArrayType })
array!: string[];
MikroORM.initSync removed
Use the constructor directly:
-const orm = MikroORM.initSync({ ... });
+const orm = new MikroORM({ ... });
MikroORM.init requires options parameter
Previously, the init method was allowed without any parameters, resulting in loading the CLI config automatically.
+import config from './mikro-orm.config.js';
-const orm = await MikroORM.init();
+const orm = await MikroORM.init(config);
ORM extensions are registered before discovery
ORM extensions are now registered before the metadata discovery process. If you used them to modify the metadata, use afterDiscovered hook instead (which you can set up as part of your extension register method).
MIKRO_ORM_TYPE env var only works in CLI
This env var is needed only for the CLI, it used to be respected in the async init method too, which was no longer necessary with the driver-specific exports of the MikroORM object, that infer the driver option automatically. The env var will still work in the CLI.
--config support removed
The command line argument --config is no longer supported outside the CLI. Use MIKRO_ORM_CLI_CONFIG env var instead.
connect option is removed
Database connection is now always established lazily.
Auto flushMode change detection
Change detection is no longer automatic on scalar properties, an explicit em.persist call is required now to detect such change. The trackChanges property option is now removed.
em.addFilter signature
The signature of em.addFilter changed, this method now accepts a single options object.
-em.addFilter('accounts', () => ({ account: { id: { $in: [1] } } }), Tag);
+em.addFilter({
+ name: 'accounts',
+ cond: () => ({ account: { id: { $in: [1] } } }),
+ entity: Tag,
+});
raw fragments aliasing
When aliasing a raw fragment via raw(...).as('alias'), the alias is now automatically quoted. Previously, we tried to detect if it was quoted or not, now the ORM will always quote the alias.
-raw('...').as('"alias"');
+raw('...').as('alias');
Defaults in EntityGenerator
The EntityGenerator now emits entity definitions with the new defineEntity helper by default, and uses JS dictionaries for enums. Also, bidirectional relations are always defined and owning sides use the Ref wrapper.
Changed defaults:
entityDefinition:defineEntity(used to bedecorators)enumMode:dictionary(used to bets-enum)bidirectionalRelations:true(used to be false)identifiedReferences:true(used to be false)
The entitySchema option is now removed in favor of entityDefinition: 'entitySchema'.
Property nullability defaults
The nullability used to be inferred based on the value of cascade option for to-one relations. This inference is now removed, use nullable option explicitly to control the nullability of such properties.
MikroORMOptions type removed
Previously, MikroORMOptions defined keys with defaults as mandatory, and we inferred the Options type out of it. This is now swapped, the Options type is defined as interface with optional keys, and a new RequiredOptions type is introduced that defines all keys with default value as mandatory.
Changes in serialized primary keys (MongoDB)
The mechanism for processing serialized primary keys in MongoDB driver has changed. There might be some side effects, one known difference in behavior is serialization of entities that do not define a serialized primary key. Those used to emit the id field regardless of not having it declared. In v7, such entity would emit _id instead, unless the serialized primary key is declared.
Default propagation in @Transactional is REQUIRED
The default propagation mode of the @Transactional decorator is now REQUIRED, which means that if there is an ongoing transaction, the decorated method will join it; otherwise, a new transaction will be started. The previous default was REQUIRES_NEW, which always started a new transaction. REQUIRES_NEW remains the default for the em.transactional method.
dataloader dependency
The dependency on dataloader package is now defined as optional peer dependency. You need to install it explicitly.
- npm
- Yarn
- pnpm
- Bun
npm install dataloader
yarn add dataloader
pnpm add dataloader
bun add dataloader
Native Node.js glob
The ORM now uses native Node.js glob implementation for file discovery instead of the globby package. This means that some features provided by the globby package are no longer available, the main one being support for brace expansion patterns (e.g. src/{entities,modules}/*.ts). If you rely on those, use tinyglobby directly:
-entities: ['src/{entities,modules}/*.ts'],
+entities: await tinyglobby(['src/{entities,modules}/*.ts']),
Migrations and seeders still support brace expansion in their
globoption.
Dotenv file support removed
If you want to use a .env file, you need to use the dotenv package directly (and install it explicitly):
import 'dotenv/config';
import { defineConfig } from '@mikro-orm/sqlite';
export default defineConfig({
// ...
});
Some discovery options removed
The following discovery options were removed:
disableDynamicFileAccessonly swapped the metadata provider toReflectMetadataProvider(which is no longer the default) and disabled metadata cache (which is disabled by default).requireEntitiesArrayonly triggered a validation error whenentitiesoption contained string paths.alwaysAnalysePropertiesis no longer supported, theTsMorphMetadataProvideralways analyzes properties.
They were relevant back in the day when ts-morph was the default metadata provider.
ArrayCollection class removed
The ArrayCollection class was merged to the Collection class, use it instead.
MikroORM extension getters removed
The following methods were removed from the MikroORM class:
orm.getSchemaGenerator()in favor oform.schemagetterorm.getMigrator()in favor oform.migratorgetterorm.getSeeder()in favor oform.seedergetterorm.getEntityGenerator()in favor oform.entityGeneratorgetter
SchemaGenerator methods renamed
The following methods were renamed:
orm.schema.createSchema()renamed toorm.schema.create()orm.schema.updateSchema()renamed toorm.schema.update()orm.schema.dropSchema()renamed toorm.schema.drop()orm.schema.clearDatabase()renamed toorm.schema.clear()orm.schema.refreshDatabase()renamed toorm.schema.refresh()orm.seeder.createSeeder()renamed toorm.seeder.create()orm.migrator.createMigration()renamed toorm.migrator.create()orm.migrator.createInitialMigration()renamed toorm.migrator.createInitial()orm.migrator.getExecutedMigration()renamed toorm.migrator.getExecuted()orm.migrator.getPendingMigration()renamed toorm.migrator.getPending()orm.migrator.checkMigrationNeeded()renamed toorm.migrator.checkSchema()
Change hashing algorithm
Previously, we used md5 hash algorithm in various places, mainly to compute a stable hash for a string value, e.g. for long index names. This was made configurable and sha256 was also allowed via hashAlgorithm option. The algorithm is now replaced with FNV-1a 64-bit, so we don't have to depend on node:crypto. The option hashAlgorithm is removed.
forceUtcTimezone enabled by default
The forceUtcTimezone option is now enabled by default for all SQL drivers. This means datetime columns without timezone (datetime in MySQL/MSSQL, timestamp in PostgreSQL) will store and retrieve values in UTC.
If your application relies on storing dates in local timezone, you can disable this by setting forceUtcTimezone: false:
MikroORM.init({
forceUtcTimezone: false,
});
Note: If you have existing data stored in local timezone and enable UTC mode, or vice versa, the timestamps will be interpreted incorrectly. Make sure to migrate your data accordingly, or keep the setting consistent with how your data was originally stored.
processOnCreateHooksEarly enabled by default
The processOnCreateHooksEarly option is now enabled by default. onCreate hooks are now executed inside em.create method if used explicitly.
validate and strict options removed
Both are now enabled by default, and the auto-fixing mechanism is removed. This means that if you try to map a raw result from the database, it needs to be correctly typed. One example where this can happen is when you use some aggregation function like sum, in some dialects like postgres, it produces strings by default, which are no longer mappable to a number property by default.
Connection.loadFile method removed
A new method is introduced to execute a schema dump called Connection.executeDump. Loading of the dump from a file is now the user's responsibility.
-await orm.driver.getConnection().loadFile('schema.sql');
+import { readFile } from 'node:fs/promises';
+const buf = await readFile('schema.sql');
+await orm.driver.getConnection().executeDump(buf.toString());`
em.findByCursor signature changed
The where parameter is now moved to the options object.
-const cursor = await em.findByCursor(User, { email: '...' }, { first: 3 });
+const cursor = await em.findByCursor(User, { where: { email: '...' }, first: 3 });
qb.as() signature changed
Previosly, it was possible to prefix the alias with target entity name. This is now done via two parameter signature instead:
-qb.as('User.fullName');
+qb.as(User, 'fullName');
Private constructors no longer allowed with defineEntity/EntitySchema
To be able to infer constructor parameters, we need the constructor to be public. The ORM will use the constructor internally (e.g. in em.create), so this was partially a lie. If you need to use a private constructor, please cast the class parameter to any and use the first generict parameter:
class User {
private constructor(private readonly id: number) {}
}
// with defineEntity
const UserSchema1 = defineEntity<User>({
class: User as any,
properties: {
// ...
},
});
// or with EntitySchema directly
const UserSchema2 = new EntitySchema<User>({
class: User as any,
properties: {
// ...
},
});