FlowKit

Installing external npm modules in n8n's Code node

Published 29 July 2026 · 4 min read

Pasting a snippet found online into an n8n Code node, with a require('axios') or an import dayjs from 'dayjs', almost always produces the same error: Cannot find module 'axios'. This isn't a bug — it's a deliberate restriction, and lifting it correctly means understanding two environment variables, a nuance tied to Task Runners, and a real security trade-off before typing NODE_FUNCTION_ALLOW_EXTERNAL=*.

Why n8n blocks imports by default

The Code node runs arbitrary JavaScript (or Python) supplied by whoever built a workflow. On an instance that handles API credentials, customer data, and sometimes publicly exposed webhooks, allowing unrestricted access to require('fs'), require('child_process'), or any npm package would hand full system access to anyone who can edit a workflow. n8n therefore defaults to a restrictive posture: neither built-in Node.js modules nor external npm modules are reachable from the Code node unless explicitly allowed.

Two separate variables, for two categories of modules

n8n distinguishes built-in Node.js modules (like crypto, path, or querystring) from external npm-installed modules (like axios, lodash, or dayjs):

  • NODE_FUNCTION_ALLOW_BUILTIN — a comma-separated list of allowed built-in Node.js modules. Example: NODE_FUNCTION_ALLOW_BUILTIN=crypto,querystring.
  • NODE_FUNCTION_ALLOW_EXTERNAL — a comma-separated list of allowed external npm packages, provided they physically exist in the instance's node_modules. Example: NODE_FUNCTION_ALLOW_EXTERNAL=axios,lodash,dayjs.

Both also accept a wildcard (NODE_FUNCTION_ALLOW_EXTERNAL=*) to allow everything at once — handy in development, to be used cautiously in production, as detailed below. In a docker-compose.yml, this simply goes into the n8n service's environment block:

environment:
  - NODE_FUNCTION_ALLOW_BUILTIN=crypto
  - NODE_FUNCTION_ALLOW_EXTERNAL=axios,dayjs

Restarting the container is enough to apply the change — no need to relaunch a workflow or reopen the Code node for the new list to take effect.

Allowing a module isn't enough if it isn't installed

NODE_FUNCTION_ALLOW_EXTERNAL lifts a restriction, it doesn't install anything. If the package isn't already in the node_modules bundled with the official n8n image (which ships a handful of common utilities like moment or lodash by default), you need to add it physically:

  • Quick, non-persistent fix: docker exec -it <container> npm install axios — works immediately, but the package disappears on the next container redeploy unless it sits on a dedicated volume.
  • Recommended production approach: build a custom Docker image extending the official one with a RUN npm install at build time. The package then survives every redeploy, the same way credentials survive thanks to PostgreSQL backups rather than some separate mechanism.

If your instance uses Task Runners

On more recent setups, Code node execution can be offloaded to a separate Task Runner process rather than the main n8n process — an architecture that isolates user code and helps scale in queue mode. The classic trap: when Task Runners are active, NODE_FUNCTION_ALLOW_BUILTIN and NODE_FUNCTION_ALLOW_EXTERNAL need to be set on the runner process, not on the main n8n process. Setting them only on the n8n service of your docker-compose.yml then has no effect: the module still can't be found, and nothing in the UI clearly explains why, since the main instance starts up normally. If Cannot find module persists after checking the variable syntax, the first thing to verify is where Code node execution actually runs — main process or external runner — before re-checking where the configuration lives.

The real risk behind the wildcard

Setting NODE_FUNCTION_ALLOW_EXTERNAL=* makes the problem disappear for good — which is exactly why it's a bad habit on a production instance. A landmark study by Zimmermann, Staicu, Tenny, and Pradel, presented at the USENIX Security Symposium in 2019, quantified this risk at the scale of the npm ecosystem: it shows that a small number of maintainers and heavily-depended-upon packages concentrate a disproportionate security risk, with a single compromised dependency able to spread transitively to hundreds of thousands of downstream projects (Zimmermann et al., 2019, "Small World with High Risks" — Google Scholar). On an n8n instance that handles API keys and business data, allowing every package present in node_modules — including whatever a future npm install adds without review — extends that attack surface with every dependency installed by mistake or by a rushed collaborator. An explicit list (axios,dayjs, not *) costs a few extra seconds each time a new need comes up, and remains the safer practice as soon as an instance moves beyond a personal test setup.

Before installing a module, check whether you actually need one

A good share of the needs that push toward require() already have a native n8n solution, with no server configuration involved:

  • Date formatting: the Code node and expressions already bundle Luxon, which covers nearly every use case that would otherwise push toward dayjs or moment.
  • Calling a third-party API: the HTTP Request node does the job of an HTTP SDK like axios, with built-in retry and credential handling — no extra dependency to maintain.
  • Reusable, complex logic: a sub-workflow or a community node often packages cleanly what a generic npm module would do more fragilely inside an isolated Code node.

The PDF ingestion pipeline in the RAG Assistant Pack ($119) illustrates this principle: document chunking before vectorization goes through n8n's native Text Splitter node rather than a hand-installed chunking library — one less dependency to secure, for an equivalent result on most text documents.

Wrapping up

NODE_FUNCTION_ALLOW_BUILTIN and NODE_FUNCTION_ALLOW_EXTERNAL unlock the Code node, provided they're set on the right process — main or Task Runner depending on your setup — and the package must physically exist in node_modules, which calls for a custom image to survive redeploys. Reserve the wildcard for test instances, prefer an explicit list in production, and before adding a dependency, check whether an HTTP Request node or a Luxon expression doesn't already cover it. The FlowKit packs follow the same logic: minimizing external dependencies for workflows that stay easier to audit and maintain over time.

FAQ

Frequently asked questions

Does NODE_FUNCTION_ALLOW_EXTERNAL work on n8n Cloud?

No. These environment variables only apply to a self-hosted instance, where you control the n8n process (or the Task Runner process). On n8n Cloud, the Code node stays limited to the built-in modules allowed by default and a fixed subset of already-bundled libraries: you can't add an arbitrary third-party package.

How do I know if my instance uses Task Runners?

Check the N8N_RUNNERS_ENABLED variable in your configuration, or look at the startup logs: an instance with active Task Runners shows a connection being established between the main process and a separate runner process. If you've never configured this explicitly, your instance is probably still running Code node execution in the classic way, inside the main process.

How do I install a package that isn't already in the n8n image?

You need to physically add it to the container's node_modules folder: either through a one-off docker exec (not persistent after a restart unless the folder sits on a dedicated volume), or — the recommended production approach — through a custom Docker image that extends the official n8n image with a RUN npm install at build time, so the package survives every redeploy.

Bundle FlowKit Complet

€269