TypeScript

Fix MikroORM: global EntityManager instance is disallowed

A fresh MikroORM 5/6 project throws a ValidationError on the first em.create() call. Here is why the global EntityManager is blocked, and the two ways to fix it properly.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

TL;DR#

MikroORM 5 and 6 refuse to run identity-map methods (em.create(), em.find(), em.persistAndFlush()…) directly on the global EntityManager returned by MikroORM.init(). Either call .fork() to get a request-scoped EntityManager before you touch any entity, or use the RequestContext helper so the global instance resolves to a fork automatically. Setting allowGlobalContext: true silences the error but removes the safety check it exists for — keep it to unit tests.

The error#

text
ValidationError: Using global EntityManager instance methods for context specific actions is disallowed.
If you need to work with the global instance's identity map, use `allowGlobalContext` configuration option or `fork()` instead

It is thrown the first time code calls an identity-map method (em.create(), em.find(), em.persistAndFlush(), em.getReference()…) on orm.em — the instance MikroORM.init() returns — without forking it first. A minimal script that triggers it:

ts
const orm = await MikroORM.init(mikroConfig);
const post = orm.em.create(Post, { title: "my first post" }); // throws here
await orm.em.persistAndFlush(post);

This is the exact script from the Stack Overflow question that asks about it (25 votes, nearly 13,000 views): a project upgraded from MikroORM v4 to v5 and the first em.create() call started failing. Reproduced on MikroORM 6.6.17 (@mikro-orm/sqlite, Node 22) with the same script and the identical message.

Why it happens#

MikroORM keeps an identity map per EntityManager: as long as you query the same row twice through the same manager, you get back the same object instance, which lets the ORM batch operations and avoid loading duplicate data. That only works if each unit of work — in practice, each HTTP request — has its own identity map. Share one EntityManager across concurrent requests and two unrelated requests can read, mutate and flush entities that belong to each other.

Earlier MikroORM versions let you use the single, global em everywhere and trusted the application to isolate requests itself. Starting with MikroORM v5, the official docs on Identity Map and Request Context say plainly: "It is no longer possible to use the global identity map… using the global EM without request context is almost always wrong." The validation error is that rule enforced at runtime, the moment any code tries to use the global instance's identity map directly.

The error message itself names two ways out: fork() or the allowGlobalContext configuration option (also settable through the MIKRO_ORM_ALLOW_GLOBAL_CONTEXT environment variable).

Fix#

1. Fork the EntityManager before you touch any entity#

ts
const orm = await MikroORM.init(mikroConfig);
 
// one fork per unit of work — request, job, script run
const em = orm.em.fork();
 
const post = em.create(Post, { title: "my first post" });
await em.persistAndFlush(post);

fork() returns a new EntityManager with its own identity map and unit of work. It shares the underlying database connection with the global instance, so it costs nothing to create one per request. This is the right fix for a one-off script, a background job, or any code path that already has direct access to the EntityManager it should use — reproduced above for the create() call alone: swapping only orm.em.create(...) for orm.em.fork().create(...) ran without the error. The original Stack Overflow script also chains persistAndFlush() and a nativeInsert() after that line; keep using the same fork for those calls too, rather than falling back to orm.em, or the global instance will throw on them just as it did on create().

2. Or let RequestContext resolve the fork for you#

The problem with fix 1 inside a web framework is that dependency-injection containers (NestJS, InversifyJS) usually inject the same EntityManager instance everywhere, so there is no natural place to call fork() once per request. MikroORM's RequestContext helper solves that with Node's AsyncLocalStorage:

ts
import { RequestContext } from '@mikro-orm/core';
 
// register this middleware LAST, after body/query parsers,
// and before your route handlers
app.use((req, res, next) => {
  RequestContext.create(orm.em, next);
});
 
app.get('/', async (req, res) => {
  // orm.em.find() now transparently resolves to the fork
  // created by RequestContext.create() for this request
  const posts = await orm.em.find(Post, {});
  res.json(posts);
});

Internally, every identity-map method on the global em first calls em.getContext(), which checks whether a RequestContext is active for the current async execution and, if so, uses the fork it holds instead of the global instance. That is also why the check exists at all: it is the signal that tells you this wiring is missing. If you fork manually elsewhere in the same app and want that fork to also respect an active RequestContext, pass useContext: true to fork().

3. Only for tests: disable the check#

ts
const orm = await MikroORM.init({
  ...mikroConfig,
  allowGlobalContext: true, // or MIKRO_ORM_ALLOW_GLOBAL_CONTEXT=true
});

Reproduced: with this option set, the same orm.em.create(...) call that threw in fix-less code runs without error. The MikroORM maintainer's own answer on the Stack Overflow thread (73 upvotes) is explicit that this is meant for unit tests, not for an app serving real traffic — disabling the check brings back exactly the shared-instance bugs it was added to prevent: unrelated requests reading and flushing each other's entities, and the global em's identity map growing for the life of the process, which behaves like a memory leak under load.

Verify the fix#

  1. Remove any allowGlobalContext workaround from the config.
  2. Confirm every place that creates, finds or flushes an entity either received a forked EntityManager as an argument, or runs after RequestContext.create() has been registered as middleware.
  3. Re-run the code path that previously threw. The ValidationError naming the global instance should be gone; if a different error appears (a missing entity, a connection error), that is now a separate, unrelated problem.
  4. In a web app, add a quick regression test: fire two requests concurrently that each create an entity with the same field value, and assert that each response only sees its own entity — this is the exact failure mode the identity-map isolation is meant to prevent.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

Variants worth knowing about#

  • NestJS: the @mikro-orm/nestjs package wires RequestContext automatically through the MikroOrmMiddleware, so most NestJS projects never see this error unless a worker, a CLI script or a test helper calls the ORM outside an HTTP request — those call sites need their own fork().
  • CLI scripts and seed/migration code run outside any request, so there is no RequestContext to resolve. orm.em.fork() at the top of the script, once, is the fix — not allowGlobalContext.
  • GraphQL resolvers behind Apollo or similar servers run inside the request lifecycle just like REST routes, so the RequestContext middleware approach applies the same way; the common mistake is wiring it on the HTTP server but not on a separate WebSocket/subscription transport, which runs its own request cycle.

Treat the validation error as a design prompt, not an obstacle: it is pointing at exactly one place in the code that still touches the global EntityManager, and fixing that place — rather than muting the check — is what keeps each request's data isolated once the app has real concurrent traffic. A Node/TypeScript backend built around MikroORM typically runs inside Docker; if the container then crashes with Cannot find module once docker compose up mounts the app folder over the image's node_modules, the fix for a missing module in Docker Compose covers the bind-mount and anonymous-volume reuse that usually causes it. If the project has no @types/node installed, or a tsconfig types list that leaves node out, and tsc stops recognising globals like console, see the TS2584 fix for a missing @types/node. And if refactoring the entities into separate modules introduces a circular import, this ESM cycle ReferenceError walks through finding and breaking the loop.

Further reading: Fix Vite 'global is not defined': shim or move fs code.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote