---
title: Metadata Customization
description: Metadata Customization protocol schemas
---

{/* ⚠️  AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs go in content/docs/guides/. */}

# 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.

<Callout type="info">
**Source:** `packages/spec/src/kernel/metadata-customization.zod.ts`
</Callout>

## TypeScript Usage

```typescript
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

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **metadataType** | `string` | ✅ | Metadata type (e.g. "object", "view") |
| **allowCustomization** | `boolean` | ✅ |  |
| **lockedFields** | `string[]` | optional | Field paths that cannot be customized |
| **customizableFields** | `string[]` | optional | Field paths that can be customized (whitelist) |
| **allowAddFields** | `boolean` | ✅ | Whether admins can add new fields to package objects |
| **allowDeleteFields** | `boolean` | ✅ | Whether admins can delete package-delivered fields |


---

## FieldChange

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **path** | `string` | ✅ | JSON path to the changed field |
| **originalValue** | `any` | optional | Original value from the package |
| **currentValue** | `any` | ✅ | Current customized value |
| **changedBy** | `string` | optional | User or admin who made this change |
| **changedAt** | `string` | optional | Timestamp of the change |


---

## MergeConflict

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **path** | `string` | ✅ | JSON path to the conflicting field |
| **baseValue** | `any` | ✅ | Value in the old package version |
| **incomingValue** | `any` | ✅ | Value in the new package version |
| **customValue** | `any` | ✅ | Customer customized value |
| **suggestedResolution** | `Enum<'keep-custom' \| 'accept-incoming' \| 'manual'>` | ✅ | Suggested resolution strategy |
| **reason** | `string` | optional | Explanation for the suggested resolution |


---

## MergeResult

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Whether merge completed without unresolved conflicts |
| **mergedMetadata** | `Record<string, any>` | optional | Merged metadata result |
| **updatedOverlay** | `Record<string, any>` | optional | Updated overlay after merge |
| **conflicts** | `Object[]` | optional | Unresolved merge conflicts |
| **autoResolved** | `Object[]` | optional | Summary of auto-resolved changes |
| **stats** | `Object` | optional |  |


---

## MergeStrategyConfig

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **defaultStrategy** | `Enum<'keep-custom' \| 'accept-incoming' \| 'three-way-merge'>` | ✅ | Default merge strategy |
| **alwaysAcceptIncoming** | `string[]` | optional | Field paths that always accept package updates |
| **alwaysKeepCustom** | `string[]` | optional | Field paths where customer customizations always win |
| **autoResolveNonConflicting** | `boolean` | ✅ | Auto-resolve changes that do not conflict |


---

## MetadataOverlay

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **id** | `string` | ✅ | Overlay record ID (UUID) |
| **baseType** | `string` | ✅ | Metadata type being customized |
| **baseName** | `string` | ✅ | Metadata name being customized |
| **packageId** | `string` | optional | Package ID that delivered the base metadata |
| **packageVersion** | `string` | optional | Package version when overlay was created |
| **scope** | `Enum<'platform' \| 'user'>` | ✅ | Customization scope (platform=admin, user=personal) |
| **tenantId** | `string` | optional | Tenant identifier |
| **owner** | `string` | optional | Owner user ID for user-scope overlays |
| **patch** | `Record<string, any>` | ✅ | JSON Merge Patch payload (changed fields only) |
| **changes** | `Object[]` | optional | Field-level change tracking for conflict detection |
| **active** | `boolean` | ✅ | Whether this overlay is active |
| **createdAt** | `string` | optional |  |
| **createdBy** | `string` | optional |  |
| **updatedAt** | `string` | optional |  |
| **updatedBy** | `string` | optional |  |


---

