A library for synchronizing MemberJunction database metadata with local file system representations. This library is integrated into the MemberJunction CLI (mj
) and is accessed through mj sync
commands. It enables developers and non-technical users to manage MJ metadata using their preferred editors and version control systems while maintaining the database as the source of truth.
MetadataSync is included with the MemberJunction CLI. Install the CLI globally:
npm install -g @memberjunction/cli
Then use the sync commands:
mj sync --help
MemberJunction is a powerful metadata-driven system where configuration, business logic, AI prompts, templates, and more are stored as metadata in the database. This approach provides tremendous flexibility and runtime configurability, but it can create friction in modern development workflows.
For Developers:
For Non-Technical Users:
For Organizations:
This tool preserves the power of MJ's metadata-driven architecture while adding the convenience of file-based workflows. The database remains the source of truth for runtime operations, while files become the medium for creation, editing, and deployment.
The Metadata Sync tool bridges the gap between database-stored metadata and file-based workflows by:
@file:filename.ext
to link external files from JSON@parent:
and @root:
to reference parent/root entity fields-v
) for detailed outputThe tool works with any MemberJunction entity - both core system entities and user-created entities. Each entity type can have its own directory structure, file naming conventions, and related entity configurations.
This tool should NOT be used to modify metadata that is reflected from the underlying database catalog. Examples include:
These properties are designed to flow from the database catalog up into MJ metadata, not the other way around. Attempting to modify these via file sync could create inconsistencies between the metadata and actual database schema.
The tool is intended for managing business-level metadata such as:
For more information about how CodeGen reflects system-level data from the database into the MJ metadata layer, see the CodeGen documentation.
Before creating entity JSON files, follow this checklist to avoid common mistakes:
✅ 1. Find the Entity Definition
packages/MJCoreEntities/src/generated/entity_subclasses.ts
or packages/GeneratedEntities/src/generated/entity_subclasses.ts
class [EntityName]Entity
(e.g., class TemplateEntity
)✅ 2. Check Required Fields
@required
annotations?
in TypeScript definitions are typically requiredName
(almost always required)UserID
(use System User ID: ECAFCCEC-6A37-EF11-86D4-000D3A4E707E
)✅ 3. Validate Field Names
Status
)✅ 4. Use Correct File Naming
filePattern
in your .mj-sync.json"filePattern": "*.json"
(matches any .json file)"filePattern": ".*.json"
(matches dot-prefixed .json files)✅ 5. Set Up Directory Structure
.mj-sync.json
in the entity directory"filePattern": "*.json"
(not regex: ".*.json"
)CRITICAL: Before creating entity files, you must understand the entity's field structure. Most errors occur because users are unfamiliar with the required fields, data types, and constraints.
The approach depends on whether you're working inside or outside the MemberJunction monorepo:
Entity classes are located in:
Core MJ Entities: packages/MJCoreEntities/src/generated/entity_subclasses.ts
Custom Entities: packages/GeneratedEntities/src/generated/entity_subclasses.ts
Entity classes are located in:
Core MJ Entities: node_modules/@memberjunction/core-entities/dist/generated/entity_subclasses.js
node_modules/@memberjunction/core-entities/dist/generated/entity_subclasses.d.ts
Custom Entities: Your project's generated entities location (varies by project structure)
src/generated/
, packages/entities/
, or similarRecommended approach for all scenarios:
Import the entity class in your IDE:
import { TemplateEntity } from '@memberjunction/core-entities';
Create an instance and explore with IntelliSense:
const template = new TemplateEntity();
// Type "template." and let your IDE show available properties
Check the class definition (F12 or "Go to Definition") to see:
Use IDE IntelliSense (Recommended):
const entity = new TemplateEntity();
Examine the BaseEntity Class:
class TemplateEntity
)Runtime Metadata Discovery:
import { Metadata } from '@memberjunction/core';
const md = new Metadata();
const entityInfo = md.EntityByName('Templates');
console.log('Required fields:', entityInfo.Fields.filter(f => !f.AllowsNull));
// BaseEntity class (accessible via IDE IntelliSense)
export class TemplateEntity extends BaseEntity {
/**
* Primary key - auto-generated GUID
*/
ID: string;
/**
* Template name - REQUIRED
* @required
*/
Name: string;
/**
* Template description - optional
*/
Description?: string;
/**
* User who created this template - REQUIRED
* Must be a valid User ID
* @required
* @foreignKey Users.ID
*/
UserID: string;
/**
* Category for organizing templates - optional
* @foreignKey TemplateCategories.ID
*/
CategoryID?: string;
// Note: Status field may not exist on all entities!
}
Most MJ entities follow these patterns:
Always Required:
ID
- Primary key (GUID) - auto-generated if not providedName
- Human-readable nameUserID
- Creator/owner (use System User: ECAFCCEC-6A37-EF11-86D4-000D3A4E707E
)Often Required:
Description
- Usually optional but recommendedID
- Check if they have .optional()
Be Careful With:
Status
fields - Some entities have them, others don't2024-01-15T10:30:00Z
{
"fields": {
"Name": "My Template",
"Status": "Active" // ❌ Templates entity may not have Status field
}
}
✅ Solution: Check the BaseEntity class first
// In entity_subclasses.ts - if you don't see Status here, don't use it
export class TemplateEntity extends BaseEntity {
Name: string; // Required
Description?: string; // Optional (note the ?)
// No Status field defined
}
{
"fields": {
"Name": "My Template"
// ❌ Missing required UserID
}
}
✅ Solution: Include all required fields
{
"fields": {
"Name": "My Template",
"UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E"
}
}
{
"entity": "Templates",
"filePattern": ".*.json" // ❌ This is regex, not glob
}
✅ Solution: Use glob patterns
{
"entity": "Templates",
"filePattern": "*.json" // ✅ Correct glob pattern
}
{
"fields": {
"Name": "My Template",
"CreatedAt": "2024-01-15", // ❌ Wrong datetime format
"Priority": "1" // ❌ Should be number, not string
}
}
✅ Solution: Use correct data types
{
"fields": {
"Name": "My Template",
"CreatedAt": "2024-01-15T10:30:00Z", // ✅ ISO format
"Priority": 1 // ✅ Number type
}
}
mydir/
├── .mj-sync.json (with "filePattern": "*.json")
├── template1.txt // ❌ Wrong extension
└── .template2.json // ❌ Dot prefix when pattern is "*.json"
✅ Solution: Match your filePattern
mydir/
├── .mj-sync.json (with "filePattern": "*.json")
├── template1.json // ✅ Matches *.json pattern
└── template2.json // ✅ Matches *.json pattern
# Open in your IDE:
packages/MJCoreEntities/src/generated/entity_subclasses.ts
# Search for your entity class (Ctrl+F):
class TemplateEntity
# Note the required vs optional fields:
Name: string; // Required (no ?)
UserID: string; // Required (no ?)
Description?: string; // Optional (note the ?)
mkdir templates
cd templates
# Create entity config (dot-prefixed configuration file)
echo '{
"entity": "Templates",
"filePattern": "*.json"
}' > .mj-sync.json
# Create metadata file (follows filePattern from .mj-sync.json)
echo '{
"fields": {
"Name": "My First Template",
"Description": "A test template",
"UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E"
}
}' > my-first-template.json
# Dry run to check for errors
mj sync push --dir="templates" --dry-run
# If successful, do actual push
mj sync push --dir="templates"
When using AI tools (like Claude, ChatGPT, etc.) to generate entity files:
🤖 For AI Assistants:
ECAFCCEC-6A37-EF11-86D4-000D3A4E707E
) for UserID fields📝 Prompt Template for AI:
I need to create entity files for the [EntityName] entity in MemberJunction.
Please:
1. First, check the entity definition in packages/MJCoreEntities/src/generated/entity_subclasses.ts
2. Find the class [EntityName]Entity (e.g., class TemplateEntity)
3. Review JSDoc comments and property definitions to identify required vs optional fields
4. Create a .mj-sync.json file with correct glob pattern
5. Create sample metadata JSON files following the filePattern
6. Use UserID: "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E" for required UserID fields
7. Follow the exact field names and data types from the BaseEntity class definition
CRITICAL: Configuration files (.mj-sync.json) must start with dot, but metadata files follow the filePattern specified in the configuration.
Configuration Files (Always Dot-Prefixed):
.mj-sync.json
- Entity configuration.mj-folder.json
- Folder defaultsmj-sync.json
- Won't be recognizedMetadata Files (Follow filePattern):
With "filePattern": "*.json"
:
my-template.json
- Will be processedgreeting.json
- Will be processed .my-template.json
- Won't match patternpackage.json
- Will be ignored (add to ignore list if needed)With "filePattern": ".*.json"
:
.my-template.json
- Will be processed.greeting.json
- Will be processedmy-template.json
- Won't match patternpackage.json
- Won't match patternError Message | Cause | Solution |
---|---|---|
No entity directories found |
Missing .mj-sync.json or wrong filePattern | Check .mj-sync.json exists and uses "*.json" |
Field 'X' does not exist on entity 'Y' |
Using non-existent field | Check BaseEntity class in entity_subclasses.ts |
User ID cannot be null |
Missing required UserID | Add "UserID": "ECAFCCEC-6A37-EF11-86D4-000D3A4E707E" |
Processing 0 records |
Files don't match filePattern | Check files match pattern in .mj-sync.json |
Failed validation | Wrong data type or format | Check BaseEntity class for field types |
Always use this GUID for UserID fields:
ECAFCCEC-6A37-EF11-86D4-000D3A4E707E
This is the System User ID that should be used when creating entity records through the MetadataSync tool. Using any other ID or leaving it null will cause validation errors.
The tool uses a hierarchical directory structure with cascading defaults:
.mj-sync.json
files define entities and base defaults.mj-folder.json
files define folder-specific defaults (optional).prompt-template.json
, .category.json
) are treated as metadata records.md
, .html
, etc.) are referenced from the JSON filesMetadata files must be prefixed with a dot (.) to be recognized by the sync tool. This convention:
package.json
, tsconfig.json
and other standard files to coexist without being processed.gitignore
and .eslintrc.json
Examples:
.greeting.json
- Will be processed as metadata.customer-prompt.json
- Will be processed as metadata greeting.json
- Will be ignoredpackage.json
- Will be ignoredEach JSON file contains one record:
{
"fields": { ... },
"relatedEntities": { ... }
}
JSON files can contain arrays of records:
[
{
"fields": { ... },
"relatedEntities": { ... }
},
{
"fields": { ... },
"relatedEntities": { ... }
}
]
This is useful for:
@file:
references for large contentmetadata/
├── .mj-sync.json # Global sync configuration
├── ai-prompts/
│ ├── .mj-sync.json # Defines entity: "AI Prompts"
│ ├── customer-service/
│ │ ├── .mj-folder.json # Folder metadata (CategoryID, etc.)
│ │ ├── .greeting.json # AI Prompt record with embedded models
│ │ ├── greeting.prompt.md # Prompt content (referenced)
│ │ └── greeting.notes.md # Notes field (referenced)
│ └── analytics/
│ ├── .mj-folder.json # Folder metadata (CategoryID, etc.)
│ ├── .daily-report.json # AI Prompt record
│ └── daily-report.prompt.md # Prompt content (referenced)
├── templates/ # Reusable JSON templates
│ ├── standard-prompt-settings.json # Common prompt configurations
│ ├── standard-ai-models.json # Standard model configurations
│ ├── high-performance-models.json # High-power model configurations
│ └── customer-service-defaults.json # CS-specific defaults
└── template-entities/
├── .mj-sync.json # Defines entity: "Templates"
├── email/
│ ├── .mj-folder.json # Folder metadata
│ ├── .welcome.json # Template record (dot-prefixed)
│ └── welcome.template.html # Template content (referenced)
└── reports/
├── .mj-folder.json # Folder metadata
├── .invoice.json # Template record (dot-prefixed)
└── invoice.template.html # Template content (referenced)
{
"fields": {
"Name": "Customer Greeting",
"Description": "Friendly customer service greeting",
"TypeID": "@lookup:AI Prompt Types.Name=Chat",
"CategoryID": "@lookup:AI Prompt Categories.Name=Customer Service",
"Temperature": 0.7,
"MaxTokens": 1000,
"Prompt": "@file:greeting.prompt.md",
"Notes": "@file:../shared/notes/greeting-notes.md",
"SystemPrompt": "@url:https://raw.githubusercontent.com/company/prompts/main/system/customer-service.md"
},
"primaryKey": {
"ID": "550e8400-e29b-41d4-a716-446655440000"
},
"sync": {
"lastModified": "2024-01-15T10:30:00Z",
"checksum": "sha256:abcd1234..."
}
}
{
"fields": {
"Name": "Customer Service Chat",
"Description": "Main customer service prompt",
"TypeID": "@lookup:AI Prompt Types.Name=Chat",
"TemplateText": "@file:customer-service.md",
"Status": "Active"
},
"relatedEntities": {
"MJ: AI Prompt Models": [
{
"fields": {
"PromptID": "@parent:ID",
"ModelID": "@lookup:AI Models.Name=GPT 4.1",
"VendorID": "@lookup:MJ: AI Vendors.Name=OpenAI",
"Priority": 1,
"Status": "Active"
},
"primaryKey": {
"ID": "BFA2433E-F36B-1410-8DB0-00021F8B792E"
},
"sync": {
"lastModified": "2025-06-07T17:18:31.687Z",
"checksum": "a642ebea748cb1f99467af2a7e6f4ffd3649761be27453b988af973bed57f070"
}
},
{
"fields": {
"PromptID": "@parent:ID",
"ModelID": "@lookup:AI Models.Name=Claude 4 Sonnet",
"Priority": 2,
"Status": "Active"
}
}
]
},
"primaryKey": {
"ID": "C2A1433E-F36B-1410-8DB0-00021F8B792E"
},
"sync": {
"lastModified": "2025-06-07T17:18:31.698Z",
"checksum": "7cbd241cbf0d67c068c1434e572a78c87bb31751cbfe7734bfd32f8cea17a2c9"
}
}
{
"primaryKey": {
"UserID": "550e8400-e29b-41d4-a716-446655440000",
"RoleID": "660f9400-f39c-51e5-b827-557766551111"
},
"fields": {
"GrantedAt": "2024-01-15T10:30:00Z",
"GrantedBy": "@lookup:Users.Email=admin@company.com",
"ExpiresAt": "2025-01-15T10:30:00Z",
"Notes": "@file:user-role-notes.md"
},
"sync": {
"lastModified": "2024-01-15T10:30:00Z",
"checksum": "sha256:abcd1234..."
}
}
The tool implements a cascading inheritance system for field defaults, similar to CSS or OOP inheritance:
.mj-sync.json
) - Base defaults for all records.mj-folder.json
) - Override/extend entity defaultsai-prompts/.mj-sync.json → Temperature: 0.7, MaxTokens: 1500
├── customer-service/.mj-folder.json → Temperature: 0.8 (overrides)
│ ├── greeting.json → Uses Temperature: 0.8, MaxTokens: 1500
│ └── escalation/.mj-folder.json → Temperature: 0.6 (overrides again)
│ └── urgent.json → Temperature: 0.9 (record override)
Final values for urgent.json
:
The tool supports special reference types that can be used in ANY field that accepts text content. These references are processed during push/pull operations to handle external content, lookups, and environment-specific values.
The tool automatically detects primary key fields from entity metadata:
{"ID": "value"}
or {"CustomKeyName": "value"}
The tool now supports deleting records from the database using a special deleteRecord
directive in JSON files. This allows you to remove obsolete records as part of your metadata sync workflow:
deleteRecord
section to any record JSON filedelete: true
to mark the record for deletionmj sync push
to execute the deletion{
"fields": {
"Name": "Obsolete Prompt",
"Description": "This prompt is no longer needed"
},
"primaryKey": {
"ID": "550e8400-e29b-41d4-a716-446655440000"
},
"deleteRecord": {
"delete": true
}
}
After successfully deleting the record, the tool updates the JSON file:
{
"fields": {
"Name": "Obsolete Prompt",
"Description": "This prompt is no longer needed"
},
"primaryKey": {
"ID": "550e8400-e29b-41d4-a716-446655440000"
},
"deleteRecord": {
"delete": true,
"deletedAt": "2024-01-15T14:30:00.000Z"
}
}
primaryKey
to identify which record to deletedeletedAt
is set, the deletion won't be attempted again--dry-run
to preview what would be deleteddeleteRecord
is present, normal create/update operations are skippedWhen a field value starts with @file:
, the tool will:
@include
directives within themExamples:
@file:greeting.prompt.md
- File in same directory as JSON@file:./shared/common-prompt.md
- Relative path@file:../templates/standard-header.md
- Parent directory reference@file:spec.json
- JSON file with @include
directives (processed automatically)When a field value starts with @url:
, the tool will:
Examples:
@url:https://example.com/prompts/greeting.md
- Remote content@url:https://raw.githubusercontent.com/company/prompts/main/customer.md
- GitHub raw content@url:file:///shared/network/drive/prompts/standard.md
- Local file URLEnable entity relationships using human-readable values:
@lookup:EntityName.FieldName=Value
@lookup:EntityName.Field1=Value1&Field2=Value2
@lookup:EntityName.FieldName=Value?create
@lookup:EntityName.FieldName=Value?create&Field2=Value2
Examples:
@lookup:AI Prompt Types.Name=Chat
- Single field lookup, fails if not found@lookup:Users.Email=john@example.com&Department=Sales
- Multi-field lookup for precise matching@lookup:AI Prompt Categories.Name=Examples?create
- Creates if missing@lookup:AI Prompt Categories.Name=Examples?create&Description=Example prompts
- Creates with descriptionWhen you need to match records based on multiple criteria, use the multi-field syntax:
{
"CategoryID": "@lookup:AI Prompt Categories.Name=Actions&Status=Active",
"ManagerID": "@lookup:Users.Email=manager@company.com&Department=Engineering&Status=Active"
}
This ensures you get the exact record you want when multiple records might have the same value in a single field.
Reference fields from the immediate parent entity in embedded collections:
@parent:ID
- Get the parent's ID field@parent:Name
- Get the parent's Name fieldReference fields from the root entity in nested structures:
@root:ID
- Get the root entity's ID@root:CategoryID
- Get the root's CategoryIDSupport environment-specific values:
@env:VARIABLE_NAME
When a field value is an array or object, the tool automatically converts it to a JSON string for database storage:
Examples:
{
"fields": {
"Name": "My Entity",
"Configuration": {
"setting1": "value1",
"setting2": {
"nested": true,
"items": [1, 2, 3]
}
},
"Tags": ["tag1", "tag2", "tag3"],
"Metadata": {
"created": "2024-01-15",
"author": "John Doe"
}
}
}
The Configuration
, Tags
, and Metadata
fields will automatically be converted to JSON strings when pushed to the database, while maintaining their structured format in your source files.
Enable content composition within non-JSON files (like .md, .html, .txt) using JSDoc-style include syntax:
{@include path/to/file.ext}
@file:
referencesWhen a JSON metadata file uses @file:
to reference an external file, the MetadataSync tool:
{@include}
patterns# My Prompt Template
## System Instructions
{@include ./shared/system-instructions.md}
## Context
{@include ../common/context-header.md}
## Task
Please analyze the following...
Directory structure:
prompts/
├── customer-service/
│ ├── greeting.json # Uses @file:greeting.md
│ ├── greeting.md # Contains {@include} references
│ └── shared/
│ ├── tone.md
│ └── guidelines.md
└── common/
├── company-info.md
└── legal-disclaimer.md
greeting.json:
{
"fields": {
"Name": "Customer Greeting",
"Prompt": "@file:greeting.md"
}
}
greeting.md:
# Customer Service Greeting
{@include ./shared/tone.md}
## Guidelines
{@include ./shared/guidelines.md}
## Company Information
{@include ../common/company-info.md}
## Legal
{@include ../common/legal-disclaimer.md}
The final content pushed to the database will have all includes fully resolved.
Benefits: