What Shopify's digital products actually do
It is worth being accurate about the starting point, because the limits are not where people assume. Shopify's digital products support a maximum file size of 5 GB, multiple files per variant, archive files containing many files, and — the one that surprises people — no bandwidth limit on downloads. If the file is larger still, you can attach a link to a supported provider instead, from a list that includes Dropbox, Google Drive, Vimeo and a dozen others.
So for an ebook, a sample pack, a template bundle, a video course hosted elsewhere: this is done. Use it. Nothing that follows applies, and building an alternative would be a way of acquiring an obligation in exchange for a worse version of something that already works.
The problem is not size. It is structure.
A download is not a library
A file is delivered once. A library is navigated, repeatedly, from more than one device, in an order the customer chooses and with a position they expect to keep.
Those are different products, and the difference shows up in every part of the design. A four-gigabyte archive has no concept of chapter nine. It has no resumability, so a failed download on a train starts again. It has no playback state, so the device remembers where they were and the platform does not. And it arrives as a decision the customer has to make — where do I put this — at exactly the moment they wanted to start listening.
There is also a hard ceiling worth knowing about if you were planning to model the parts as product media. The per-product media limit is 250 and it was explicitly not raised when the variant ceiling went to 2,048 in October 2025. A sixty-file audiobook fits; a catalogue where someone eventually wants every chapter, every sample and every cover variant addressable does not, and finding that out late is expensive.
Metadata has the same shape of problem. Author, narrator, ISBN, runtime and chapter list are structured facts about the product. Put them in the description and they are unsearchable, unfilterable prose; put them in structured fields and the storefront can do something with them later. That is not a large decision at the time and it is an extremely annoying one to reverse.
The key between the product and the file
Once the media lives outside Shopify, the only thing holding the two together is the mapping, and the mapping is the part to get right on the first attempt.
It has to be derivable rather than stored, so that re-running an import does not depend on a lookup table that may itself be stale. It has to come from an identifier that outlives both sides — a title changes, a product record gets rebuilt, an audio file gets re-mastered and re-uploaded. And it has to sort correctly, because something downstream will list the objects and trust the order.
const ISBN13 = /^\d{13}$/;
/** Hyphens and spaces are presentation. The identifier is the digits. */
export function normaliseIsbn(raw) {
const digits = String(raw ?? "").replace(/[\s-]/g, "");
if (!ISBN13.test(digits)) throw new Error(`not a 13-digit ISBN: ${JSON.stringify(raw)}`);
return digits;
}
/**
* Deterministic object key for one chapter.
*
* Zero-padded to four digits so lexical order matches chapter order. Object
* stores list lexically, and "chapter-10" sorts before "chapter-2" — which is
* a playlist in the wrong order, discovered by a customer rather than a test.
*/
export function assetKey(isbn, chapter, { ext = "m4a" } = {}) {
const id = normaliseIsbn(isbn);
if (!Number.isInteger(chapter) || chapter < 1) {
throw new Error(`chapter must be a positive integer, got ${chapter}`);
}
if (chapter > 9999) throw new Error(`chapter ${chapter} exceeds the four-digit key width`);
return `audio/${id}/${String(chapter).padStart(4, "0")}.${ext}`;
}Why the refusals, and what determinism buys
The refusals matter as much as the format. A bad identifier failing here is a job that stops; the same identifier failing silently is two thousand objects in a bucket that nothing points at, discovered in six months by a storage bill.
Determinism also buys the two operational properties that matter on a transfer of this size. It is resumable: re-deriving a key costs nothing, so a job that fails two-thirds of the way through can run again over the same inputs without a checkpoint table to get out of step with reality. And it is checkable, which is the half people skip — did all 2,200 files land becomes a set difference between the keys the catalogue implies and the keys the bucket actually contains, rather than a reconciliation against a list somebody has to maintain by hand.
Commerce here, media there
The division this settles on: Shopify is the commerce layer, and specialised infrastructure handles the heavy media. Shopify holds the product, the price, the entitlement and the structured metadata. Object storage holds the files. The key ties them together and neither side needs to know much about the other.
That is a boring architecture and it is the right one, mostly because of what it does not couple. Re-recording a chapter does not touch a product. Rebuilding a product does not move a file. A migration of 2,200 files can run, fail halfway and resume, because the keys are deterministic and re-deriving one costs nothing.
It is the same principle as keeping identity off a mutable key when two systems have to agree about the same product — different domain, same rule: the thing that joins two systems must not be a thing either of them is free to change.
What this does not do
It does not handle entitlement by itself. Shopify knows who bought what; the object store knows nothing about customers, and the gap between them — signed URLs, expiry, how many devices, what happens on a refund — is a real piece of work and the part most likely to be underestimated.
It does not give you a player. Storage is storage. Resumable playback, position sync across devices and offline listening are an application, and if the requirement is a listening experience rather than a delivery mechanism, that is the larger half of the project.
And it does not remove the storage bill. Media outside Shopify is media you are paying for and monitoring, with a lifecycle policy somebody has to own. That cost is usually smaller than the alternative, and it is not zero.
When this needs an engineer
Frequently it does not. One file per product, under 5 GB, delivered on purchase: that is the Digital Products app and it is free, supported and maintained by somebody else. Content hosted on a provider Shopify already integrates with is a link on the product. A dozen titles that rarely change do not need an asset pipeline.
It becomes engineering when the product is a structured set rather than a file, when the catalogue is large enough that the mapping between products and media has to be derivable rather than curated, and when the customer's experience of the product is using it rather than receiving it. That was the case for an audiobook operation moving onto Shopify with thousands of files behind it — the Lantern build sits with our other custom Shopify applications, and it is custom Shopify app development rather than a storefront exercise, because the thing being designed is the relationship between a catalogue and a library.
Send us the store and the symptom.
