om-eject-and-customize

star 1.4k

Guide for safely ejecting and customizing core modules. Use when a developer needs to modify a core module's behavior beyond what UMES extensions support, wants to eject a module, or is considering whether to eject vs extend. Triggers on "eject", "customize module", "modify core module", "override module", "fork module", "change built-in", "should I eject".

open-mercato By open-mercato schedule Updated 6/5/2026

name: om-eject-and-customize description: Guide for safely ejecting and customizing core modules. Use when a developer needs to modify a core module's behavior beyond what UMES extensions support, wants to eject a module, or is considering whether to eject vs extend. Triggers on "eject", "customize module", "modify core module", "override module", "fork module", "change built-in", "should I eject".

Eject & Customize

Guide for safely ejecting core modules and making targeted customizations. Ejecting is a one-way door — always consider UMES extensions first.

Table of Contents

  1. Should You Eject?
  2. Pre-Ejection Analysis
  3. Performing the Ejection
  4. What Gets Ejected
  5. Safe Modification Zones
  6. Dangerous Modification Zones
  7. Tracking Customizations
  8. Upgrade Strategy
  9. Common Ejection Scenarios

1. Should You Eject?

Ejection is the last resort. Before recommending ejection, verify that UMES extensions cannot solve the problem.

Decision Matrix

What You Want to Do Use UMES Extension Eject Required
Add fields to a form Field Injection (system-extension skill) No
Add columns to a table Column Injection (system-extension skill) No
Add data to API responses Response Enricher No
Validate/block mutations Mutation Guard or API Interceptor No
Change how a component looks Component Replacement (wrapper) No
Add menu items Menu Injection No
React to domain events Event Subscribers No
Change entity schema (add/remove columns) Not possible via UMES Yes
Change core business logic (pricing, auth flow) Not possible via UMES Yes
Remove built-in fields from forms Not possible via UMES Yes
Change API route validation rules Partially (interceptors), but deep changes need eject Maybe
Change database relationships Not possible via UMES Yes

Before Ejecting — Try These First

  1. Response Enricher — Add computed data to any API response
  2. API Interceptor — Validate, transform, or enrich requests/responses
  3. Mutation Guard — Block or modify mutations before persistence
  4. Component Replacement — Swap, wrap, or transform any registered component
  5. Widget Injection — Add UI elements to any registered spot
  6. Event Subscriber — React to domain events with side effects

If UMES truly cannot solve the problem, proceed with ejection.


2. Pre-Ejection Analysis

Before ejecting, understand what you're taking ownership of.

Step 1: Identify the Module

# List available core modules
ls node_modules/@open-mercato/core/dist/modules/

Step 2: Check Module Size

Look at the module's file count and complexity. Larger modules mean more upgrade burden.

Step 3: Identify the Specific Change

Be precise about what you need to change. Often only 1-2 files need modification, but ejection copies the entire module.

Step 4: Check Dependencies

Does the module depend on other core modules? Does anything depend on it? Cross-module dependencies increase risk.

Step 5: Document the Reason

Before ejecting, record why:

## Ejection: <module_id>
- **Date**: YYYY-MM-DD
- **Reason**: <why UMES extensions were insufficient>
- **Files to modify**: <specific files that need changes>
- **UMES alternatives considered**: <what was tried first>

Save this in .ai/specs/ or a project README for future reference.


3. Performing the Ejection

Run the Eject Command

yarn mercato module eject <module-id>

This copies the module from node_modules/@open-mercato/core/dist/modules/<module-id>/ to src/modules/<module-id>/.

The legacy alias yarn mercato eject <module-id> remains supported.

Post-Ejection Steps

# 1. The module is automatically re-registered as '@app' source
# Verify in src/modules.ts:
grep '<module-id>' src/modules.ts
# Should show: { id: '<module-id>', from: '@app' }

# 2. Regenerate discovery files
yarn generate

# 3. Verify the app starts
yarn dev

# 4. Verify module functionality
# Test CRUD operations in the admin panel

4. What Gets Ejected

The eject command copies all module files to your src/modules/ directory:

src/modules/<module-id>/
├── index.ts           # Module metadata
├── acl.ts             # ACL features
├── setup.ts           # Tenant init, role defaults
├── di.ts              # DI registrations
├── events.ts          # Event declarations
├── entities/          # MikroORM entities
├── data/
│   ├── validators.ts  # Zod schemas
│   ├── enrichers.ts   # Response enrichers (if any)
│   └── extensions.ts  # Entity extensions (if any)
├── api/               # API route handlers
├── backend/           # Admin UI pages
├── frontend/          # Public pages (if any)
├── subscribers/       # Event handlers
├── workers/           # Background jobs
├── widgets/           # UI widgets
├── commands/          # Command pattern implementations
└── migrations/        # Database migrations

Important: After ejection, the npm package version of this module is no longer used. Your local copy takes precedence.


5. Safe Modification Zones

These files can be modified with low risk:

File/Area Safe Changes Risk Level
backend/*.tsx UI layout, field order, page structure Low
data/validators.ts Validation rules, field constraints Low
api/*/ handlers Business logic within existing routes Medium
commands/ Command execute/undo logic Medium
entities/ Adding new columns (with migration) Medium
subscribers/ Event handler logic Low
workers/ Job processing logic Low
widgets/ Widget components Low

Safe Modification Example: Add a Column

// 1. Add field to entity
@Property({ type: 'varchar', length: 100, nullable: true })
custom_field: string | null = null

// 2. Update validator
export const createSchema = z.object({
  // ... existing fields
  customField: z.string().max(100).optional(),
})

// 3. Generate migration
// yarn db:generate

// 4. Update form fields in backend pages

6. Dangerous Modification Zones

These changes have high upgrade risk or can break other modules:

File/Area Danger Why
index.ts metadata Changing name/id Other modules reference this ID
acl.ts feature IDs Renaming features Feature IDs are stored in DB, referenced by roles
events.ts event IDs Renaming events Other modules subscribe to these event IDs
Entity table/column rename Database references break FKs in other modules, stored data
di.ts service names Renaming DI keys Other modules resolve by name
Removing API routes External consumers break Other modules/integrations call these
setup.ts structure Changing tenant init Affects new tenant provisioning

Rules for Dangerous Zones

  • NEVER rename entity table names or column names — other modules may reference them
  • NEVER rename event IDs — subscribers in other modules depend on exact IDs
  • NEVER rename ACL feature IDs — stored in database with role assignments
  • NEVER remove API routes — other modules or external systems may call them
  • NEVER rename DI service registration keys — other modules resolve by key name
  • Adding is safe; renaming/removing is dangerous

7. Tracking Customizations

Keep a record of every change made to ejected modules. This is critical for upgrades.

Create a Customization Log

Create .ai/specs/EJECTED-MODULES.md:

# Ejected Module Customizations

## <module_id>

- **Ejected from version**: 0.4.2 (check package.json at time of ejection)
- **Ejected on**: YYYY-MM-DD
- **Reason**: <why UMES was insufficient>

### Changes Made

| Date | File | Change | Reason |
|------|------|--------|--------|
| YYYY-MM-DD | entities/Entity.ts | Added `custom_field` column | Business requirement X |
| YYYY-MM-DD | backend/page.tsx | Modified list columns | UX improvement |
| YYYY-MM-DD | data/validators.ts | Added custom validation rule | Data quality requirement |

After Every Change

Add a row to the changes table. This makes future upgrades manageable by showing exactly what was customized.


8. Upgrade Strategy

When upgrading Open Mercato packages (@open-mercato/*), ejected modules don't update automatically. You must manually merge upstream changes.

Upgrade Workflow

  1. Check the changelog for the new version — look for changes to the ejected module
  2. Compare your version with the new version:
    # After updating packages
    diff -r src/modules/<module-id>/ node_modules/@open-mercato/core/dist/modules/<module-id>/
    
  3. Review each difference — your customizations should be the only differences
  4. Merge upstream changes — apply bug fixes and new features from upstream to your local copy
  5. Test thoroughly — run yarn typecheck, yarn test, yarn dev

Minimizing Upgrade Burden

  • Minimize changes — only modify what's strictly necessary
  • Keep changes isolated — prefer adding new files over modifying existing ones
  • Document everything — update the customization log
  • Consider UMES first — for new customizations, check if UMES can handle it even though the module is ejected

9. Common Ejection Scenarios

Scenario: Custom Pricing Logic

Problem: Need to change how product prices are calculated Module: catalog Files to modify: commands/UpdateProductPrice.ts, lib/pricing.ts UMES alternative tried: API Interceptor can't modify internal pricing calculation

Scenario: Custom Auth Flow

Problem: Need SSO integration not supported by built-in auth Module: auth Files to modify: api/post/login.ts, lib/auth-providers.ts UMES alternative tried: None available for core auth flow

Scenario: Custom Order Workflow

Problem: Need non-standard order status transitions Module: sales Files to modify: commands/UpdateOrderStatus.ts, entity status enum UMES alternative tried: Mutation Guard can block transitions but can't add new states

Scenario: Add Column to Core Entity

Problem: Need a column on Customer that doesn't exist Module: customers Files to modify: entities/Person.ts, data/validators.ts, backend/ pages UMES alternative tried: Enricher adds read-only data; need writable field in core schema


Rules

  • MUST try UMES extensions before ejecting — recommend the om-system-extension skill first
  • MUST document the reason for ejection before proceeding
  • MUST track all changes in a customization log
  • MUST run yarn generate after ejection
  • MUST verify the app starts and module works after ejection
  • MUST NOT rename entity tables, columns, event IDs, feature IDs, or DI keys
  • MUST NOT remove API routes or event definitions
  • MUST NOT modify files outside the ejected module's directory
  • Treat ejected modules as owned code — you are responsible for updates and bug fixes
  • When in doubt, extend rather than eject
Install via CLI
npx skills add https://github.com/open-mercato/open-mercato --skill om-eject-and-customize
Repository Details
star Stars 1,404
call_split Forks 299
navigation Branch main
article Path SKILL.md
More from Creator
open-mercato
open-mercato Explore all skills →