Skip to content

DAM Sync

Source: omni-cms-composable-cms/infrastructure/lib/components/dam-sync-kafka.ts

📐 ADR: ADR-0010 — CMS NOT to consume Bynder directly — Decision: asset metadata syncs via an event pipeline, not by the CMS querying Bynder at render time.

All images in the CMS come from Bynder (HEMA’s Digital Asset Management). Direct uploads are disabled. When asset metadata changes in Bynder, a Kafka-based pipeline syncs those changes to Sanity.

Diagram
Bynder ChangeSanity Update
Tags changedUpdates opt.media.tags on asset + creates media.tag_* documents
Description changedUpdates description on asset document
Metadata properties changedUpdates metadata object on asset.metadata document
Archive status changedUpdates archived flag
Media version changedFlags as input_required (manual action needed)

Only MediaUpdated and MediaDeleted events are processed (filtered at the pipe level).

new KafkaPipe(this, "BynderEventsPipe", {
topicName: "Hema.dam-engine.BynderAssets",
consumerGroupId: `${service}-${stage}-dam-sync`,
batchSize: 100,
maximumBatchingWindowInSeconds: 5,
startingPosition: "LATEST",
enrichment: new SchemaDecodeEnrichLambda(...),
target: new KafkaRecordsToSqsLambda(...),
});
  • FIFO with content-based deduplication
  • Dead-letter queue for failed messages
  • S3 bucket for oversized payloads

Finds related Sanity documents by Bynder media_id, builds a transaction with patches, and commits atomically.

A scheduled Lambda removes unused/orphaned assets:

new AssetCleanupScheduler(this, "AssetCleanupScheduler", {
cleanupSchedule: props.assetCleanupSchedule,
dryRun: props.assetCleanupDryRun,
gracePeriodDays: props.assetCleanupGracePeriodDays,
maxDeletions: props.assetCleanupMaxDeletions,
});

The Studio uses a custom Bynder plugin (plugins/bynder/) that:

  • Replaces default image upload with Bynder’s Compact View
  • Imports assets with source.name = "bynder" and source.id = <media-id>
  • Stores metadata in a companion asset.metadata document
sanity.config.ts
form: {
image: { assetSources: () => [bynderMediaSource], directUploads: false },
file: { assetSources: () => [bynderMediaSource], directUploads: false },
},
IssueCauseResolution
Asset not syncingMedia ID not in SanityRe-import asset from Bynder in Studio
Tags not updatingFailed messagesCheck DLQ, replay messages
input_required statusMedia file version changedEditor must re-select asset in Studio
Lambda timeoutLarge batchCheck batch size config