All articles
Medusa.js · npm · Open Source · TypeScript

Publishing a Medusa.js plugin: what @jaykanjia/medusa-file-cloudinary taught me

Notes from building and maintaining a public Medusa.js file storage adapter — the abstract service contract, the failure modes users actually hit, and what I would design differently now.

Published
Mar 24, 2026
Reading time
6 min read
Level
Deep Dive
Publishing a Medusa.js plugin: what @jaykanjia/medusa-file-cloudinary taught me cover

The gap the plugin fills

Medusa.js ships a local file service by default, which is fine for development but unworkable for production stores that need CDN-backed image delivery and on-the-fly transformations. @jaykanjia/medusa-file-cloudinary implements Medusa's FileService abstract class against Cloudinary, so a store gets responsive image variants and CDN caching without a custom integration.

Designing against an abstract service, not a REST endpoint

Building a Medusa plugin is different from building a typical integration because you are implementing an abstract class contract (upload, delete, getUploadStreamDescriptor, getDownloadStream, and related methods) that Medusa's core calls internally — you don't control the calling convention, so the plugin has to behave correctly under Medusa's existing assumptions about error handling, return shapes, and async behavior, not your own.

The failure modes that only show up in production

Two issues came up repeatedly from real users that never appeared in my own testing: large file uploads timing out on slower connections because the default retry/backoff behavior was too aggressive, and duplicate uploads on retried requests because the original implementation was not idempotent. Both got fixed by adding exponential backoff with jitter on the Cloudinary upload call, and generating a deterministic content-hash-based public ID so a retried upload overwrites rather than duplicates.

Idempotency was the real lesson

Almost every hard bug reported against the plugin traced back to the same root cause: some part of the flow (a network retry, a webhook redelivery, a user double-clicking upload) assumed it would only ever run once. Once uploads were made idempotent by content hash, most of the reported issues stopped recurring even without a related code change.

What I would do differently

I would add contract tests against the abstract FileService interface from day one instead of only integration-testing against a live Cloudinary account. It would have caught the timeout/retry behavior mismatch far earlier, and would make it much cheaper to verify future Medusa core version upgrades don't silently change the expected method signatures.

More articles