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
Prefix every package. Replace bare names with
npm:and a version:// before import Stripe from 'stripe' // after import Stripe from 'npm:[email protected]'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'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.Use
node:for Node built-ins. Libraries that needBufferorcryptofrom Node work through Deno's compatibility layer; in your own code import them explicitly, for exampleimport { Buffer } from 'node:buffer'.Write full specifiers in the code itself. Don't depend on a
deno.jsonor import map to turn bare names into packages; put the fullnpm:,jsr:orhttps:specifier in eachimportline.Avoid packages that can't run here. Native add-ons (packages that compile C or C++ code, such as
sharporbcrypt), packages that need a real filesystem, and ones that start child processes won't work. Look for a pure JavaScript or WebAssembly alternative, such asbcryptjsinstead ofbcrypt.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.