Lambda "Unable to Import Module": Python and Node Fix

Logeshwaran.C

The fastest fix for Runtime.ImportModuleError: Unable to import module 'lambda_function' in Python, or Cannot find module 'index' in Node.js, is almost never "go install the missing library." Check your handler setting first, then your zip's folder structure, then — only if those are correct — your dependencies. The error message names a library it can't find, but in most real cases that library is already there. It's either sitting in the wrong folder inside your deployment package, or it was compiled for the wrong CPU architecture and Lambda can't load the binary at all.

⚡ Quick Answer

Python → check the Handler value under Configuration > General configuration. It must read filename.function_name, and filename.py must sit at the root of your zip.

Node.js → same idea: Handler must be filename.exportedMethod, matching a real file at the zip's root (not inside a subfolder).

Package installed but still "No module named X" → you built it on the wrong OS or CPU architecture, or put it in the wrong path inside a layer. See the packaging fix or layer folder paths.

If you only read one section, make it the diagnostic table — it will tell you which of the five usual causes you actually have.

Jake's confirmation-text Lambda broke on a Friday afternoon, right as a walk-in customer was standing at his counter waiting for a "your phone is ready" SMS. He'd pushed a small code change that morning. Nothing about SMS logic. Just a new library for formatting phone numbers. The function had worked fine for six months, and now it wouldn't even start.

That's the part that throws people. This error fires before your code runs a single line — it happens while Lambda is still trying to load the file, not while your logic is executing. So the fix is rarely inside your business logic. It's in how the pieces are arranged.

What "Unable to import module" actually means

Every Lambda invocation starts the same way behind the scenes: Lambda loads your deployment package, finds the file named in your function's Handler setting, and imports it as a module before calling the specific function inside it. errorType: Runtime.ImportModuleError means that loading step failed — Python (or Node) tried to run import (or require) on your handler file, or on something your handler file itself imports, and hit a dead end.

The errorMessage tells you exactly which name it choked on. If it says No module named 'lambda_function', Lambda can't find the file your Handler setting points to at all. If it says No module named 'requests' or No module named 'pyarrow.lib', it found your handler file just fine — the problem is one level deeper, inside something your code tries to import.

🙋‍♂️ Jake's Reality Check

"I didn't touch the handler file. Why is it suddenly saying it can't find my own code?"

Because the zip you uploaded doesn't have that file where Lambda expects it — not because Lambda forgot how to find files it found yesterday. A re-zip, a build-tool change, or a folder nested one level too deep is the usual reason.

Quick diagnostic: which of these five things is happening

Read the exact module name in the error, then match it here before you change anything.

The error names Likely cause Where to look
Your own handler file (e.g. lambda_function, index) Handler setting doesn't match the actual file name, or the file isn't at the zip's root Python fix / Node fix
A third-party package (requests, numpy, pyarrow, moviepy...) Not included in the package, or built for the wrong OS/architecture Packaging fix
A sub-attribute like pyarrow.lib or a compiled .so file Architecture mismatch — built on macOS/Windows, deployed to Amazon Linux Packaging fix
A package you know is in a layer Wrong folder inside the layer zip (not python/ or nodejs/node_modules) Layer paths
Node: literally 'index', or a Cannot-find-package error ESM/CommonJS mismatch, or file zipped inside a subfolder ESM traps

Fixing it in Python

The Handler setting for a Python function is written as filename.function_name — the name of the file (without the .py extension), a dot, then the name of the function inside it that Lambda should call. The console's default is lambda_function.lambda_handler, which means Lambda is looking for a file called lambda_function.py sitting directly at the root of your deployment package, containing a function called lambda_handler.

Step-by-step: matching your handler to your file

  1. Check the current value: aws lambda get-function-configuration --function-name my-function --query 'Handler'.
  2. Open your zip and confirm a file with that exact name exists directly at the top level — not inside src/, not inside a project folder created when you zipped the whole directory instead of its contents.
  3. If your file is app.py with a function called handler, set the Handler value to app.handler, not lambda_function.lambda_handler.
  4. If your handler lives in a subdirectory, use dots for the path: a file at handlers/user_handler.py with a function handle is set as handlers.user_handler.handle. Every directory in that path needs an __init__.py file, or Python's regular package import won't find it.
  5. Update the setting: aws lambda update-function-configuration --function-name my-function --handler app.handler.

If the handler name and file line up and you're still getting the error, the module it's naming is a dependency, not your own code — that's the next section.

Python: package installed but still "No module named X"

Lambda's Python runtime ships with a small standard set of libraries — the AWS SDK (boto3/botocore) and the core standard library. Anything else, including popular packages like requests, numpy, or pandas, has to travel with your deployment package or arrive through a layer. If you pip install a package on your own laptop and zip it up, one of two things commonly goes wrong.

⚠️ What this actually breaks

Packages with compiled C extensions (numpy, pyarrow, Pillow, cryptography) are built as binaries for a specific operating system and CPU architecture. A wheel built on your Mac or Windows machine will not run inside Lambda's Amazon Linux execution environment. You'll see errors that look like a missing module — No module named 'pyarrow.lib' — even though the package "installed" without complaint on your own machine.

The reliable way around this is to install with the --platform flag targeting Lambda's actual environment, or to build inside a container using AWS's own Lambda base image so the binaries are compiled correctly the first time.

Step-by-step: an architecture-correct layer

  1. Pick the right platform tag for your function's architecture: manylinux2014_x86_64 for an x86_64 function, manylinux2014_aarch64 for arm64.
  2. Create the folder and install for that platform and Python version: mkdir -p lambda-layer/python && cd lambda-layer/python && pip3 install --platform manylinux2014_x86_64 --target . --python-version 3.13 --only-binary=:all: numpy.
  3. Zip the python folder (not its contents individually): cd .. && zip -r layer.zip python.
  4. Publish it: aws lambda publish-layer-version --layer-name numpy-layer --zip-file fileb://layer.zip --compatible-runtimes python3.13 --region us-east-1.
  5. Attach the layer to your function, matching its architecture (x86_64 or arm64) to the one you built for. A layer built for the wrong architecture reproduces the exact same import error.

Alternatively, build using AWS's Amazon Linux–based Lambda Docker image — Docker guarantees the binaries match the execution environment regardless of what OS you're developing on, so this sidesteps the platform-tag guesswork entirely.

✅ Why this is the one to use

Building inside the official Lambda Python Docker image is the most dependable option because it matches the exact OS your function runs on, rather than approximating it with a platform tag. It costs a few extra minutes of setup and saves the recurring "works on my machine" cycle.

Fixing it in Node.js

Node's Handler setting follows the same shape as Python's: file name, a dot, then the exported method name. The default the console suggests is index.handler, meaning Lambda expects a file called index.js or index.mjs, exporting a method called handler, sitting at the top level of your package.

A typical, working Node.js Lambda project looks like this at the root of the zip:

  • index.mjs — contains the main handler
  • package.json — project metadata and dependencies
  • package-lock.json — dependency lock file
  • node_modules/ — installed dependencies

Step-by-step: fixing "Cannot find module 'index'"

  1. Unzip your deployment package locally and look at the top level. If index.js (or your real handler file) sits one folder down — for example inside a folder that got zipped along with its contents — Lambda won't find it.
  2. Re-zip so the files themselves are at the archive's root: cd your-project && zip -r ../function.zip . — note the dot, meaning "this directory's contents," not the folder name itself.
  3. In the console, confirm under Code that index.js (or your file) actually appears at the top level after upload — not nested.
  4. Confirm the Handler setting matches: file name without extension, dot, exported function name.

If the file is present and correctly named and you're still stuck, the next most common Node.js cause is a mismatch between how the file is written and how Lambda is loading it — covered next.

Node.js: ESM vs CommonJS traps

Node.js supports two module systems, and Lambda needs to know which one your file uses before it can load it. A .mjs file is always treated as an ES module. A .cjs file is always CommonJS. A plain .js file defaults to CommonJS — unless the nearest package.json sets "type": "module", in which case Node treats every .js file in that project as an ES module.

Mixing these up doesn't always produce Runtime.ImportModuleError — sometimes it surfaces as a close cousin, Runtime.UserCodeSyntaxError: SyntaxError: Cannot use import statement outside a module. That happens when your file uses import/export syntax but Node is loading it as CommonJS, because there's no "type": "module" in the package.json that shipped with the function and the file isn't named .mjs.

🕐 What changed between versions

  • Before: the Node.js 18 runtime was a common target for these functions; it reached its scheduled Lambda deprecation on September 1, 2025.
  • Now: Node.js 22 and 24 are the current generally available runtimes, with Node.js 26 available as a public preview (not yet supported for production workloads).
  • What that means for you: functions written years ago on an older runtime, then quietly moved to a newer one during a routine update, can start hitting module-loading errors that weren't there before — not because your code changed, but because the assumptions your code made about the runtime did.

Fixes, in order of how much they touch: add "type": "module" to the package.json you deploy with the function if you want to keep import syntax; rename the file to .mjs if you can't touch package.json; or convert the file to require()/module.exports if you'd rather stay on CommonJS. Don't do more than one of these — picking two half-measures is how a working file turns into a broken one.

Lambda layers: the folder structure that actually works

A layer is just a zip file that Lambda unpacks into /opt inside the execution environment. Each runtime already has specific folders under /opt baked into its search path, so your layer's dependencies have to land in one of those exact folders — not just anywhere convenient.

Runtime Required path inside the layer zip
Node.jsnodejs/node_modules (also version-specific paths like nodejs/node22/node_modules)
Pythonpython or python/lib/python3.x/site-packages
Javajava/lib
Rubyruby/gems/3.4.0 or ruby/lib
All runtimesbin (executables) and lib (shared libraries)

A Python layer holding requests, for example, needs a top-level folder literally named python, with the package folders directly underneath it — python/requests/, python/boto3/, and so on. Zip the folder one level too high or too low, and Lambda's runtime will genuinely not see it, producing the exact same No module named message as if you'd never installed anything.

One more thing worth saying plainly, because it's popular advice that's wrong: adding more layers is not a substitute for fixing a folder-path or architecture problem. A misplaced package inside a correctly-attached layer still fails the same way. Check the path before you check whether the layer is attached.

Container image functions: a different flavor of the same error

If your function deploys as a container image instead of a zip, the same Runtime.ImportModuleError can appear for a slightly different reason: the CMD in your Dockerfile doesn't match where your code actually landed inside the image, or your local test invocation via the Lambda Runtime Interface Emulator points at the wrong entry point.

When testing locally with Docker, the command you run against the Lambda base image needs to name the handler exactly the way it would in the console — for example /usr/local/bin/python -m awslambdaric lambda_function.handler for a Python image, or /usr/local/bin/npx aws-lambda-ric index.handler for a Node.js image. If your Dockerfile's COPY instruction renamed or relocated the file (a common slip when simplifying a Dockerfile from a multi-file COPY app/* down to a single COPY app.py), the path in that command needs to change to match, or you'll get an import error that has nothing to do with your dependencies at all. The same logic applies to the Handler value if you're building the image for standard Lambda invocation rather than local testing — it still has to name a file that genuinely exists at the location your Dockerfile put it.

Why this suddenly broke without a code change

By default, Lambda applies runtime updates to functions using managed runtimes automatically, migrating your function to a newer runtime version through what AWS calls a two-phase rollout. You don't request this or approve it — it happens in the background because Lambda, not you, is responsible for patching the managed runtime. That's usually invisible — until a dependency your layer bundled makes an assumption the older runtime version happened to satisfy and the newer one doesn't.

The current supported Python runtimes are 3.10 through 3.15 (3.15 in public preview), and current Node.js runtimes are 22, 24, and 26 (26 in public preview). Python 3.10 is scheduled for deprecation on October 31, 2026 — worth checking now if that's what your function still targets, since a deprecated runtime stops receiving security patches even though it keeps running.

🙋‍♂️ Jake's Reality Check

"So AWS changed something and just... broke my function? That doesn't seem fair."

Ethan doesn't sugarcoat it: "It's in the fine print you agreed to when you didn't pin a runtime version. Auto mode is the default because most functions never notice the update. Yours noticed because a layer built for an older Python version made an assumption the update didn't honor." If that trade-off bothers you, Lambda's runtime management controls let you switch to Function-update mode — the runtime only advances when you deploy — but then patching becomes your job to track, not Lambda's.

When you've done everything above and it's still broken

Work through these in order — each one rules out a cause the earlier fixes above don't touch:

  1. Read the full errorMessage in CloudWatch Logs, not just the errorType. The exact module name it names is the single most useful clue you have, and it's easy to skim past when you're already frustrated.
  2. Confirm you're pointed at the layer version you think you are. Republishing a fixed layer creates a new version ARN — Lambda doesn't move your function to it automatically. Check Configuration > Code > Layers and confirm the version number matches your latest publish.
  3. Check for a circular import inside the dependency itself. Two modules importing each other during a package's own initialization can surface as an import error that looks packaging-related but is actually a bug in the dependency's own code structure — search the package's issue tracker for "circular import" plus the version you have installed.
  4. Verify the deployment package actually contains everything you meant to include. A zip step that fails partway, or a build script that silently skips a folder, can produce a package that looks the right size but is missing files. Unzip it locally and check.
  5. Rule out that this is an import error at all. If CloudWatch Logs shows the function never reaches the import step, or fails with a different errorType entirely, you're looking at an execution-role or configuration problem, not a module-loading one — don't keep re-packaging code that was never the issue.

What we can't tell you from here: without your CloudWatch Logs in front of us, we can't say which of the five causes above applies to your specific function. That errorMessage text is worth reading in full before touching anything else.

Errors that look related but aren't

A few searches land here that describe a different problem entirely. Worth separating out so you don't spend time fixing the wrong thing:

"Cannot assign to lambda"

This is a plain Python syntax error, unrelated to AWS Lambda the service. It happens when code tries to assign a value to the result of a lambda expression — Python's anonymous-function keyword, which has nothing to do with the AWS product beyond sharing a name. If you see this, look at the line the traceback points to for a lambda expression being used on the left side of an =.

"No module named 'importlib.metadata'" or 'importlib_metadata'

This shows up when a dependency expects a Python version where importlib.metadata is part of the standard library (Python 3.8 and later) but the environment resolving it is older, or when a package that vendors its own importlib_metadata backport gets confused about which one to use. On Lambda specifically, it tends to appear in layers built for one Python version and attached to a function running a different one — matching your layer's --python-version flag to your function's actual runtime avoids it.

Both of these are real, common searches — but neither one is fixed by anything in the sections above, so if this is what you're actually facing, that's the direction to look instead.

Frequently asked questions

Why does Python say "Unable to import module" but Node says "Cannot find module"?

They're the same underlying failure — Lambda's bootstrap process couldn't load your handler file or a dependency — described in each language's own native error phrasing. Python's import system reports ModuleNotFoundError-style messages; Node's require/ESM loader reports Cannot find module. Lambda wraps both as errorType: Runtime.ImportModuleError.

Why does the error name a library I know I installed?

Because "installed" on your own computer and "present in the deployment package Lambda actually runs" are two different things. If you didn't zip your virtual environment's site-packages (or the equivalent node_modules) alongside your code, or if you installed a compiled package for the wrong operating system, Lambda never sees it — even though pip or npm reported success locally.

Do I need to reinstall dependencies for arm64 versus x86_64?

Only for packages with compiled components — pure-Python or pure-JavaScript packages with no native code work on either architecture. For compiled dependencies, yes: a binary built for x86_64 will not load on an arm64 function and vice versa. Match the --platform flag (or your Docker build's target platform) to your function's configured architecture.

Can one Lambda layer serve multiple functions?

Yes — that's the main reason layers exist. Publish the layer once, then attach it to as many functions as need it, as long as each function's runtime is listed in the layer's compatible-runtimes list and its architecture matches what the layer was built for. A practical pattern: if three functions in your account all call the same internal API client library, put that client in one layer, publish it once, and attach it to all three. When you fix a bug in the client, republish the layer and update each function to reference the new layer version ARN — the functions' own code never has to change.

Can a layer serve both Python and Node.js functions?

A single layer zip can technically hold both a python/ folder and a nodejs/node_modules folder at the same time, and Lambda will only load the folder relevant to the runtime of the function attaching it — a Python function ignores the nodejs folder entirely, and vice versa. Most teams still keep them as separate layers, mainly because the compatible-runtimes list and the versioning lifecycle rarely line up between two unrelated language ecosystems, not because Lambda enforces separation.

Why did this break without me changing any code?

By default, Lambda applies runtime updates to your function automatically through a two-phase rollout — this is the standard, recommended behavior, not a bug. A newer patch of your runtime, or a scheduled deprecation of the version you were on, can expose an assumption a dependency made about an older runtime. Check your function's runtime identifier against the current supported list, and rebuild any layers that bundle compiled dependencies if you've recently moved to a new major runtime version.

Does upgrading off Python 3.9 or 3.10 fix this?

It can, if the root cause is version drift, but it isn't guaranteed. Python 3.9 reached its Lambda deprecation date in December 2025, and Python 3.10 is scheduled for October 31, 2026. Upgrading is worth doing regardless of this specific error, but rebuild and re-test any layers with compiled dependencies against the new version rather than assuming they'll carry over unchanged.

Do I need "type": "module" in my Node.js package.json?

Only if your handler file uses .js with import/export syntax. If your file is named .mjs, Node already treats it as an ES module regardless of package.json. If you're using require() and module.exports, leave package.json without that field, or your CommonJS syntax will itself start failing.

Is "cannot assign to lambda" the same bug as this?

No. That's a Python language syntax error about the lambda keyword for anonymous functions — it has nothing to do with AWS Lambda the service, despite the shared name. Look for a lambda expression being used on the left-hand side of an assignment in the line the traceback points to.

Is "Cannot use import statement outside a module" the same as this error?

It's closely related but reports as a different errorType — Runtime.UserCodeSyntaxError rather than Runtime.ImportModuleError. It means Node parsed your file as CommonJS while your file uses ES module syntax. The fix is the same family as the ESM/CommonJS section above: match the file extension, package.json type field, and syntax style to each other.

What does "No module named 'importlib.metadata'" mean?

importlib.metadata is part of Python's standard library from version 3.8 onward. This error usually means a dependency was resolved against a different, older Python version than the one it's actually running under — commonly because a layer's packages were installed with a mismatched --python-version flag. Rebuild the layer targeting the exact runtime version your function uses.

How do I share code between Lambda functions?

Layers are the standard mechanism. Package your shared code the same way you'd package a dependency — under python/ for Python or nodejs/node_modules for Node.js — publish it as a layer, and attach it to every function that needs it. Update the layer once and republish a new version when the shared code changes, then point each dependent function at the new version. There's no way to have functions auto-follow a layer's "latest" version — the ARN you attach is pinned to a specific version until you update it.

Can this error happen with a container image function too?

Yes, though the cause is usually different from a zip-based function: a mismatch between the file's actual location inside the built image and the CMD, ENTRYPOINT, or Handler value referencing it. For example, if your Dockerfile copies app.py to the function's root but your invocation command still references lambda_function.handler from an earlier version of the Dockerfile, you'll get this exact error even though the image builds successfully. Check the path in your COPY instruction against the path in whichever command or setting invokes the handler.

My handler is in a subfolder — how do I set the Handler value?

Use dots to represent the folder path. In Python, a file at handlers/user_handler.py with function handle is set as handlers.user_handler.handle, and every directory in that path needs an __init__.py file. In Node.js, the equivalent uses forward slashes before the final dot, such as handlers/userHandler.handle.

Do I need __init__.py files in every folder for Python?

For a regular package import to resolve reliably, yes — every directory between your zip's root and your handler file needs one. Python 3 does technically support namespace packages without them, but relying on that in a Lambda deployment package invites exactly the kind of intermittent import failure this article is about, so it's not worth the risk.

Where do I actually see this error message?

In CloudWatch Logs for the function's log group, and in the Test tab of the Lambda console if you run a test invocation — the full JSON with errorType, errorMessage, and a stack trace appears there immediately, since this failure happens before your handler code has a chance to log anything of its own.

Revision note. Written August 2026, covering the current Lambda Python and Node.js managed runtimes (Python 3.10–3.15, Node.js 22–26) and both .zip and container-image deployment. This will need a refresh once Python 3.15 and Node.js 26 leave public preview and become the defaults teams reach for first. If you're staring at this error with a customer waiting or a deploy deadline pressing in, hang in there — nine times out of ten it's a folder, not a rewrite.

Related