TL;DR: DAM quotes land in the mid five figures a year, and that figure doesn't include the integration. The moment your site depends on assets living in someone else's system, you own a sync pipeline: partial webhook coverage, a reconciliation sweep, a delete pass, and ids you didn't mint. We've written that pipeline. It's more work than the demo call implies, and it's the strongest argument for keeping assets in your content graph.
There's a meeting on every enterprise headless build where someone asks where the DAM goes. We wrote up the commercial answer to that question in Mux + Sanity: do you really need a DAM?, and the short version is that most teams already own a DAM through Sanity's Media Library, and that dedicated licences run to $30k–70k a year for a system that still can't stream video.
One line in that post's FAQ gets a single sentence and deserves a whole article:
That second cost is the one procurement forgets: a DAM your site depends on needs a sync pipeline, webhook handling, and someone who owns it.
This is that article. Not the licence, the pipeline.
The distinction that decides everything
An asset in Sanity's Media Library is a document. Your front end queries it like any other document, and a reference to it from a page is a real reference the CMS understands.
An asset in a bolted-on DAM is, from your CMS's point of view, a string. A URL or an external id, sitting in a field, pointing at a row in someone else's database. As the original post puts it, it's "a file behind someone else's API that you mirror into your CMS and hope stays in sync."
That word hope is doing a lot of work, and closing the gap it describes is an engineering project. Here's what's in it.
Webhooks won't cover it
The plan is always webhooks. The asset changes over there, you get told, you update over here.
We build commerce integrations with exactly this shape, where a catalog lives in one system and the content referencing it lives in Sanity. Here's the comment we ended up writing at the top of our sync package, after finding out how much the events actually cover:
packages/sanity-sync/src/reconcile.ts
/**
* The sweep is the primary sync mechanism, not a fallback. BigCommerce has no
* CRUD webhooks for variants and none for brands, and most product image
* changes — including changing the thumbnail — fire no update event at all.
* Webhook payloads are id-only, unordered, and can duplicate. A webhook-only
* sync is therefore structurally incomplete; webhooks are at best a latency
* optimisation layered on top of this sweep.
*/
That's BigCommerce, not a DAM, but the shape is the vendor-agnostic one. Webhook coverage is partial, the gaps aren't in the documentation, and you find them by noticing a thumbnail that didn't change. Which means you write a scheduled sweep that walks the whole library and compares it against what you're holding. Once the sweep exists, the webhook is a latency optimisation you may not bother shipping.
Nobody scopes the sweep on the demo call. It's the actual deliverable.
Then the parts that only show up later
Deletes are a second program. An upsert sweep tells you what exists and says nothing about what stopped existing. So you need a subtraction pass: everything you hold, minus everything the library just returned, is stale. Ours soft-deletes rather than hard-deletes, because content may still reference those documents and a tombstone beats a hole. It also has to be guarded so it only ever runs on a full sweep, because after an incremental one every unmodified asset looks deleted. That guard is one if standing between you and wiping the library on a cron.
The ids aren't yours. Assets get their identity from the DAM. Anything you seed, commit, or move between environments is carrying an identifier minted by a system you don't control, so you need a resolution step that repoints content at whatever ids this environment actually has. We ship one as a separate build command. It exists purely because the identifier belongs to someone else.
Alt text has to be copied. You're not making a third-party HTTP call during a server render, so asset metadata gets denormalised into your CMS. Now you hold two copies and a job whose entire purpose is keeping them equal, and every field marketing adds over there is a schema change over here.
Nothing tells you when it breaks. This is the one that stings. A reference pointing at an id that no longer resolves doesn't throw. In our stack it renders as nothing at all, which we wrote into the seed docs in as plain a form as we could manage:
A dangling weak reference renders as nothing — no error, no gap in the log, just an empty navbar.
No stack trace, no alert. A homepage quietly rendering four fewer things than it should, until somebody scrolls past it on a Tuesday.
The whole pipeline is open source if you want to read it rather than take our word for it, and I've written up the four things that caught us out building it: Four surprises from writing a BigCommerce to Sanity catalog sync.
What this is worth in the DAM decision
Put the two costs next to each other. The licence is $30k–70k a year. The pipeline above is a build, plus an owner, plus an on-call story, indefinitely. Procurement prices the first and rarely the second, which is how a DAM ends up looking cheaper than it is.
None of it exists if assets are documents in the same content graph as your content. There's no webhook because there's no second system, no reconciliation because there's nothing to reconcile, and referential integrity is just referential integrity, because the CMS knows what uses what.
Usual disclosure: Sanity is the stack we build on and sell, so weigh our enthusiasm accordingly. The architecture question survives whatever CMS you're on. Ask it of Contentful, Payload or Strapi and the answer will tell you what you're signing up for.
Video is the honest exception, and it splits the other way. Storing a video and delivering one are unrelated problems, and no DAM solves the second, so a specialist service earns its place there whatever you do about images. We wrote up the player side separately, lazy-loaded facade and all: a really nice Mux video component.
Modelling content before you buy tooling. Structured content modelling, migrations off legacy stacks, and the unglamorous business of deciding what should be a reference and what shouldn't. See our Sanity service. Expect some of it to come back as a recommendation to spend nothing.
The question to ask in the meeting
Not "what features does it have". This:
Will a reference from a page to an asset be a real reference the CMS understands, or a string?
If the answer is a string, ask who owns the sweep, who's on call for it, and what happens when the two systems disagree. Those questions all have answers. They're just expensive, and they're much cheaper to hear before the contract than after.
The genuine exception holds up. If several disconnected projects share one asset operation and most of the consumers aren't on your CMS, a central DAM is doing work nothing else is doing, and the build cost really can climb past the licence. That's a smaller share of teams than the sales cycle implies.
Frequently asked questions
Isn't a sync pipeline just the cost of any integration?
Mostly it's the cost of stateful integrations. Plenty of services are request-response: you call them, you get an answer, nothing needs reconciling afterwards. Asset systems are different because durable content holds references to their records, so the two systems have to agree over time rather than at one moment.
Our DAM ships a CMS plugin. Doesn't that cover it?
It covers the picker, usually the metadata copy, sometimes the sweep. It can't give you referential integrity, because that would need your CMS to own the asset record, which is the thing the DAM exists to prevent. Read the plugin's source and check what it does on a delete.
What if the DAM is already bought?
Count the consumers. If everything downstream is a single CMS, you're paying a licence plus the engineering tax above to keep two systems agreeing. If genuinely disconnected systems consume those assets, it's earning its keep and this post isn't about you.
I'm Jono. I run Roboto Studio. Next.js and Sanity mostly, plus a lot of time spent unpicking integrations that seemed reasonable at the time. If you've written the sync pipeline, I'd like to hear which part caught you out.
Top comments (0)