What's New in MikroORM 7? Complete Feature Guide
After 18 months of development, MikroORM 7 “Unchained” has landed. I’ve been using MikroORM on several projects, and this release addresses many pain points I’ve encountered - especially the lack of polymorphic relations. Let me walk you through the changes that actually matter for day-to-day development.
Why “Unchained”?
The name isn’t marketing fluff. The core package now has zero runtime dependencies. Previously, pulling in @mikro-orm/core meant dragging Knex and its dependency tree along. Now? Nothing. This isn’t just about bundle size - it opens the door for Deno, Cloudflare Workers, and other edge runtimes.
The Feature I’ve Been Waiting For: Polymorphic Relations
This was the most requested feature, and for good reason. If you’ve ever built a commenting system that needs to work with multiple entity types, you know the pain. Here’s how it works now:
@Entity()abstract class Content { @PrimaryKey() id!: number
@Property() title!: string}
@Entity()class Article extends Content { @Property() text!: string}
@Entity()class Video extends Content { @Property() url!: string}
@Entity()class Comment { @PrimaryKey() id!: number
// This is the magic - one relation, multiple target types @ManyToOne(() => Content, { polymorphic: true }) content!: Content // Can be Article or Video
@Property() text!: string}Query works exactly as you’d expect:
const comments = await em.find(Comment, {}, { populate: ['content']})
// Access the concrete typefor (const comment of comments) { if (comment.content instanceof Article) { console.log('Article:', comment.content.title) } else if (comment.content instanceof Video) { console.log('Video:', comment.content.url) }}Why does this matter? Before v7, you’d need separate comment tables for each content type or a complex join table setup. Now it’s one clean relation.
Streaming for Large Datasets
If you’ve ever run out of memory processing a large table, this one’s for you:
// Process millions of records without loading them allfor await (const user of em.stream(User, {})) { await sendNewsletter(user)}
// Works with QueryBuilder tooconst stream = em.createQueryBuilder(User, 'u') .where({ active: true }) .stream()
for await (const user of stream) { await processUser(user)}The streaming implementation uses database cursors under the hood, so you’re not buffering everything in memory. I used this for a data migration job that previously crashed my Node process - it now runs smoothly.
Kysely Replaces Knex
The query building layer has moved from Knex to Kysely. For most developers, this is transparent - the QueryBuilder API hasn’t changed. But there are benefits:
- Better TypeScript inference (Kysely was built for type safety)
- More active development (Knex has slowed down)
- Cleaner internal architecture
If you’re using raw SQL or the createQueryBuilder() method, you might notice better autocomplete and type checking. The migration was handled internally, so your existing queries should work without changes.
Collection Size Queries Without Loading
A small but useful addition - you can now query by collection size:
// Find users with more than 5 postsconst activeUsers = await em.find(User, { posts: { $size: { $gt: 5 } }})
// Find categories with exactly 10 itemsconst specificCategories = await em.find(Category, { items: { $size: 10 }})Previously, this required custom SQL or loading collections first. Now it’s a one-liner.
Common Table Expressions (CTEs)
For complex queries, CTEs are now supported:
// Recursive CTE for hierarchical dataconst result = await em.createQueryBuilder(Category, 'c') .withCTE('category_tree', qb => qb .select(['id', 'name', 'parent_id']) .from('categories') .where({ parent_id: null }) ) .from('category_tree') .getResult()This opens up patterns like traversing hierarchical data, complex aggregations, and multi-step transformations that were painful to write before.
View Entities and Materialized Views
PostgreSQL views are now first-class citizens:
@Entity({ view: true })class UserPostCount { @PrimaryKey() userId!: number
@Property() userName!: string
@Property() postCount!: number}
// Materialized views for caching heavy queries@Entity({ view: true, materialized: true })class CachedStatistics { @PrimaryKey() id!: number
@Property() value!: number}
// Refresh when neededawait em.refresh(CachedStatistics)This is particularly useful for read-heavy dashboards where you don’t need real-time data.
Performance: 40% Fewer Type Instantiations
The MikroORM team optimized TypeScript inference, resulting in 40% fewer type instantiations. In practice, this means:
- Faster
tsccompilation times - Better IDE performance
- Less memory usage during builds
On a project with 200+ entities, I saw compilation time drop from 45 seconds to 28 seconds. Your mileage will vary, but it’s a noticeable improvement.
Oracle Database Support
MikroORM now supports 8 databases: PostgreSQL, MySQL, MariaDB, SQLite, MongoDB, LibSQL, MS SQL Server, and Oracle. If you’re in an enterprise environment stuck with Oracle, this is your green light to use MikroORM.
import { OracleDriver } from '@mikro-orm/oracle'
export default defineConfig({ driver: OracleDriver, host: 'localhost', port: 1521, dbName: 'ORCL',})Migration Gotchas
Before upgrading from v6, be aware of these breaking changes:
- forceUtcTimezone defaults to true - If your app relies on local timezone handling, explicitly set this to
falseor audit your datetime handling - Kysely is now a required peer dependency - Run
npm install kysely - mikro-orm-esm script is gone - Use the unified CLI
Check the migration guide for the full list of changes.
When Should You Upgrade?
- New projects: Absolutely. Start with v7.
- Existing projects: Wait for v7.1 if you rely heavily on datetime handling or have complex migrations. The breaking changes are manageable but require testing.
- Edge/serverless projects: Now. The zero-dependency core and pre-compiled entity support make MikroORM viable for Cloudflare Workers and similar environments.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments