What it means
Parsing happens before execution. When Lambda initialises a Node function it reads the handler file and hands it to the parser, and a file that cannot be parsed produces no exports, no side effects, and no log lines — the runtime never gets far enough to run the first statement. Runtime.UserCodeSyntaxError is that failure, reported by the runtime on your behalf.
The instinctive reading is "I deployed a typo", and occasionally that is exactly right. But on Lambda specifically, the majority of these are not syntax errors in any meaningful sense. They are the CommonJS parser being handed ESM syntax, or the ESM parser being handed CommonJS. Both produce a genuine SyntaxError — Cannot use import statement outside a module is the best-known phrasing — and neither indicates anything wrong with the code. The code is valid; it is being parsed by the wrong grammar.
Which grammar runs is decided by two things: the file extension, and the type field in the nearest package.json. .mjs is always ESM, .cjs is always CommonJS, and a plain .js file takes whichever the type field says, defaulting to CommonJS. A local dev server, a test runner or a bundler frequently papers over a mismatch by transpiling on the fly, which is why this class of failure has a habit of appearing for the first time in a deployed environment.
The other recurring shape is an artifact that does not contain what you think. If the error names syntax your build step is supposed to remove — a type annotation, JSX, an enum — then the deployment package contains source rather than compiled output, and the packaging step is collecting the wrong directory. That is worth checking early, because no amount of reading the source will explain an error that comes from a file you never intended to ship.
CloudWatch’s Errors metric: The error escaped your handler, so Lambda reports the invocation as failed and CloudWatch’s Errors metric counts it. LogStitch classified the invocation above as uncaught.
What it looks like in CloudWatch
This is the shape the failure arrives in: the lines of one invocation scattered among everything else the log group received at the same moment.
CloudWatch Logs Before
6 raw lines, in the order CloudWatch delivered them.
LogStitch After
The same lines, grouped into the invocation they belong to.
ErrorCold start · 276ms initd40e2b17-6c93-4a58-b1f7-8e3a5d0c9f24
- 07:22:15.008PLAT
INIT_START Runtime Version: nodejs:22.v14 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:d1e0a1c1f0e0
- 07:22:15.284
Runtime.UserCodeSyntaxError: SyntaxError: Cannot use import statement outside a module
- 07:22:15.284
at _loadUserApp (file:///var/runtime/index.mjs:1087:17)
- 07:22:15.401PLAT
START RequestId: d40e2b17-6c93-4a58-b1f7-8e3a5d0c9f24 Version: $LATEST
- 07:22:15.403PLAT
END RequestId: d40e2b17-6c93-4a58-b1f7-8e3a5d0c9f24
- 07:22:15.403PLAT
REPORT RequestId: d40e2b17-6c93-4a58-b1f7-8e3a5d0c9f24 Duration: 2.44 ms Billed Duration: 3 ms Memory Size: 256 MB Max Memory Used: 66 MB Init Duration: 276.09 ms Status: error Error Type: Runtime.UserCodeSyntaxError
The panel on the right is generated by running the excerpt on the left through the same parser that powers the free web stitcher — it is what the tool actually produces for this input, not an illustration of it.
How to confirm it from the logs
The message carries the underlying SyntaxError and, in the stack, the file and position where the parser gave up. It arrives immediately after INIT_START with none of your own output before it, because the file never finished loading. Error Type: Runtime.UserCodeSyntaxError appears on the REPORT line where the runtime writes it.
Read the specific SyntaxError text rather than stopping at the wrapper. Cannot use import statement outside a module and Unexpected token 'export' are not typos at all — they are the CommonJS parser meeting ESM syntax, which is a configuration problem. A genuine typo usually reads as Unexpected token at a position that matches something you recently edited.
Causes, most likely first
ESM syntax is being parsed as CommonJS, or the reverse
Check the file extension and the type field in package.json against the syntax in the file. import/export need either a .mjs extension or "type": "module"; require/module.exports need .cjs or the absence of that field. This is by far the most common cause, and it does not reproduce locally when the local tooling transpiles.
The deployed artifact is not what you compiled
Look at whether the syntax in the error could exist in your build output at all. If the project is TypeScript or uses JSX, and the error points at syntax the transpiler should have removed, the zip contains source rather than build output — the packaging step collected the wrong directory.
The file uses syntax newer than the runtime supports
Compare the feature at the error position against the Node version behind your configured runtime. Code targeting a newer Node than the function's runtime parses on a modern laptop and fails in an older execution environment.
The file is truncated or corrupted
Check for Unexpected end of input, which means the parser reached the end of the file mid-construct. Download the deployed package and compare the file's size against the build output; an interrupted upload or a partially written build produces exactly this.
Fixes
Make the module system explicit and consistent
Decide which module system the function uses and configure it in both places. The extension wins over package.json, so .mjs and .cjs are the unambiguous options when a project contains both styles.
json{
"name": "orders-function",
"type": "module",
"engines": { "node": ">=22" }
}
Verify the artifact parses before you deploy it
A syntax check in CI catches this before it reaches Lambda, where the feedback loop is a deployment long. Run it against the built output, not the source.
bash# Parse without executing — non-zero exit on a syntax error.
node --check dist/index.js
# And confirm the artifact holds build output, not source.
unzip -l function.zip | grep -E '\.(ts|tsx|jsx)$' && echo "source leaked into the package"
Target the runtime you actually deploy to
Set the compile target to match the function's runtime so unsupported syntax is a build error rather than a deployment one.
bashnpx esbuild src/index.ts \
--bundle --platform=node \
--target=node22 \
--format=esm \
--outfile=dist/index.mjs
Also seen as
The same underlying failure, worded differently by a different runtime, SDK version, or logging layer. All of these land here — there is no separate page for each phrasing.
Related errors
Errors that show up alongside this one, or that people mistake for it.
References
LogStitch finds this automatically, across every invocation in your account.
Paste a log excerpt into the free web stitcher and see it grouped, classified, and measured in your browser — nothing is uploaded. Or run the Mac app against your own AWS profiles and get the same view over every function you own.