How to Build an AWS Lambda Layer for npm Packages (Node.js)
If you need to use a shared npm package—or a package that makes your Lambda deployment too bulky—a Lambda layer can be a tidy solution. A layer is a separate ZIP archive that Lambda extracts into /opt when your function starts.
This guide shows the current, reliable way to build an AWS Lambda layer for Node.js dependencies: put the packages in the directory Lambda expects, build them for Linux when native code is involved, publish the ZIP, and attach the resulting layer version to your function.
Short version: create a nodejs directory, install production dependencies inside it, ZIP that directory, and publish the ZIP as a layer. The top-level folder name matters.
When should you use an npm Lambda layer?
- Several ZIP-deployed Lambda functions share the same dependency.
- You want to keep the function bundle small and update dependencies independently.
- You need to pin a dependency version rather than rely on a runtime-provided SDK.
- You have a large dependency such as a headless-browser package.
Layers are not automatically better. They add version management and can add download or extraction work during cold starts. AWS also limits a ZIP-deployed function to five layers, and container-image Lambdas package their dependencies in the image instead. For a small, single-function dependency, keeping it in the function bundle is often simpler.
The required Node.js layer structure
For a Node.js Lambda layer, package dependencies under nodejs/node_modules. Lambda adds the relevant paths below /opt to Node’s module search path, so a ZIP with node_modules at its root will not work as expected.
my-layer/
└── nodejs/
├── package.json
├── package-lock.json
└── node_modules/
└── your-package/Build an AWS Lambda layer for npm packages
From an empty working directory, install only the dependencies your function needs into nodejs:
mkdir my-node-layer
cd my-node-layer
mkdir nodejs
cd nodejs
npm init -y
npm install --omit=dev package-name
cd ..
zip -r layer.zip nodejsReplace package-name with the library you need. If you are creating a reproducible production layer, commit package.json and the lock file, then use npm ci --omit=dev rather than a fresh unpinned install.
Build on Linux for native dependencies
Pure JavaScript packages are usually straightforward. Packages with native binaries are different: they must be built for the Lambda operating system and the same CPU architecture as the function. A layer built on macOS or Windows may install successfully but fail at runtime with a module or binary error.
The safe approach is to run the install step in an Amazon Linux-compatible Docker environment, or in another Linux build environment that matches your target runtime and architecture. Build separate layer artifacts for x86_64 and arm64 when the dependency contains native code. This is particularly important for Chromium, image-processing libraries, database drivers, and cryptography packages.
Keep the layer lean: install production dependencies only, exclude caches and test files where possible, and check the unzipped size before publishing. A layer is a great fit for reusable dependencies; it is not a magic escape hatch for every package-size problem.
Publish the layer
You can upload layer.zip in the Lambda console, or publish it with the AWS CLI:
aws lambda publish-layer-version \
--layer-name my-node-dependencies \
--description "Shared Node.js production dependencies" \
--zip-file fileb://layer.zip \
--compatible-runtimes nodejs20.x nodejs22.x \
--compatible-architectures x86_64Use the runtime and architecture that match the function you will attach it to. Each publication creates an immutable layer version. That is useful for safe releases: update the layer, test the new version on a function, then move the remaining functions deliberately.
Attach the layer to a function
In the Lambda console, open the function, choose Layers, then Add a layer, and select the layer version. You can also update a function configuration with an exact layer ARN:
aws lambda update-function-configuration \
--function-name my-function \
--layers arn:aws:lambda:REGION:ACCOUNT_ID:layer:my-node-dependencies:1Once attached, import the dependency normally:
import packageName from "package-name";Puppeteer and Chromium: a practical warning
Headless-browser deployments deserve special care. Use puppeteer-core with a Lambda-compatible Chromium distribution, pin compatible versions, and build for the target architecture. Do not copy an old compatibility table into a long-lived guide: Puppeteer, Chromium, Node.js runtimes, and Lambda base images move too quickly. Check the package’s current release notes and test the exact combination in the target Lambda runtime before deploying.
Common Lambda layer mistakes
- Wrong ZIP structure: the archive must contain
nodejs/node_modules, not justnode_modules. - Wrong platform: native dependencies must match Linux and the function architecture.
- Development bloat: use
--omit=devornpm ci --omit=dev. - Assuming a layer works with container Lambdas: layers apply to ZIP-deployed functions; add dependencies to a container image instead.
- Overwriting a layer in place: layer versions are immutable, so attach the new version intentionally.
Useful AWS documentation
- Managing Lambda dependencies with layers
- Packaging your layer content
- Working with layers for Node.js Lambda functions
Once the directory structure and target platform are right, an npm Lambda layer is pleasantly boring: a versioned, reusable dependency bundle that keeps function code focused on what it actually does.







