Import errors with esm.sh or npm packages

Fix module-not-found, bare specifier and esm.sh errors in SupaHost Edge Functions. Use npm: and jsr: specifiers, pin versions, and know which packages can't run in Deno.

What you see

A deploy ends in error, or the function fails to boot with 503 BOOT_ERROR, and the logs or a local deno check show something like:

error: Relative import path "stripe" not prefixed with / or ./ or ../
error: Module not found "https://esm.sh/[email protected]/dist/index.js"

Or the import works, but the package throws at runtime because it expects a browser or a Node API that isn't there.

Why it happens

Edge Functions run on Deno, which resolves imports by explicit specifier. A bare name such as 'stripe' means nothing unless something maps it to a package. Functions on SupaHost support three kinds of remote import:

Specifier Example Notes
npm: npm:[email protected] Deno's built-in npm support. Usually the best choice.
jsr: jsr:@std/encoding@1 Packages from the JSR registry.
https: https://esm.sh/[email protected] Any URL that serves ES modules, including CDNs like esm.sh.

esm.sh converts npm packages on the fly. That's convenient, but the output can change between builds, some packages convert badly, and an outage on the CDN becomes an outage for your deploy.

How to fix it

  1. Prefix every package. Replace bare names with npm: and a version:

    // before
    import Stripe from 'stripe'
    // after
    import Stripe from 'npm:[email protected]'
    
  2. Prefer npm: over esm.sh for packages on npm. Deno installs the real package, so you avoid conversion problems:

    import { createClient } from 'npm:@supabase/supabase-js@2'
    
  3. Pin versions. npm:[email protected] always resolves to the same code. An unpinned import can pick up a breaking release the next time you deploy.

  4. Use node: for Node built-ins. Libraries that need Buffer or crypto from Node work through Deno's compatibility layer; in your own code import them explicitly, for example import { Buffer } from 'node:buffer'.

  5. Write full specifiers in the code itself. Don't depend on a deno.json or import map to turn bare names into packages; put the full npm:, jsr: or https: specifier in each import line.

  6. Avoid packages that can't run here. Native add-ons (packages that compile C or C++ code, such as sharp or bcrypt), packages that need a real filesystem, and ones that start child processes won't work. Look for a pure JavaScript or WebAssembly alternative, such as bcryptjs instead of bcrypt.

  7. Check locally before deploying:

    deno check index.ts
    

On SupaHost

The deploy step bundles your function on the instance and resolves every import, so a bad import shows up as a deploy error and the previous version keeps serving. The bundle also warms the module cache, so the first request after a deploy doesn't have to download dependencies. Relative imports between your own files work as usual (./_shared/cors.ts); a function can have up to 50 files and 1 MB of source in total, counted over the files you upload.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us