Why base44 fails on unsupported dependencies
Base44's unsupported dependency error fires when a package your project allows pulls in a transitive dependency the sandbox blocks, or when code reaches a Node-only API like fs, crypto, or a native binding the browser-shaped runtime does not expose. The package installs cleanly because the direct dep was on the allowlist — the failure surfaces later when the bundler walks the import graph or when the offending line first executes in production. Fix the base44 unsupported dependency error by inspecting the dependency tree with npm ls, identifying the offending transitive package or Node-only API, and choosing one of three paths: swap to a browser-compatible alternative such as bcryptjs for bcrypt or sql.js for sqlite3, polyfill the missing Node API with crypto-browserify, or move the unsupported code to an external backend function the sandbox calls over HTTP.
You added a package. The install completed. The build failed with a message about an unsupported dependency or a module that cannot be resolved. Or worse, the build succeeded and a request crashes the moment it touches the affected code path with fs is not defined or crypto.randomBytes is not a function.
This is not the same error as a flat package rejection at install time. The platform allowed the direct dependency. Something inside it — a transitive package, a Node-only API call, a native binary — is what the sandbox refuses. The first-pass allowlist check looks at what you declared. The second-pass build and runtime checks look at what the code actually does. The gap between them is where this error lives.
The pattern repeats across dozens of audits. A team adds bcrypt to hash passwords. Install passes. Build fails because bcrypt ships a native .node binary the sandbox cannot load. Swap to bcryptjs and the next deploy works. A team adds node-rsa for a crypto routine. Install passes. The browser-shaped bundler refuses to resolve crypto because the polyfill is not wired in. Swap to jose and the problem disappears. The errors look mysterious until you see the same shape three times.
What causes the unsupported dependency error in base44
Four distinct root causes produce the same error class. The fix for each is different, so identifying which one you are seeing is the first real step.
1. Transitive dependency on the block list
Your direct dependency is allowed. The package it depends on is not. The Base44 allowlist enforces declared imports, but the bundler resolves the full graph at build time and will refuse a transitive package that violates the policy. The error message names the transitive package — not the direct dependency you installed — which makes the connection hard to see without npm ls.
A common shape: you install feathers-authentication (allowed) and the build fails on bcrypt (blocked). The package author chose bcrypt as a peer for password hashing. You never imported bcrypt directly. The bundler still walks to it and breaks.
2. Node-only API in the browser sandbox
The sandbox is browser-shaped. Many Node globals do not exist there: fs, child_process, cluster, dgram, dns, net, tls, process.binding, require('os'). Even partially-supported modules like crypto, buffer, and stream need explicit polyfills, and the polyfills are not always wired in by default.
A package that calls fs.readFileSync to load a config file at import time will pass install, fail the build, and produce a Module not found: 'fs' error. A package that calls process.binding('util') to detect its environment will fail at runtime with process.binding is not a function. These look like bugs in your code; they are usually bugs in your dependency's assumption about the host.
3. Native binary dependency
Packages with native bindings — sharp, bcrypt, sqlite3, canvas, ffi-napi, node-gyp-built modules — ship a compiled .node file or invoke node-gyp at install time to build one. The sandbox does not run node-gyp and cannot load a .node binary in a browser-shaped runtime. The error here is usually a gyp ERR! build error at install time or a Module did not self-register at runtime.
Sharp is the most common offender for image processing. Bcrypt is the most common for password hashing. Both have pure-JS alternatives (@resvg/resvg-js or browser-image-compression for sharp, bcryptjs for bcrypt) that resolve the problem in a single import swap.
4. ESM and CJS conflict
A package ships only as ESM and the bundler resolves it as CJS, or vice versa. The error is usually require is not defined (CJS code reaching ESM-only output) or Cannot use import statement outside a module (ESM code reaching CJS-only output). Base44's bundler is biased toward ESM for browser-shaped output; older CJS-only packages frequently misfire.
A user on the feedback board described the pattern: install passes, the runtime first-load throws require is not defined, and the workaround was to wrap the import in a dynamic import() rather than a top-level require.
Sources: feedback.base44.com posts on "unsupported dependency", "bcrypt fails", and "sharp not working", Node.js compatibility notes in the Vite docs (vitejs.dev/guide/troubleshooting), docs.base44.com on platform runtime constraints.
How to confirm an unsupported dependency error in base44 (reproduction)
Run these steps in order. Each one rules out a possibility before the next.
- Open the build log or browser console and copy the full error verbatim. Note the exact module name, the file path the bundler was resolving, and whether the error fired at install, build, or runtime.
- Re-run
npm installfrom the project root. If the install fails with a rejection error, you are in the install-time rejection mode, not this one — see the npm package rejected fix. - If install passes but build or runtime fails, run
npm ls <offending-package>from the project root. Note the dependency chain printed. The grand-parent at the top is the package you actually need to replace or contain. - Read the error class.
Module not foundon a sub-package is a transitive issue.fs is not defined,crypto.randomBytes is not a function, orprocess.bindingerrors are Node-API misses.gyp ERR!or.nodebinary errors are native bindings.require is not definedorCannot use import statementis an ESM/CJS conflict. - Run
npm run buildlocally and confirm the error reproduces in the production build. The dev server resolves dependencies more permissively than the production build — a problem that only shows in prod is almost always a build-time resolution issue. - If the package is one of the well-known offenders (bcrypt, sharp, sqlite3, canvas, pg-native, node-rsa), the fix is a known swap. Jump straight to the fix section below.
How to fix base44 unsupported dependency error — step-by-step
Apply the fix for the cause you confirmed. Applying all four blindly will leave the project in a broken intermediate state.
Fix for cause 1 — swap the transitive dependency
If npm ls shows that a direct dependency you control depends on the blocked package, look for an alternative version or a fork that does not pull in the offender. For feathers-authentication depending on bcrypt, switching to @feathersjs/authentication-local with an explicit bcryptjs peer dep resolves the issue. The pattern: pick the parent package, identify the swap, install the swap, and remove the parent.
If no clean swap exists, you can sometimes override the transitive version via npm's overrides field in package.json. This forces npm to use a compatible version of the transitive package even when the parent declares a different one.
{
"overrides": {
"bcrypt": "npm:bcryptjs@^2.4.3"
}
}
Whether Base44's bundler honors overrides depends on the platform's package-resolution rules. Test in a non-production project first.
Fix for cause 2 — swap to a browser-compatible alternative
The common swaps are:
// BEFORE — Node-only crypto
import bcrypt from "bcrypt";
const hash = await bcrypt.hash(password, 10);
// AFTER — pure-JS, browser-compatible
import bcrypt from "bcryptjs";
const hash = await bcrypt.hash(password, 10);
// BEFORE — Node-only JWT library
import jwt from "jsonwebtoken";
const token = jwt.sign(payload, secret);
// AFTER — pure-JS JWT for both Node and browser
import { SignJWT } from "jose";
const token = await new SignJWT(payload)
.setProtectedHeader({ alg: "HS256" })
.sign(new TextEncoder().encode(secret));
// BEFORE — Node-only image processing
import sharp from "sharp";
const buffer = await sharp(input).resize(800).toBuffer();
// AFTER — WASM-based, works in browser sandbox
import { Resvg } from "@resvg/resvg-js";
const buffer = new Resvg(input, { fitTo: { mode: "width", value: 800 } })
.render()
.asPng();
// BEFORE — native SQLite driver
import Database from "better-sqlite3";
const db = new Database("app.db");
// AFTER — WASM SQLite for browser-shaped runtimes
import initSqlJs from "sql.js";
const SQL = await initSqlJs();
const db = new SQL.Database();
The swap table is short because the offenders are predictable. Memorize four or five common ones and most unsupported-dependency errors resolve in under ten minutes.
Fix for cause 3 — polyfill the missing Node API
When no swap exists but the underlying API is pure JavaScript, install the polyfill and wire it into the bundler. Common polyfills:
| Node module | Polyfill package |
|---|---|
crypto | crypto-browserify |
buffer | buffer |
stream | stream-browserify |
path | path-browserify |
util | util |
process | process |
Wiring the polyfill into the bundler usually means adding a resolve.alias or a define block in the build config. Base44 does not expose Vite or webpack config directly, so this step depends on whether the agent will accept and apply a build-config override. Frame the prompt as "add an alias from crypto to crypto-browserify in the bundler resolve config." If the agent declines or the override does not take effect, polyfilling is not viable on this platform and you should jump to the off-platform path.
Polyfills are slow. A crypto-browserify hash is 10-50x slower than the Node-native equivalent. Use this for low-frequency code only — config decoding at startup is fine; per-request password hashing is not.
Fix for cause 4 — resolve the ESM/CJS conflict
For require is not defined errors, the package is CJS-only and the bundler is producing ESM. Wrap the import in a dynamic import():
// BEFORE — top-level require, fails in ESM output
const pkg = require("legacy-cjs-package");
// AFTER — dynamic import that the bundler can handle
const pkg = await import("legacy-cjs-package").then((m) => m.default ?? m);
For Cannot use import statement outside a module errors, the package is ESM-only and the bundler is producing CJS. Check whether the package publishes a CJS build alongside ESM (most do — look at the package's exports field in its package.json). If not, the only fix is finding an alternative or asking the package author to publish a dual build.
Move unsupported code to an external backend function
When no swap, polyfill, or import dance works, the code does not belong in the sandbox. Move it to an external worker exposed over HTTP.
// In your Base44 app — call an external worker instead of running locally
const response = await fetch("https://image-worker.yourdomain.com/resize", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: inputUrl, width: 800 }),
});
const { resizedUrl } = await response.json();
// On the external worker (Vercel function, Cloudflare Worker, Render service)
import sharp from "sharp"; // Works here — full Node runtime
export async function POST(req: Request) {
const { url, width } = await req.json();
const input = await fetch(url).then((r) => r.arrayBuffer());
const output = await sharp(Buffer.from(input)).resize(width).toBuffer();
// Upload output to storage, return the URL
return Response.json({ resizedUrl: await uploadAndReturnUrl(output) });
}
This pattern adds operational surface — one more service to deploy and monitor — but unblocks anything the sandbox refuses. It is also future-proof against the next allowlist tightening, because anything off-platform is independent of Base44's evolving restrictions. The decoupling pattern is the same shape as the vendor-lock-in escape, just for a single unsupported dependency instead of the whole SDK surface.
Pin the swap and document the constraint
Once the swap works, pin the exact version in package.json and add a comment near the import explaining why this package was chosen. The AI agent will re-suggest the original Node-only package on the next nearby prompt unless the constraint is visible in the code itself. This is the same regression pattern documented in the AI agent regression fix — without an in-code signal the agent reverts your decision within days.
// REQUIRED: bcryptjs not bcrypt — the sandbox blocks native bindings.
// Do not switch back to bcrypt even if the agent suggests it.
import bcrypt from "bcryptjs";
How long does it take to fix base44 unsupported dependency error?
Timeline depends on which cause you hit.
- Transitive dependency swap (cause 1): 15-30 minutes when a clean alternative exists. Longer when you need to fork the parent package or wire up an
overridesblock and retest. - Browser-compatible swap (cause 2): 10-20 minutes for a well-known offender like bcrypt or sharp. The swap is one import change and a rebuild. Most projects with a documented swap path are back online within half an hour.
- Polyfill (cause 3): 30-90 minutes including testing. Polyfilling requires bundler config access, which on Base44 means working through the agent. Expect 2-4 failed attempts before the override sticks. If the platform refuses the config change, escalate to the off-platform path instead.
- ESM/CJS resolution (cause 4): 20-45 minutes. Dynamic
import()wrappers are usually fast. Finding a dual-build alternative when the package is ESM-only takes longer. - Off-platform worker (any cause): 2-6 hours for a first-time deploy. Faster on subsequent unsupported deps because the worker infrastructure is reusable — one Cloudflare Worker can host five different unblock endpoints.
Most teams resolve their first unsupported-dependency error in under an hour. Recurring errors usually mean the agent is re-introducing the same Node-only package on every prompt — fix the in-code comment and the regression stops.
DIY vs hire decision
DIY this if: The offending package is a well-known offender (bcrypt, sharp, sqlite3, canvas, jsonwebtoken, node-fetch), a clean swap exists, and you are comfortable with npm ls, package-lock.json, and editing imports. Most teams solve this themselves once they have seen the pattern once.
Hire help if: You have multiple unsupported-dependency errors stacking up, the polyfill path keeps getting rejected by the platform, or the right answer is an off-platform worker and you do not have prior experience deploying one. The fix-sprint engagement maps the dependency tree, applies the swap or builds the worker, and pins the constraint in code so it does not regress.
If the same error class keeps recurring on different packages, the underlying problem is that the application is reaching for Node-runtime capabilities the sandbox cannot host. At that point the right conversation is not about this dependency but about whether the workload belongs on the platform at all. See the vendor-lock-in fix for the decoupling pattern that precedes a partial migration.
Need help with a complex dependency tree?
For one-off unsupported-dependency errors, our fix-sprint engagement maps the tree, applies the swap, validates the production build, and pins the constraint so the agent does not regress. For projects with multiple stacking unsupported deps or an architectural dependency mismatch, the right shape is a small audit followed by an off-platform worker buildout.
Start a fix sprint for an unsupported dependency error
Related problems
- Base44 npm package rejected error — the install-time sibling failure where the direct dependency is blocked before any code touches it.
- Vendor lock-in via SDK dependency — the decoupling pattern when sandbox restrictions force you to move work off-platform.
- Backend functions 404 in production — the routing problem you will hit when an external worker is wired in and the calling function is misrouted.