Building Applications on MemberJunction
The short version — MemberJunction is best known as an AI data platform: unify your data, add intelligence, ship faster. But the engine that ingests and reasons over your data is also a first-class application development platform for database-driven business apps. You model a schema; MJ generates a complete, type-safe, secured full-stack application around it — typed data access, APIs, UI, business logic, and AI — all in one language (TypeScript) with one object model that runs identically on every tier. AI isn’t bolted on at the end: agents, prompts, and RAG are first-class building blocks that operate directly on the same entities and actions your UI uses, so the apps you build are AI-native by default. Once your data is in MJ, you build directly on it. You don’t move it somewhere else first.
This is the developer’s hub for that story. It explains the architecture, the model that makes it work, and links to the authoritative README/guide for every layer so you can go from “my data is in MJ” to “I’m shipping a custom application on MJ.”
Table of contents
Section titled “Table of contents”- Who this guide is for
- The one idea that matters most: one language, one object model, every tier
- The architecture, in layers
- The core idea: model the schema, get the application
- The build journey
- Layer-by-layer deep dive
- 1. Data modeling & schema
- 2. Code generation — the engine that ties it together
- 3. The entity framework — typed data access
- 4. Write once, run on every tier — the isomorphic core in practice
- 5. The API layer
- 6. Security, permissions & audit
- 7. The UI layer — MJExplorer, reusable components & your framework
- 8. Business logic — the Actions framework
- 9. AI — the force multiplier
- 10. Application metadata as code
- 11. Deployment
- Building AI-native applications
- How MJ compares to other frameworks
- Building on MJ & joining the community
- Cross-cutting developer guides
- A mental model to keep
Who this guide is for
Section titled “Who this guide is for”You’ve probably arrived one of two ways:
- You came for the data/AI platform. You pulled data in from other systems — CRM, accounting, a legacy database, spreadsheets — and now you’re wondering: can I actually build applications on top of this, or do I have to export it somewhere else? You build right here.
- You’re starting greenfield. You want a database-driven business application — a membership system, a CRM, a case-management tool, an internal ops console — and you want the data layer, API, security, audit, and UI handled for you so you can focus on what’s unique to your domain.
Either way, the rest of this guide shows you the layers MJ gives you and where to learn each one.
The one idea that matters most: one language, one object model, every tier
Section titled “The one idea that matters most: one language, one object model, every tier”If you take away nothing else, take this: MemberJunction lets you write your data and business logic once, in TypeScript, and run that exact same code on the server, in the browser, in a CLI, and inside background jobs and AI agents. This is rare, and it is the single biggest reason MJ apps are faster to build and cheaper to maintain than conventionally-architected apps.
Why this is uncommon — and powerful
Section titled “Why this is uncommon — and powerful”A typical database-driven web app forces you to redefine your domain several times, in several languages, across a tier boundary:
| Concern | Conventional stack | MemberJunction |
|---|---|---|
| Schema / migrations | SQL | SQL (migrations) |
| Server data model | ORM models (often a 2nd language/DSL) | BaseEntity subclass (TypeScript) |
| API contract | GraphQL SDL / OpenAPI / gRPC proto | Auto-generated from metadata |
| API serialization | Hand-written DTOs / serializers | Handled by the provider |
| Client data model | TS interfaces duplicating the server | The same BaseEntity subclass |
| Client API calls | fetch/axios/Apollo hooks | The same RunView / .Save() calls |
| Validation | Re-implemented per tier | One Validate() on the entity, runs everywhere |
Each row in the “conventional” column is a place where your model can drift, where a field rename breaks something silently, and where you write and test the same logic twice. MemberJunction collapses the whole column into one typed object model.
Quoting
@memberjunction/core’s own readme: “The package uses a provider model that allows the same application code to run transparently on different tiers. On the server, aSQLServerDataProvidercommunicates directly with the database. On the client, aGraphQLDataProviderroutes requests through the GraphQL API. Your code does not need to know which provider is active.”
How it works: the provider model
Section titled “How it works: the provider model”Metadata, BaseEntity, RunView, RunQuery, and TransactionGroup are tier-independent abstractions in @memberjunction/core. At application startup, you register one provider. That provider is the only thing that knows how to reach the data. Everything above it is identical regardless of tier.
flowchart TB
subgraph Same["Your code — written ONCE, identical on every tier"]
direction LR
MD["Metadata"]
BE["BaseEntity<br/>subclasses"]
RV["RunView /<br/>RunViews"]
RQ["RunQuery"]
TG["TransactionGroup"]
VAL["Validate()"]
end
subgraph Boot["Bootstrap registers ONE provider per process"]
direction LR
SP["SQLServerDataProvider"]
GP["GraphQLDataProvider"]
end
DB[("SQL Server / PostgreSQL")]
API["GraphQL API"]
Same --> SP
Same --> GP
SP -->|"Server, CLI,<br/>jobs, agents"| DB
GP -->|"Browser /<br/>Angular"| API
API --> DB
style Same fill:#2d8659,stroke:#1a5c3a,color:#fff
style Boot fill:#7c5295,stroke:#563a6b,color:#fff
style DB fill:#b8762f,stroke:#8a5722,color:#fff
style API fill:#2d6a9f,stroke:#1a4971,color:#fff
The payoff:
- Share real logic, not just types. A validation rule, a computed field, a “recalculate totals” method on an entity subclass is written once and is correct on the server and in the browser.
- No DTO/mapping layer. There is no translation step between “server object” and “client object” — they are the same class.
- Refactors are safe. Rename a field via CodeGen and TypeScript flags every call site across all tiers, because there’s only one set of types.
- One mental model for the whole team. Front-end and back-end developers use the same
GetEntityObject/RunView/.Save()vocabulary.
See §4 for concrete side-by-side code.
The architecture, in layers
Section titled “The architecture, in layers”MemberJunction is a layered stack. Each layer depends only on the ones below it, and the core object model layer is shared by everything above it on every tier.
flowchart TB
subgraph L7["⬢ Presentation — MJExplorer (Angular) · custom apps · CLI · MCP clients"]
direction LR
UI1["Generated & custom forms"]
UI2["Dashboards & grids"]
UI3["Generic component library"]
end
subgraph L6["⬢ AI & Automation — agents · prompts · vectors · actions · scheduling · comms"]
direction LR
AI1["Agent framework"]
AI2["Actions (business logic)"]
AI3["Communication & jobs"]
end
subgraph L5["⬢ API — GraphQL (auto-generated) · REST · MCP · A2A"]
direction LR
API1["MJServer"]
API2["API keys & scopes"]
end
subgraph L4["⬢ Core Object Model (TypeScript) — SHARED ON EVERY TIER"]
direction LR
C1["Metadata"]
C2["BaseEntity"]
C3["RunView / RunQuery"]
C4["TransactionGroup"]
end
subgraph L3["⬢ Providers — SQLServerDataProvider (server) · GraphQLDataProvider (client)"]
direction LR
P1["Server provider"]
P2["Client provider"]
end
subgraph L2["⬢ Generated Code — entity classes · views · stored procs · resolvers · forms"]
direction LR
G1["CodeGen output"]
end
subgraph L1["⬢ Data & Metadata — SQL Server / PostgreSQL + MJ metadata catalog"]
direction LR
D1["Your schema"]
D2["Entity / field / permission metadata"]
end
L7 --> L6 --> L5 --> L4 --> L3 --> L2 --> L1
style L7 fill:#2d6a9f,stroke:#1a4971,color:#fff
style L6 fill:#7c5295,stroke:#563a6b,color:#fff
style L5 fill:#2d8659,stroke:#1a5c3a,color:#fff
style L4 fill:#1f9d8f,stroke:#147068,color:#fff
style L3 fill:#9f6f2d,stroke:#71501f,color:#fff
style L2 fill:#b8762f,stroke:#8a5722,color:#fff
style L1 fill:#64748b,stroke:#475569,color:#fff
The teal Core Object Model layer is the one to internalize: it sits in the middle of the stack and is literally the same code whether the request originates from an Angular component or a server-side agent.
The core idea: model the schema, get the application
Section titled “The core idea: model the schema, get the application”MemberJunction is metadata-driven. You define (or import) a database schema, register the tables as entities in MJ’s metadata, and the platform generates and maintains everything downstream — and keeps it in sync as the schema evolves.
flowchart LR
SCHEMA[("Your DB schema")] --> META["MJ Metadata<br/>(Entities, Fields,<br/>Relationships,<br/>Permissions, Validation)"]
META -->|CodeGen| OUT
subgraph OUT["Generated & kept in sync"]
direction TB
O1["Typed entity classes (TS)<br/>validation · change tracking"]
O2["SQL views & stored procs<br/>RLS & audit baked in"]
O3["GraphQL API + typed client"]
O4["Angular forms, grids,<br/>dashboard scaffolding"]
O5["Action subclasses"]
end
style SCHEMA fill:#64748b,stroke:#475569,color:#fff
style META fill:#2d8659,stroke:#1a5c3a,color:#fff
style OUT fill:#2d6a9f,stroke:#1a4971,color:#fff
In practice: you write the code that’s genuinely unique to your business — custom forms, dashboards, actions, agents — and MJ handles the repetitive, error-prone plumbing every database app needs (CRUD, typed clients, API surface, row-level security, field permissions, audit trails, change tracking). All of it regenerates from metadata whenever the schema changes, so it never drifts.
The build journey
Section titled “The build journey”A typical “build an app on MJ” path, with the doc to read at each step:
flowchart LR
A["1. Model data<br/>migrations"] --> B["2. Run CodeGen<br/>generate the stack"]
B --> C["3. Work with data<br/>typed entities + RunView"]
C --> D["4. Expose & consume<br/>GraphQL + typed client"]
D --> E["5. Build UI<br/>forms · dashboards"]
E --> F["6. Add business logic<br/>Actions"]
F --> G["7. Layer in AI<br/>agents · prompts · RAG"]
G --> H["8. Config as code<br/>MetadataSync"]
H --> I["9. Deploy<br/>containers · migrations"]
style A fill:#64748b,stroke:#475569,color:#fff
style B fill:#b8762f,stroke:#8a5722,color:#fff
style C fill:#1f9d8f,stroke:#147068,color:#fff
style D fill:#2d8659,stroke:#1a5c3a,color:#fff
style E fill:#2d6a9f,stroke:#1a4971,color:#fff
style F fill:#7c5295,stroke:#563a6b,color:#fff
style G fill:#7c5295,stroke:#563a6b,color:#fff
style H fill:#9f6f2d,stroke:#71501f,color:#fff
style I fill:#64748b,stroke:#475569,color:#fff
| Step | What you do | Start here |
|---|---|---|
| 1. Model your data | Create/evolve tables via migrations; register them as entities | migrations/CLAUDE.md · Organic Keys |
| 2. Generate the stack | Run CodeGen to produce entities, views, sprocs, API, forms | CodeGenLib README |
| 3. Work with data in code | Use typed BaseEntity classes + RunView | MJCore readme · MJCoreEntities readme |
| 4. Expose & consume the API | GraphQL server + typed client provider | MJServer README · GraphQLDataProvider README |
| 5. Build the UI | Explorer shell, generated + custom forms, dashboards | Angular README · Dashboard Best Practices |
| 6. Add business logic | Actions for workflows, validation, integrations | Actions README |
| 7. Layer in AI | Agents, prompts, RAG over your now-unified data | AI README · Agents README |
| 8. Manage app metadata as code | Version your config/seed data declaratively | MetadataSync README · metadata/CLAUDE.md |
| 9. Deploy | Containers, migrations, environments | DEPLOYMENT.md |
Layer-by-layer deep dive
Section titled “Layer-by-layer deep dive”1. Data modeling & schema
Section titled “1. Data modeling & schema”Your application starts with a schema. MJ doesn’t hide SQL from you — it embraces it and layers metadata on top.
You own the schema — MJ never alters it behind your back
Section titled “You own the schema — MJ never alters it behind your back”A defining choice in MJ: the database is the source of truth for your schema, and you (DBA/developer) own all DDL. MJ performs no implicit schema changes. Instead, it introspects your database to discover tables, columns, keys, indexes, and relationships, reads the documentation already in the database (SQL Server extended properties / Postgres COMMENTs), and keeps its metadata layer in sync with what’s actually there. You’re free to use any column types, indexing strategy, schemas, computed columns, or vendor features you like — MJ adapts to your schema rather than dictating it.
Schema changes flow through explicit, versioned migrations applied by Skyway — MJ’s open-source, Flyway-compatible migration engine written in TypeScript. This is what makes MJ’s upgrade story deterministic, verifiable, and robust, and why it slots cleanly into CI/CD:
- Deterministic & repeatable. The same immutable, versioned migration files run in dev, staging, and production, in the same order, producing the same result. No “it worked on my environment” schema drift.
- Verifiable. Migrations are checksum-verified against the recorded history — if a previously-applied migration is altered, Skyway detects it and refuses to proceed, so tampering and accidental drift surface immediately instead of silently corrupting an environment.
- Atomic. Skyway’s per-run transaction mode wraps all pending migrations in a single transaction on databases with transactional DDL (SQL Server and PostgreSQL). A failed upgrade rolls back cleanly — you never end up with a half-applied schema.
- Reviewable as code. Because schema changes are DDL files in source control (not side effects of editing metadata in a UI), they go through normal pull-request review, diff cleanly, and are gated by the same CI as the rest of your code.
- In sync automatically. After a migration runs, CodeGen (see §2) introspects the change and regenerates the typed entities, base views, and CRUD procedures to match — so the application layer never drifts from the database.
The loop is: author DDL in a migration → Skyway applies it atomically → MJ introspects & syncs metadata → CodeGen regenerates the typed stack. Each step is explicit, versioned, and reproducible — the foundation for a robust CI/CD pipeline.
Two more freedoms worth knowing:
- UUID primary keys by default, but no fixed key shape. MJ defaults to
UNIQUEIDENTIFIER/ UUID PKs, yet — because it reads keys from your schema rather than mandating them — supports any primary-/foreign-key style, including composite and natural keys. (MJ normalizes UUID casing differences between SQL Server and PostgreSQL for you.) - Bring existing data as-is. Point CodeGen at existing tables and MJ registers them as entities — migrations are how you evolve the schema over time, not a precondition for building.
Modeling beyond plain tables: type inheritance & organic (soft) keys
Section titled “Modeling beyond plain tables: type inheritance & organic (soft) keys”Two metadata features matter especially when you’re ingesting data from third-party systems and want to layer metadata-driven and AI intelligence on top of it:
- IS-A type inheritance (table-per-type). A child entity can be a parent entity — it shares the parent’s primary key and inherits all of the parent’s fields (the generated view JOINs them in), while its own table holds only its unique columns. This models real hierarchies cleanly — e.g. a Webinar IS-A Meeting IS-A Product — and the inheritance is orchestrated in
BaseEntitywith no hand-written subclass code. It lets ingested or domain data be specialized without duplicating shared structure. See IS-A Relationships. - Organic keys (natural / “soft” keys). Relationships based on shared business data — email address, domain, tax ID, phone — rather than enforced foreign keys. This is the key to working with third-party data: records ingested from Mailchimp, QuickBooks, HubSpot, Salesforce, and the like arrive with their own IDs and no cross-system FKs, but they do share business values. Organic keys formalize that matching (with normalization, compound keys, and transitive joins through bridge views) so MJ can surface “related records” across integration boundaries — a contact’s campaigns, invoices, and tickets matched by email. Because the matches are first-class metadata, the platform — and your AI agents and RAG — can reason over these cross-system relationships, use them for retrieval and enrichment, and even surface likely duplicates. See Organic Keys.
Reference material
Section titled “Reference material”- migrations/CLAUDE.md — Authoring database migrations: naming conventions, hardcoded UUIDs, and what CodeGen adds automatically (timestamps, FK indexes) so you don’t.
- Skyway — The open-source, Flyway-compatible TypeScript migration engine that applies your migrations atomically and verifiably.
- IS-A Relationships & Organic Keys — Type inheritance and natural/soft-key matching (see above).
- Virtual Entities — Surface a view or external source as a first-class entity without a physical table.
- Soft Deletes Guide — Opt into
DeleteType='Soft'and get filtered views + soft-delete stored procedures managed for you. - Full-Text Search Guide — Native SQL full-text search wired into entities.
2. Code generation — the engine that ties it together
Section titled “2. Code generation — the engine that ties it together”CodeGen turns metadata into a working full-stack app and keeps everything synchronized as your schema evolves. One schema change fans out into every artifact your app needs:
flowchart TB
CG{{"mj codegen"}}
META[("MJ Metadata")] --> CG
CG --> E1["entity_subclasses.ts<br/>typed classes + Zod schemas"]
CG --> E2["SQL views<br/>(joins, computed fields)"]
CG --> E3["spCreate / spUpdate / spDelete<br/>+ permissions"]
CG --> E4["GraphQL resolvers"]
CG --> E5["Angular entity forms"]
CG --> E6["Action subclasses"]
style CG fill:#b8762f,stroke:#8a5722,color:#fff
style META fill:#2d8659,stroke:#1a5c3a,color:#fff
- CodeGenLib README — What CodeGen produces and how to run it.
- Multi-Database Workflow — Running CodeGen across multiple database targets/platforms.
- Never hand-edit generated files — regenerate instead (see the CodeGen rules in the root CLAUDE.md). This is what guarantees the layers never drift apart.
3. The entity framework — typed data access
Section titled “3. The entity framework — typed data access”This is how you read and write data in code, with full type safety, validation, dirty-tracking, and automatic audit. Three lines load, modify, and save any record — validated and audited:
import { Metadata } from '@memberjunction/core';import { CustomerEntity } from 'mj_generatedentities';
const md = new Metadata();const customer = await md.GetEntityObject<CustomerEntity>('Customers');await customer.Load(customerId);customer.Status = 'Active'; // strongly typed — IntelliSense + compile-time checksconst ok = await customer.Save(); // runs validation, writes audit trail, fires downstream eventsLoading collections uses RunView with a generic for full typing:
import { RunView } from '@memberjunction/core';
const rv = new RunView();const result = await rv.RunView<CustomerEntity>({ EntityName: 'Customers', ExtraFilter: `Status = 'Active'`, OrderBy: 'AnnualRevenue DESC', ResultType: 'entity_object'});if (result.Success) { for (const c of result.Results) { /* fully-typed CustomerEntity */ }}Read these before going deep:
- MJCore readme — The metadata engine,
Metadata,BaseEntity,RunView/RunViews, providers,TransactionGroup, and the core utilities every tier uses. - MJCoreEntities readme — The generated entity subclasses for MJ’s own metadata schema, and the pattern your app’s entities follow.
- BaseEntity Server-Side Patterns — Persisted embeddings, cross-record invariants via
ValidateAsync, FK cleanup before delete. - RunQuery Pagination & Keyset Pagination Guide — Efficient paging for grids and bulk jobs.
- Caching & Real-Time Sync Guide — The multi-tier cache, RunView cache behavior, and event-driven invalidation that keeps your UI live.
4. Write once, run on every tier — the isomorphic core in practice
Section titled “4. Write once, run on every tier — the isomorphic core in practice”This section makes the headline idea concrete. The code below is the same whether it runs in a Node.js server, an Angular component, a CLI command, or an AI agent. The only difference is which provider was registered at bootstrap.
Server-side (a resolver, action, or job — note contextUser for multi-user isolation):
const rv = new RunView();const result = await rv.RunView<InvoiceEntity>( { EntityName: 'Invoices', ExtraFilter: `Status='Open'`, ResultType: 'entity_object' }, contextUser // server passes the request's user; loaded entities carry it);for (const inv of result.Results) { inv.Status = 'Overdue'; await inv.Save(); // uses the entity's established user context}Client-side (an Angular component — same classes, same calls, no DTOs, no fetch):
const rv = new RunView();const result = await rv.RunView<InvoiceEntity>( { EntityName: 'Invoices', ExtraFilter: `Status='Open'`, ResultType: 'entity_object' }); // browser context user is implicitfor (const inv of result.Results) { inv.Status = 'Overdue'; await inv.Save();}Same InvoiceEntity class. Same RunView. Same .Save() with its validation, dirty-tracking, and audit. The server hits the database directly through SQLServerDataProvider; the browser routes through GraphQLDataProvider → the GraphQL API → the database. You wrote and tested the logic once.
Where this pays off most:
- Domain methods on entity subclasses. Put
RecalculateTotals()or aValidate()rule on yourInvoiceEntitysubclass and it’s enforced identically on both tiers — no client/server skew. - Background agents and the UI share logic. An AI agent that processes invoices and the screen a human uses to process them run the same code path.
- Migrating code between tiers is free. Logic that started client-side can move to a server action with zero rewrite.
Provider-tier docs:
- GraphQLDataProvider README — The typed client provider; the browser half of the model.
- SQLServerDataProvider README — The server provider that executes against the database directly.
One caution for multi-provider scenarios: in code that may run under a non-default provider (a client talking to multiple MJ servers, or per-request server contexts), use the provider you were handed (
this, an event’sprovider, or a passed parameter) rather than reaching for the globalnew Metadata(). See the “Don’t reach for the global Metadata provider” rules in the root CLAUDE.md.
5. The API layer
Section titled “5. The API layer”Every entity is automatically exposed through a secured, typed API — no resolver code to write.
flowchart LR
subgraph Clients
NG["Angular / browser"]
EX["External systems"]
MC["MCP / A2A clients"]
end
GQL["GraphQL API<br/>(MJServer)"]
AUTH["Auth: Auth0 / Azure AD / Okta<br/>+ API keys & scopes"]
PROV["SQLServerDataProvider"]
DB[("Database")]
NG --> GQL
EX --> GQL
MC --> GQL
GQL --> AUTH --> PROV --> DB
style GQL fill:#2d8659,stroke:#1a5c3a,color:#fff
style AUTH fill:#7c5295,stroke:#563a6b,color:#fff
style PROV fill:#9f6f2d,stroke:#71501f,color:#fff
style DB fill:#64748b,stroke:#475569,color:#fff
- MJServer README — The GraphQL API gateway: schema generation, authentication, query-depth limiting, scheduling hooks.
- GraphQLDataProvider README — The typed client so the same
BaseEntity/RunViewcode runs in the browser against the API. - APIKeys README — Programmatic access with hierarchical scopes for service-to-service and integration use cases.
6. Security, permissions & audit
Section titled “6. Security, permissions & audit”Security isn’t an add-on you wire up per screen — it’s generated into the data layer and enforced everywhere the object model runs.
- Row-level security & field permissions — defined in metadata, baked into generated views/stored procedures, enforced at the data layer regardless of tier.
- Audit trail / Record Changes — every
BaseEntity.Save()andDelete()is tracked automatically. You get built-in version history without writing any of it. See Entity Version Control in the root CLAUDE.md. - Authentication — Auth0, Azure AD (MSAL), and Okta supported out of the box via auth-services on the client and MJServer on the server.
- API authorization — APIKeys README for scoped, key-based access.
- Field-level encryption — Encryption README for AES-256 encryption of sensitive columns.
7. The UI layer — MJExplorer, reusable components & your framework of choice
Section titled “7. The UI layer — MJExplorer, reusable components & your framework of choice”MJ ships MJExplorer — a complete Angular application — as the default tool for exploring data, chat, dashboards, and administration. But “the default UI is Angular” doesn’t mean you’re locked into Angular:
- Most of Explorer is generic, reusable Angular. The large majority of what’s in Explorer is a library of generic Angular components (grids, viewers, dialogs, chat, filter builders, dashboards, and more) designed to be used in any Angular application — Explorer is largely a curated assembly of these reusable pieces, not a monolith. Drop them into your own Angular app.
- A React bridge lets teams build their UI in React while still using MJ component capabilities — see the React runtime.
- The TypeScript object model is frontend-agnostic.
Metadata,BaseEntity,RunView, and the GraphQL data provider are plain TypeScript with no UI-framework dependency, so they work in any TS-enabled environment — Vue, Next.js, Svelte, a Node service, or a CLI. You consume your data and business logic identically regardless of frontend.
So the UI story is: a great default app (Explorer) + reusable Angular components + a React bridge + a framework-agnostic TypeScript core. Because every surface speaks the same object model, components bind directly to typed entities.
- Angular README — Overview of the 60+ Angular packages: the Explorer app, the generic component library, bootstrap.
- Generic Angular component library — Reusable components usable in any Angular app (grids, viewers, dialogs, chat, filter builder, dashboards, and more).
- React runtime — The React bridge for building MJ UIs in React.
- Angular CLAUDE.md — Conventions: standalone vs NgModule, change detection, custom forms, naming, the MJ UI components.
- Explorer README — The Explorer application: shell, routing, generated entity forms, custom forms, lists, dashboards.
- MJExplorer README — The runnable Explorer app itself.
- Dashboard Best Practices — Building dashboards the MJ way: page chrome, state management, engine classes, permissions.
- Navigation and Routing Guide — URL state, deep links, sub-navigation.
- Lazy Loading Guide — Code-split your dashboards/features for fast startup.
- Theming & App Color Architecture — Design tokens, dark mode, white-labeling. No hardcoded colors.
8. Business logic — the Actions framework
Section titled “8. Business logic — the Actions framework”Actions are MJ’s metadata-driven unit of business logic: reusable, discoverable, and invocable by UI, workflows, schedules, agents, and external systems. They’re the seam where your domain logic becomes available to both humans and AI.
flowchart LR
subgraph Callers
U["UI buttons"]
S["Scheduled jobs"]
AG["AI agents"]
EXT["External systems"]
end
ACT["Action<br/>(metadata-defined)"]
LOGIC["Your TypeScript<br/>(uses BaseEntity, RunView,<br/>service classes directly)"]
U --> ACT
S --> ACT
AG --> ACT
EXT --> ACT
ACT --> LOGIC
style ACT fill:#7c5295,stroke:#563a6b,color:#fff
style LOGIC fill:#1f9d8f,stroke:#147068,color:#fff
- Actions README — The framework, core actions, and the BizApps integration actions (CRM, Accounting, LMS, Social, Form Builders).
- Actions CLAUDE.md — Authoring patterns, parameter validation, error handling, and when to use an Action vs. a direct class call (a critical design boundary — Actions are for integration edges, not internal code-to-code calls).
- Scheduling README — Run actions on cron schedules with distributed locking.
- Communication README — Multi-channel email/SMS (SendGrid, Gmail, MS Graph, Twilio) and entity-level messaging.
- Templates README — Nunjucks templating with optional AI-generated content for documents, emails, and more.
9. AI — the force multiplier
Section titled “9. AI — the force multiplier”This is where the “AI data platform” and “app dev platform” stories converge: because your data is unified in MJ entities, AI features have a clean, governed substrate to reason over — and agents invoke the very same Actions and entities your UI does.
- AI README — The full AI stack: provider abstraction (15+ providers), engine, prompts, vectors, agents.
- Agents README — Build autonomous, multi-step agents with sub-agent delegation that operate on your entities and actions.
- Prompts README — Hierarchical prompt templates, model selection, execution tracking.
- Search Scopes & RAG+ Guide — Retrieval-augmented generation over your data.
- Content Autotagging & Taxonomy & Tagging — Automatically classify and organize records.
10. Application metadata as code
Section titled “10. Application metadata as code”Beyond schema, your application configuration — apps, navigation, seed/reference data, agent and action definitions — is itself metadata you can version, diff, and deploy declaratively.
- MetadataSync README — Push/pull MJ metadata records as files; treat config as code.
- metadata/CLAUDE.md — Authoring metadata files:
@lookup/@file/@parentreferences, seeding lookup tables, defining applications and nav. - MJCLI README — The command line for codegen, metadata sync, and platform operations.
11. Deployment
Section titled “11. Deployment”- DEPLOYMENT.md — Containerized deployment, environment configuration.
- docker/CLAUDE.md — MJAPI container and workbench configurations.
- UPDATES.md & v5.0 Upgrade Guide — Promoting changes across dev/stage/prod and upgrading MJ versions.
Building AI-native applications
Section titled “Building AI-native applications”“AI-native” doesn’t mean “has a chatbot in the corner.” In MemberJunction it means AI is woven into the fabric of the application: the same entities, actions, and metadata that power your screens are the things your AI agents read, reason over, and act on. There’s no separate “AI integration layer” to build and keep in sync — because there’s no second model of your domain to integrate with.
Why MJ apps are AI-native by construction
Section titled “Why MJ apps are AI-native by construction”Everything an AI feature needs is already first-class in the platform:
- A governed, unified data substrate. Your data is already modeled as entities with relationships, permissions, and descriptions. That metadata is exactly what an LLM needs to query and manipulate data safely — and what powers RAG without a bespoke pipeline.
- Actions as a tool surface. The Actions framework is metadata-driven and discoverable, which is precisely the shape an agent needs to call your business logic. The action a user triggers from a button is the same action an agent invokes as a tool.
- The shared object model. An agent running server-side uses the same
BaseEntity/RunView/.Save()code your UI uses (§4). Human and agent paths can’t drift, because they’re literally the same code. - Provider independence. 15+ AI providers sit behind one abstraction, so you choose models per task without rewrites.
The AI-native loop
Section titled “The AI-native loop”flowchart LR
subgraph App["Your AI-native application"]
direction TB
USER["User"]
AGENT["AI Agent<br/>(plan · decide · act)"]
PROMPT["Prompts<br/>(hierarchical templates)"]
RAG["RAG / Vector search<br/>over your entities"]
end
subgraph Shared["Shared domain — same model for humans & AI"]
direction TB
ENT["Entities<br/>(BaseEntity)"]
ACT["Actions<br/>(business logic / tools)"]
DATA[("Your unified data")]
end
USER <--> AGENT
AGENT --> PROMPT
AGENT --> RAG
AGENT -->|"invokes as tools"| ACT
RAG --> ENT
ACT --> ENT
ENT --> DATA
style App fill:#7c5295,stroke:#563a6b,color:#fff
style Shared fill:#1f9d8f,stroke:#147068,color:#fff
style DATA fill:#64748b,stroke:#475569,color:#fff
Patterns for AI-native features
Section titled “Patterns for AI-native features”- Agentic workflows over your data — multi-step agents that read entities, call actions, and write results back, with sub-agent delegation. See Agents README.
- RAG grounded in your entities — semantic search and retrieval scoped to the records a user is allowed to see. See Search Scopes & RAG+ Guide.
- AI-assisted content — generate documents, emails, and summaries from entity data via the Templates engine and Prompts.
- Automatic classification & enrichment — autotagging and taxonomy assignment as records arrive. See Content Autotagging and Taxonomy & Tagging.
- In-app AI surfaces — embed chat, agent test harnesses, and conversation UIs from the Angular generic component library (
@memberjunction/ng-conversations,@memberjunction/ng-ai-test-harness).
The throughline: because AI shares your app’s object model and action surface, adding intelligence is additive, not architectural. You don’t re-platform to become AI-native — you already are.
How MJ compares to other frameworks
Section titled “How MJ compares to other frameworks”If you’re weighing MemberJunction against the stacks you already know — Next.js/Vercel, Supabase, Ruby on Rails, Django, or a hand-rolled Node + ORM + SPA — see the companion Framework Comparison Guide. It’s an objective, where-each-shines breakdown: what MJ gives you out of the box (typed isomorphic object model, generated API/UI/security/audit, AI-native substrate), where the others are a better fit, and how to think about the trade-offs.
Building on MJ & joining the community
Section titled “Building on MJ & joining the community”MemberJunction is open source (ISC), and there are two complementary ways to engage with it — building on the platform, and helping shape the platform itself.
Build your application on MJ
Section titled “Build your application on MJ”You’re encouraged to build on MJ, whether your app is commercial or open source. The platform gives you the data layer, API, security, UI, Actions, and AI; you bring the domain. Several open-source apps in the MemberJunction org show this in practice and are worth studying as references (and reusing):
- BizApps Common — a production-ready, schema-complete, fully-typed set of foundational business entities (people, organizations, addresses, relationships) packaged as an MJ Open App, so applications share these core entities instead of reinventing them.
- BizApps Tasks — a complete, reusable task-management system as an MJ Open App: multi-person assignment, sub-task hierarchies, dependency tracking, and templating that any MJ app can integrate without building its own.
- Committees — a governance platform built on MJ that unifies committee structure, membership, meetings, motions and voting, action tracking, and document linking.
These are real, working examples of the patterns in this guide — clone them, read how they’re structured, and build on or alongside them.
Help shape the platform
Section titled “Help shape the platform”If you want to extend the framework itself, the best first step isn’t to fork in isolation — it’s to plug into the community, where your ideas can land in the platform and benefit everyone:
- Join the community and get active in Discussions — ask questions, share what you’re building, and float ideas early.
- File Issues for bugs and feature requests.
- Raise pull requests with fixes, new features, ideas, and plugins — see
CONTRIBUTING.mdfor setup, coding standards, and the PR process.
And of course, because MJ is open source, you can always read, fork, and extend the platform directly. But contributing back through the community means your extensions are maintained with the project rather than diverging from it.
Cross-cutting developer guides
Section titled “Cross-cutting developer guides”These apply no matter which layer you’re working in. The guides/ index is the full list; the essentials:
- UUID Comparison Guide — Always use
UUIDsEqual(); SQL Server and PostgreSQL case UUIDs differently. - Caching & Real-Time Synchronization Guide — Server cache + reactive
BaseEnginepatterns for live UIs. - BaseEntity Server-Side Patterns — The right way to extend entities on the server.
- The foundational CLAUDE.md at the repo root — coding standards, type-safety rules, naming conventions, and the architectural guardrails that keep an MJ app maintainable.
A mental model to keep
Section titled “A mental model to keep”MJ as a data platform ingests and unifies your data and makes it intelligent. MJ as an app platform lets you build the applications your business runs on, directly on that unified data — in one language, with one object model, on every tier — without rebuilding the data layer, API, security, audit, or UI from scratch every time.
They’re the same platform. The data you brought in is the foundation; everything in this guide is what you build on top of it — and the fact that you build it once and run it everywhere is what makes MJ fast.
For the bird’s-eye view of all 175 packages, see the packages/ directory overview and the package directory in the root README.