ObjectStackObjectStack

Metadata Customization

Metadata Customization protocol schemas

Metadata Customization Layer Protocol

Defines the overlay system for managing user customizations on top of

package-delivered metadata. This protocol solves the critical challenge

of separating "vendor-managed" metadata from "customer-customized" metadata,

enabling safe package upgrades without losing user changes.

Architecture Alignment

  • Salesforce: Managed vs Unmanaged metadata components

  • ServiceNow: Update Sets with collision detection

  • WordPress: Parent/child theme overlay model

  • Kubernetes: Strategic merge patch for resource customization

Three-Layer Model


┌─────────────────────────────────┐

│  User Layer (scope: user)       │  ← Personal overrides (per-user)

├─────────────────────────────────┤

│  Platform Layer (scope: platform)│  ← Admin customizations (per-tenant)

├─────────────────────────────────┤

│  System Layer (scope: system)   │  ← Package-delivered metadata (read-only)

└─────────────────────────────────┘

Merge Resolution Order

Effective metadata = System ← merge(Platform) ← merge(User)

Each layer only stores the delta (changed fields), not the full definition.

Source: packages/spec/src/kernel/metadata-customization.zod.ts

TypeScript Usage

import { CustomizationOrigin, CustomizationPolicy, FieldChange, MergeConflict, MergeResult, MergeStrategyConfig, MetadataOverlay } from '@objectstack/spec/kernel';
import type { CustomizationOrigin, CustomizationPolicy, FieldChange, MergeConflict, MergeResult, MergeStrategyConfig, MetadataOverlay } from '@objectstack/spec/kernel';

// Validate data
const result = CustomizationOrigin.parse(data);

CustomizationOrigin

Allowed Values

  • package
  • admin
  • user
  • migration
  • api

CustomizationPolicy

Properties

PropertyTypeRequiredDescription
metadataTypestringMetadata type (e.g. "object", "view")
allowCustomizationboolean
lockedFieldsstring[]optionalField paths that cannot be customized
customizableFieldsstring[]optionalField paths that can be customized (whitelist)
allowAddFieldsbooleanWhether admins can add new fields to package objects
allowDeleteFieldsbooleanWhether admins can delete package-delivered fields

FieldChange

Properties

PropertyTypeRequiredDescription
pathstringJSON path to the changed field
originalValueanyoptionalOriginal value from the package
currentValueanyCurrent customized value
changedBystringoptionalUser or admin who made this change
changedAtstringoptionalTimestamp of the change

MergeConflict

Properties

PropertyTypeRequiredDescription
pathstringJSON path to the conflicting field
baseValueanyValue in the old package version
incomingValueanyValue in the new package version
customValueanyCustomer customized value
suggestedResolutionEnum<'keep-custom' | 'accept-incoming' | 'manual'>Suggested resolution strategy
reasonstringoptionalExplanation for the suggested resolution

MergeResult

Properties

PropertyTypeRequiredDescription
successbooleanWhether merge completed without unresolved conflicts
mergedMetadataRecord<string, any>optionalMerged metadata result
updatedOverlayRecord<string, any>optionalUpdated overlay after merge
conflictsObject[]optionalUnresolved merge conflicts
autoResolvedObject[]optionalSummary of auto-resolved changes
statsObjectoptional

MergeStrategyConfig

Properties

PropertyTypeRequiredDescription
defaultStrategyEnum<'keep-custom' | 'accept-incoming' | 'three-way-merge'>Default merge strategy
alwaysAcceptIncomingstring[]optionalField paths that always accept package updates
alwaysKeepCustomstring[]optionalField paths where customer customizations always win
autoResolveNonConflictingbooleanAuto-resolve changes that do not conflict

MetadataOverlay

Properties

PropertyTypeRequiredDescription
idstringOverlay record ID (UUID)
baseTypestringMetadata type being customized
baseNamestringMetadata name being customized
packageIdstringoptionalPackage ID that delivered the base metadata
packageVersionstringoptionalPackage version when overlay was created
scopeEnum<'platform' | 'user'>Customization scope (platform=admin, user=personal)
tenantIdstringoptionalTenant identifier
ownerstringoptionalOwner user ID for user-scope overlays
patchRecord<string, any>JSON Merge Patch payload (changed fields only)
changesObject[]optionalField-level change tracking for conflict detection
activebooleanWhether this overlay is active
createdAtstringoptional
createdBystringoptional
updatedAtstringoptional
updatedBystringoptional

On this page