Upgrading to v6
MemberJunction 6.x is live on the Edge channel. This guide covers everything a 5.x
deployment or codebase needs to know to move to the 6.x line — current as of
6.1.0-edge.0, and maintained as the Edge stream advances.
The short version: 6.x opened a new release era, not a platform rewrite. The core API
surface — RunView, BaseEntity, the provider interfaces, @memberjunction/core’s entire
export list — is byte-for-byte identical to 5.51.0, and Angular, Node, TypeScript, zod, and
rxjs floors did not move. There is a short list of real breaking changes, documented below;
most upgrades will only feel one or two of them.
License change — read this first
Section titled “License change — read this first”6.x is the first MemberJunction line released under the Business Source License 1.1. 5.x and every version before it were released under the ISC License and remain ISC-licensed forever. Upgrading is what changes your terms; nothing about an existing 5.x installation changes.
The BUSL grants the following without any further agreement:
- Internal use. You may run MemberJunction in production for your own business or organizational operations.
- Nonprofit use. A nonprofit — 501(c)(3), (c)(4), (c)(5), or (c)(6), or a foreign organization recognized under substantially equivalent laws — may run it in production for the operations and activities of its Organizational Family.
- Non-production use. Development, testing, and evaluation are unrestricted for everyone.
- Change Date. Four years after a given version is first made available, that version converts to the MIT License.
What now requires certification is providing professional services to clients on MemberJunction — implementation, integration, customization, or consulting performed in a client’s environment on their behalf. That runs through the MemberJunction Certified Program.
The controlling terms, including the definitions that govern the Nonprofit grant and the documentation a
nonprofit may be asked to provide, are in LICENSE. If your usage doesn’t fit the grants
above, staying on the 5.x LTS line keeps you on ISC.
Understanding the 6.x version scheme
Section titled “Understanding the 6.x version scheme”Five facts up front, because the numbering is the part most likely to confuse a 5.x user:
- There is no 6.0.0. It was a never-published internal baseline; npm goes straight from
5.51.0to6.1.0-edge.0. Don’t go looking for it. lateststill points at the certified 5.x line (5.51.0 today). It moves only when a line is certified — never for an Edge release.- Edge builds publish under the
edgedist-tag:npm install @memberjunction/core@edge. - Ordinary semver ranges never auto-resolve an Edge build.
^5.51.0— and even^6.0.0— will not pull6.1.0-edge.N, because npm excludes prereleases from range matching. Your existing installation cannot drift onto 6.x by accident; upgrading is always an explicit opt-in. - Docker
:latesttracks certified builds only. Edge releases publish no image.
Before you upgrade — checklist
Section titled “Before you upgrade — checklist”Work through these before installing 6.x. Each links to its detail section below.
- Do you import vendor connector classes from
@memberjunction/integration-connectors, or haveCompanyIntegrationrows for those vendors? → Connectors - Does anything read
ActionExecutionLog.Paramsto see an action’s outputs? → ActionExecutionLog - Do you call
AIEngine.FindSimilarAgents/FindSimilarActionsor the agent/action embedding services directly from TypeScript? → AIEngine - Do any stored MJ Queries, view filters, or user expressions reference
INFORMATION_SCHEMA,sys.*, orpg_catalog? → System catalogs - Does your identity provider sign tokens with HS256 (a shared secret) rather than RS256/ES256? → JWT algorithms
- Do you use negation-form (
<>,NOT IN,NOT LIKE) row-level-security filters? → RLS filters
If all six answers are “no,” your upgrade is: install the @edge versions, run the
database migrations, run mj sync push then mj codegen, restart MJAPI. Done.
Breaking changes
Section titled “Breaking changes”The vendor connectors moved to Open Apps
Section titled “The vendor connectors moved to Open Apps”The single biggest change in 6.x. @memberjunction/integration-connectors was emptied
down to its three shared base classes (BaseExternalDataSourceConnector,
BaseSqlExternalDataSourceConnector, BaseDocumentDataSourceConnector). The 36 vendor
connectors — Salesforce, HubSpot, Mailchimp, QuickBooks, Blackbaud, NetSuite, iMIS, and the
rest — now ship from the MemberJunction/Integrations
repository as one Open App per connector, versioned independently of the MJ release train.
Note the package itself was not removed or unpublished — it still exists at 6.1.0-edge.0
with the base classes six shipped Open Apps depend on. What’s gone is the vendor connector
classes inside it.
Who feels it, two ways:
- Build time: any direct
import { SalesforceConnector } from '@memberjunction/integration-connectors'is now a compile error. - Runtime — the quieter one: existing
CompanyIntegrationrows for these vendors still point at a package that no longer contains the class. Nothing fails at build time; the integration fails to resolve when it runs.
Migration:
- Install the Open App for each connector you use (
@memberjunction/connector-<vendor>). - Re-point direct imports at the connector’s own package.
- Registration keys changed from the bare class symbol to the npm package name — see
packages/Integration/docs/connector-development.md. - Each Open App’s seed migration rewrites the same
__mj.Integrationrow (same ID) with the newClassName/ImportPath, so existingCompanyIntegrationrecords keep working.
⚠️ Known gap — seven integrations do not cleanly re-point yet. The same-ID re-point covers 16 of the 24 monorepo-seeded integrations. For Mailchimp, Blackbaud, HubSpot, MagnetMail, and Wild Apricot, the Open App seeds a different row ID with a colliding name, so installing the connector fails on the
Integrationname unique constraint until the pre-existing row is renamed or removed. For Constant Contact the install succeeds but leaves the original row dangling alongside a new one, and File Feed has no Open App seed migration at all. If you use any of these seven, plan manual row cleanup as part of the upgrade — this is a known, not-yet-resolved gap upstream, not something you’re doing wrong.
ActionExecutionLog.Params now holds inputs only
Section titled “ActionExecutionLog.Params now holds inputs only”The column was repurposed: it previously held the final merged parameter set (inputs plus
whatever outputs the action appended); it now holds the as-called inputs, captured at the
top of RunAction so all exit paths record the same values. The merged set moved to the new
ResultParams column, which is written on failure exactly as on success — NULL now means
precisely “the run never finished.”
This is the one 6.x change most likely to produce wrong numbers rather than an error:
any dashboard, report, or saved Query reading Params for an action’s outputs will
silently get inputs instead.
Migration: re-point output-readers at ResultParams. Input-readers need no change and
are now more accurate than before.
AIEngine similarity APIs were replaced
Section titled “AIEngine similarity APIs were replaced”AIEngine.FindSimilarAgents, FindSimilarActions, RefreshAgentEmbeddings,
RefreshActionEmbeddings, and the AgentVectorService/ActionVectorService getters were
removed, along with the AgentEmbeddingService/ActionEmbeddingService modules. Direct
TypeScript callers get a compile error.
Migration: use the unified search pipeline —
Provider.SearchEntity({ entityName: 'MJ: AI Agents' /* or 'MJ: Actions' */, ... })Metadata-driven callers need no change: the five core actions built on the old paths (“Find Best Action”, “Find Candidate Actions”, “Find Best Agent”, “Find Candidate Agents”, “Search Query Catalog”) were rewritten as backward-compatible wrappers with identical parameters and output shapes. Query embeddings are now populated automatically by the daily Entity Vector Sync job.
System catalog references are now rejected
Section titled “System catalog references are now rejected”The SQL expression validator now blocks references to database system catalogs in all user-supplied SQL contexts — stored MJ Queries, view filters, ad-hoc expressions:
- SQL Server:
sys.*,INFORMATION_SCHEMA,syslogins - PostgreSQL:
pg_catalog.*,pg_authid/pg_shadow/pg_user/pg_roles
Queries that previously validated and ran — schema-explorer helpers, “list all columns”
admin tooling — now fail validation with “Access to database system catalogs / metadata
objects is not allowed.” String literals are stripped before the check, so a literal value
containing sys. is safe; only real object references trip it.
Migration: audit stored Queries for catalog references before upgrading, and replace
catalog introspection with MJ’s own metadata (Metadata.Entities, EntityInfo.Fields),
which respects the entity-permission model. There is deliberately no opt-out: these objects
sit outside MJ’s permission model.
JWT signature algorithms are now pinned
Section titled “JWT signature algorithms are now pinned”Token verification (MJServer and the MCP server) now accepts only asymmetric algorithms:
RS256, RS384, RS512, ES256, ES384, ES512, PS256. Previously every algorithm the JWT
library understood was accepted, including symmetric HMAC.
Who feels it: only deployments whose identity provider issues HS256 (shared-secret) tokens — those now fail closed. Standard OIDC providers (Auth0, Microsoft Entra, Okta) sign RS256 and are unaffected.
Migration: decode a live token and check its alg header before upgrading. If it’s
HS256, reconfigure the issuer to an asymmetric algorithm. The list is hardcoded by design
(it forecloses alg=none and RS256→HS256 confusion attacks); there is no configuration
escape hatch.
Negation-form RLS filters now match fewer rows
Section titled “Negation-form RLS filters now match fewer rows”Row-level-security filter substitution had two permissive bugs: undefined user properties
substituted as the literal string "undefined", and embedded quotes weren’t escaped. Both
are fixed. Equality-style filters are unaffected, but filters written in negation form
(<>, NOT IN, NOT LIKE) against a sometimes-undefined user property previously matched
more rows than intended — and now correctly match fewer.
Migration: none required — but tell your users. “I see fewer rows after the upgrade” is the fix working, not a regression. Audit negation-form filters in advance if you need to know the blast radius.
Top-level Flow agents run on the task-graph dispatcher
Section titled “Top-level Flow agents run on the task-graph dispatcher”Since 6.1.0 a Flow agent compiles to a task graph, and the durable task-graph dispatcher runs its
steps. A top-level run submits the graph and returns before any step has executed. It parks as
Paused, and the dispatcher completes it when the graph settles. That keeps a workflow alive
across page reloads and server restarts.
A caller that needs the workflow’s result has to run it in-process instead:
- Sub-agent runs do this automatically from 6.1.3. Any Flow agent run with a
parentRunwalks its steps in the calling process and returns its final payload, as in 5.x. On 6.1.0 to 6.1.2 these runs failed at once with:‘<agent>’ is a workflow, and a workflow cannot be used as a sub-agent step yet. It returns as soon as its steps are scheduled, so the calling agent would continue before any of the work had happened.
- Top-level callers that use the payload must opt in. An API handler, a script or a test that
reads
result.payloadfrom a Flow agent run should passagentTypeParams: { executionMode: 'inRun' }(FlowAgentExecuteParams). Without it, the run returns before the flow has produced anything. startAtStepneeds in-process execution. A dispatched run refuses it with a message that says so.- Scheduled jobs still refuse Flow agents. A scheduled job cannot target a Flow agent yet.
Each Flow agent run records which way it ran as a Decision step, “Workflow runs in this run” or
“Workflow runs on the task-graph dispatcher”, with the reason.
One path-selection change applies to both modes. A path whose destination step is not
Active is no longer followed. A flow whose only satisfied path leads to a disabled step now
finishes with Success, where 5.x failed with “No active steps found”.
Migration: add executionMode: 'inRun' to any top-level Flow agent call whose result you use.
Sub-agent Flow agents need no change on 6.1.3 or later. See the
Workflows and Task Graphs Guide.
Value-list fields are validated on save
Section titled “Value-list fields are validated on save”Since 6.1.0, BaseEntity.Validate() refuses a save when a field whose ValueListType is List
holds a value that is not in the field’s value list (PR #3972). On 5.x only the database decided,
and CodeGen emits an IN (...) CHECK constraint for some of these fields but not all, so a row
whose List field held a comma-separated multi-select or free-text value saved fine before the
upgrade and is refused after it:
Phase must be one of: Discovery, Design, Build, Test, Launch. Current value is ‘Design, Build’
Nothing warns at upgrade time. The failure shows up later, on the first save of an affected row, and it is easy to miss the connection to the upgrade. One consumer found 5 of 15 production rows affected on one field.
Migration: for each field where existing data legitimately holds values outside the list,
change the field’s ValueListType from List to ListOrUserEntry. The list stays as UI
suggestions and the validation rung no longer applies (it only applies to List):
UPDATE [__mj].[EntityField]SET [ValueListType] = 'ListOrUserEntry'WHERE [Entity] = '<entity name>' AND [Name] = '<field name>' AND [ValueListType] = 'List';then restart MJAPI so the metadata cache picks it up. To find affected rows before you upgrade,
compare each List field’s column against its EntityFieldValue rows; a scan tool for this is
tracked for 6.2.
ng-filter-builder renamed two public exports
Section titled “ng-filter-builder renamed two public exports”@memberjunction/ng-filter-builder renamed createEmptyFilter to CreateEmptyFilter and
isCompositeFilter to IsCompositeFilter in 6.1.0, to match the PascalCase convention for
exported functions. Consumers that import the old names fail at build time with No matching
export. Published @mj-biz-apps/sonar-ng 0.4.1 to 0.6.0 import the old names, and 0.6.0’s peer
range names a 6.1.0-edge prerelease, so it accepts 6.1.x stable and breaks only when you build.
Migration: rename the imports. If you consume sonar-ng, wait for a release built against
6.1.x stable, or disable its client entries until then.
Angular peer ranges moved to ^21.2.22
Section titled “Angular peer ranges moved to ^21.2.22”Every @memberjunction/ng-* package peered Angular at exactly 21.1.3 in 5.51.x. On 6.1 they
peer ^21.2.22. An app that pins Angular to exactly 21.1.3 gets a hard ERESOLVE failure on
install. Angular 21.2 also rejects asset paths outside the workspace root
(../../node_modules/.../assets), which npm workspaces that install at the root sometimes rely
on; pnpm-linked apps such as MJ Explorer are not affected.
Migration: move your Angular pin to ^21.2.22 and check angular.json asset paths that
climb out of the project.
Minor: ElevenLabs realtime session initiation
Section titled “Minor: ElevenLabs realtime session initiation”ElevenLabsRealtimeSession.SendInitiation now takes the wire-shaped overrides object rather
than a system-prompt string, and PromptOverrideEnabled is deprecated in favor of
OverridesSatisfied. The class is driver-constructed and essentially never instantiated by
consumers — listed for completeness.
For contributors — building MJ from source
Section titled “For contributors — building MJ from source”The monorepo is on pnpm
Section titled “The monorepo is on pnpm”As of 6.x, the MJ repo is a pnpm workspace (packageManager pins pnpm; the lockfile is
pnpm-lock.yaml; there is no package-lock.json). Never run npm install in the repo —
it would write a lockfile the repo no longer uses and resolve a materially different tree.
corepack enable # or: npm i -g pnpmpnpm install # at the repository ROOT, never inside a packagepnpm run buildDeclare every import: pnpm’s strict linking gives a package only what its package.json
declares — no more silently borrowing a hoisted copy. (The cutover itself surfaced and fixed
fifteen packages’ worth of exactly that bug class.)
Consumers are unaffected — published package contents and npm/yarn installs of MJ packages are identical. This is purely a build-time change for source checkouts.
CI gates got stricter
Section titled “CI gates got stricter”PRs that passed at 5.51.0 can fail now: spec files are type-checked before running
(test:types), DOM-spec placement and anti-patterns are linted, Explorer DOM coverage has a
floor, and every SQL Server migration change requires its PostgreSQL counterpart — both
existence and content are CI-enforced. The adopted-standards checks now ship as
@memberjunction/standards with mj standards adopt|check|list.
Database migrations
Section titled “Database migrations”Seven migrations, each with a PostgreSQL counterpart. None are destructive — the changes are additive columns, nullable FKs, a widened CHECK constraint, view/procedure regeneration, and metadata syncs.
Run order matters:
npx mj migrate --verbose # apply the 6.x migrationsnpx mj sync push # push metadata BEFORE codegen (standing rule)npx mj codegen # regenerate — required after the EntityAction migration# then restart MJAPI — its boot-time caches don't re-read liveBehavior changes worth knowing (no action required)
Section titled “Behavior changes worth knowing (no action required)”- The metadata cache now actually refreshes every 3 minutes. A units bug had the refresh interval at roughly 50 hours — if you’ve been restarting MJAPI to pick up metadata changes, that ritual is now unnecessary.
- Push-subscription hijack fixed:
statusUpdatessubscriptions previously filtered only on a client-supplied session id without checking it against the authenticated subscriber; they now fail closed. No operator action needed. - Additive features you can ignore until you want them: layered base views (SQL Server via
sp_refreshview; PostgreSQL restars the outer view after inner regeneration; opt-in per entity), per-verb direct-SQL flags (default off), API-key row-filter columns (enforcement lands later), a pluggable search-scope permission resolver, and mobile records UX below 768px.
What did NOT change
Section titled “What did NOT change”Verified by diffing the v5.51.0 and v6.1.0-edge.0 tags directly:
| Concern | Status |
|---|---|
@memberjunction/core export surface | Byte-identical |
RunView / RunViews / BaseEntity / provider interfaces | Byte-identical |
| Node floor | >=20.0.0, unchanged |
| TypeScript / zod / rxjs | unchanged (5.9.x / ^3.25.0 / ^7.8.2) |
| Published package inventory | none removed (one added: @memberjunction/standards) |
mj.config.cjs / install.config.json shape | no diff |
Entity naming (MJ: prefix) | unchanged |
If a concern of yours isn’t listed anywhere in this document, it most likely didn’t change — the 6.x era open was a release-process milestone (Edge/LTS channels, the era model) far more than a platform break.