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

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
Aug 18, 2026 · 7 min read
Cutting search latency from 40s to under 6s across 200,000+ SKUs
The exact sequence I used to diagnose and fix a marketplace search that was timing out at 40 seconds: EXPLAIN ANALYZE first, indexing second, Redis last — in that order.
Jun 9, 2026 · 8 min read
Building Haira: multi-agent resume screening with FastAPI and pgvector
What it actually took to move Haira from a single prompt call to a reliable multi-step agent pipeline: structured function calling, pgvector semantic matching, and SSE streaming for live interviews.