Skip to content

OIM Metadata Schema ​

Complete reference for the Zod-validated metadata structure in .oim files.

Schema Overview ​

typescript
import { z } from "zod";

export const OimMetadataSchema = z.object({
  oim: z.string().optional(),              // Format identifier
  version: z.string().optional(),          // Schema version
  image: z.string().optional(),            // Base64 image (legacy)
  oim_id: z.string().max(128).optional(),  // Unique tracking ID
  media_type: z.enum(["image", "video"]).optional(),
  creator_user_id: z.string().max(128).optional(),
  image_hash: z.string().max(64).optional(), // SHA-256 of original image
  immutable_lock: z.boolean().optional(),  // Seal file from edits
  allow_external_urls: z.boolean().optional(),
  redirect_membership_verified: z.boolean().optional(),
  terms_accepted: z.boolean().optional(),
  canvas: z.object({
    natural_width: z.number().positive(),
    natural_height: z.number().positive()
  }).optional(),
  lang: z.array(z.string().max(5)).optional(),
  zones: z.array(zoneSchema).optional(),
  slots: z.array(slotSchema),              // Required
  elements: z.array(slotSchema).optional(),
  layers: z.array(slotSchema).optional(),
  signature: z.string().optional(),        // ECDSA signature
  key_version: z.string().optional(),
  timestamp: z.string().datetime().optional(),
}).passthrough();

Field Reference ​

Top-Level Fields ​

FieldTypeRequiredDescription
oimstringFormat identifier (e.g., "1.0")
versionstringSchema version
imagestringBase64-encoded image (legacy, avoid)
oim_idstring (≤128)Unique ID for document re-editing
media_type"image" | "video"Type of media content
creator_user_idstring (≤128)Creator tracking identifier
image_hashstring (≤64)SHA-256 hash of the baked image (Layer −1)
immutable_lockbooleanIf true, file cannot be edited
allow_external_urlsbooleanPermit external redirection URLs
redirect_membership_verifiedbooleanPaid membership verification flag
terms_acceptedbooleanTerms & conditions acceptance flag
canvasobjectCanvas dimensions { natural_width, natural_height }
langstring[] (≤5 chars each)Available language codes
zonesZone[]Named bounding box zones
slotsSlot[]Text/image overlay definitions
elementsSlot[]Alternative slot array
layersSlot[]Layer-based slot array
signaturestringCryptographic ECDSA signature
key_versionstringKey version used for signature
timestampISO datetimePackage creation timestamp

Slot Schema ​

typescript
const slotSchema = z.object({
  id: z.string().min(1).max(128),
  type: z.enum(["text", "image", "lottie", "zone", "geo", "shape", "draw", "filter", "container", "group", "component", "mask", "svg", "pattern", "artistic-text"]).optional(),
  content: z.record(z.string(), z.string().max(10000000)).optional(),
  style: styleSchema.optional(),
  position: z.tuple([z.number().min(0).max(100), z.number().min(0).max(100)]).optional(),
  tracking_data: z.array(z.array(z.number()).min(2)).optional(),
  zone_id: z.string().max(128).optional(),
  link: z.string().max(2048).optional(),
  target: z.enum(["_blank", "_self"]).optional(),
  interaction: interactionSchema.optional(),
  
  // Abbreviated keys for size optimization
  p: positionSchema.optional(),                     // alias for position
  s: z.tuple([z.number(), z.number()]).optional(),  // alias for size/dimensions
  st: styleSchema.optional(),                       // alias for style
  c: contentSchema.optional(),                      // alias for content
  t: z.array(trackingKeyframeSchema).optional(),    // alias for tracking_data
});
FieldTypeRequiredDescription
idstring (1-128)Unique slot identifier
type"text" | "image" | "lottie" | "zone"Slot content type
contentRecord<lang, text>Language-to-content dictionary (up to 10MB for base64 images)
styleStyleVisual styling (see below)
position[x, y] (0-100)Normalized position coordinates
tracking_datanumber[][]Keyframe animation data
zone_idstring (≤128)Target containment zone
linkstring (≤2048)Redirection URL for hotspot/e-commerce
target"_blank" | "_self"Link opening behavior
p[x, y]Retired in 1.12.0: the schema still accepts it, but no writer emits it; use position
s[w, h]Retired in 1.12.0
stStyleRetired in 1.12.0: the schema still accepts it, but no writer emits it; use style
cRecord<lang, text>Retired in 1.12.0: the schema still accepts it, but no writer emits it; use content
tnumber[][]Retired in 1.12.0: the schema still accepts it, but no writer emits it; use tracking_data

Style Schema ​

typescript
const styleSchema = z.object({
  font: z.string().max(64).optional(),
  color: z.string().max(64).optional(),
  size: z.string().max(16).optional(),
  fontSize: z.string().max(16).optional(),
  fontWeight: z.union([z.string(), z.number()]).optional(),
  fontStyle: z.enum(["normal", "italic", "oblique"]).optional(),
  textDecoration: z.string().max(64).optional(),
  textTransform: z.enum(["none", "uppercase", "lowercase", "capitalize"]).optional(),
  fontSizeOverrides: z.record(z.string(), z.string()).optional(),
  fontSizes: z.record(z.string(), z.string()).optional(),
  maxWidths: z.record(z.string(), z.string()).optional(),
  outline: z.string().max(128).optional(),
  objectFit: z.enum(["contain", "cover", "fill", "none", "scale-down"]).optional(),
  verticalAlign: z.enum(["top", "center", "bottom", "mixed", "justify"]).optional(),
  animation: z.object({
    loop: z.enum(["none", "pulse", "float", "bounce", "spin", "glow", "custom"]).optional(),
    hover: z.enum(["none", "grow", "shrink", "lift", "glow", "custom"]).optional(),
    custom_loop_css: z.string().max(500).optional(),
    custom_keyframes: z.string().max(2000).optional(),
    custom_hover_css: z.string().max(500).optional(),
    duration: z.number().min(0.1).max(20).optional(),
    delay: z.number().min(0).max(10).optional(),
  }).optional(),
  shader_fx: z.object({
    blur: z.number().min(0).max(10).optional(),
    grain: z.number().min(0).max(1).optional(),
    crt_curve: z.boolean().optional(),
  }).optional(),
}).passthrough();

Style Fields ​

FieldTypeDescription
fontstring (≤64)Font family name
colorstring (≤64)CSS color value
size / fontSizestring (≤16)Font size (e.g., "48px")
fontWeightstring | numberFont weight (e.g., "700" or 700)
fontStyle"normal" | "italic" | "oblique"Font style
textDecorationstring (≤64)Text decoration
textTransform"none" | "uppercase" | "lowercase" | "capitalize"Text transform
outlinestring (≤128)Text outline CSS
objectFit"contain" | "cover" | "fill" | "none" | "scale-down"Image fit mode
verticalAlign"top" | "center" | "bottom" | "mixed" | "justify"Vertical alignment

Animation ​

FieldTypeDescription
loopenumLoop animation type
hoverenumHover animation type
custom_loop_cssstring (≤500)Custom CSS for loop
custom_keyframesstring (≤2000)Custom @keyframes definition
custom_hover_cssstring (≤500)Custom CSS for hover
durationnumber (0.1-20)Animation duration in seconds
delaynumber (0-10)Animation delay in seconds

Shader Effects ​

FieldTypeDescription
blurnumber (0-10)Blur intensity
grainnumber (0-1)Film grain intensity
crt_curvebooleanCRT curve effect

Zone Schema ​

typescript
const zoneSchema = z.object({
  id: z.string().min(1).max(128),
  position: z.tuple([z.number().min(0).max(100), z.number().min(0).max(100)]),
  size: z.tuple([z.number().min(0).max(100), z.number().min(0).max(100)]),
  style: z.record(z.string(), z.any()).optional(),
});
FieldTypeDescription
idstring (1-128)Zone identifier
position[x, y] (0-100)Zone origin (top-left)
size[width, height] (0-100)Zone dimensions
styleRecord<string, any>Zone-specific styles

Complete Example ​

json
{
  "oim": "1.0",
  "version": "1.0",
  "lang": ["en", "es"],
  "canvas": {
    "natural_width": 1920,
    "natural_height": 1080
  },
  "slots": [
    {
      "id": "main_title",
      "type": "text",
      "content": {
        "en": "Welcome to OIM",
        "es": "Bienvenido a OIM"
      },
      "position": [50, 20],
      "style": {
        "font": "Bricolage Grotesque",
        "fontSize": "48px",
        "fontWeight": "800",
        "color": "#f59e0b",
        "textTransform": "uppercase"
      }
    },
    {
      "id": "cta_button",
      "type": "text",
      "content": {
        "en": "Learn More",
        "es": "Saber Más"
      },
      "position": [50, 80],
      "style": {
        "fontSize": "24px",
        "color": "#ffffff",
        "animation": {
          "loop": "pulse",
          "duration": 2.0
        }
      },
      "link": "https://example.com",
      "target": "_blank"
    }
  ],
  "zones": [
    {
      "id": "header_zone",
      "position": [0, 0],
      "size": [100, 30]
    }
  ]
}

Security Constraints ​

ConstraintPurpose
String max lengthsPrevent buffer overflow
Color regex validationPrevent CSS injection
Max 50 slotsPrevent resource exhaustion
content ≤ 10MBAllow base64 images but cap size
position 0-100Normalized coordinates only
.passthrough()Allow forward-compatible fields

TypeScript Types ​

typescript
export type OIMMetadata = z.infer<typeof OimMetadataSchema>;
export type Slot = z.infer<typeof slotSchema>;
export type Style = z.infer<typeof styleSchema>;
export type Zone = z.infer<typeof zoneSchema>;

See Also ​