Deploy a TypeScript Lambda function
Build a Node.js Lambda function from TypeScript and deploy it with the CLI — type-check with tsc, bundle with esbuild to ESM, create the execution role, create and update the function, and pick a runtime that is not about to be deprecated.
markdownguidev41TAT-T4FFstage: buildtrust: officialservice: lambdainterface: cliinterface: sdkLambda TypeScriptesbuildtsc --noEmit@types/aws-lambdacreate-functionupdate-function-codeexecution rolelambda.amazonaws.comAWSLambdaBasicExecutionRolenodejs24.xnodejs22.xindex.mjshandler
Lambda runs JavaScript, not TypeScript: type-check with tsc --noEmit, bundle with esbuild into one .mjs file, zip it, and create the function on nodejs24.x with an execution role that trusts lambda.amazonaws.com. Updates are update-function-code with a new zip.
1. Write the handler
// src/index.ts
import type { Context, S3Event } from "aws-lambda";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({}); // created once per execution environment, reused across invocations
export const handler = async (event: S3Event, context: Context) => {
for (const r of event.Records) {
const key = decodeURIComponent(r.s3.object.key.replace(/\+/g, " "));
const obj = await s3.send(new GetObjectCommand({ Bucket: r.s3.bucket.name, Key: key }));
console.log(JSON.stringify({ key, bytes: obj.ContentLength, remainingMs: context.getRemainingTimeInMillis() }));
await obj.Body?.transformToString();
}
return { ok: true };
};
npm i -D @types/aws-lambda typescript esbuild. The import type … from "aws-lambda" line pulls only type definitions; it is not the unrelated aws-lambda npm package, and you need no runtime dependency for it. Clients go outside the handler so warm invocations reuse them. The region comes from the environment Lambda sets.
2. Type-check, bundle, zip
npx tsc --noEmit
npx esbuild src/index.ts --bundle --platform=node --target=node24 --format=esm --outfile=dist/index.mjs
cd dist && zip -q ../function.zip index.mjs && cd ..
esbuild does not type-check, so tsc --noEmit comes first. Match --target to the runtime you deploy to. The .mjs extension makes Node treat the file as an ES module; the handler setting is then index.handler. Bundling the AWS SDK into the zip is what AWS recommends rather than relying on the copy the runtime includes, whose version changes underneath you.
Size limits (fixed): 50 MB zipped for direct upload, 250 MB unzipped including layers, 10 GB for a container image. Past 50 MB, upload the zip to S3 and pass --code S3Bucket=…,S3Key=….
3. Create the execution role
aws iam create-role --role-name <fn>-role \
--assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
aws iam attach-role-policy --role-name <fn>-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
AWSLambdaBasicExecutionRole lets the function write CloudWatch Logs. Add only what the code calls (here s3:GetObject on the bucket's objects; kms:Decrypt if it is SSE-KMS). Lambda assumes this role for you on every invocation: do not call sts:AssumeRole on it from the code. The identity you deploy with needs iam:PassRole on this role (guides/iam/create-service-role.md). A brand-new role can take a few seconds to be usable; if create-function says the role cannot be assumed, wait and retry (UNVERIFIED exact message).
4. Create the function
aws lambda create-function --function-name <fn> \
--runtime nodejs24.x --architectures arm64 \
--handler index.handler --zip-file fileb://function.zip \
--role arn:aws:iam::<account>:role/<fn>-role \
--timeout 30 --memory-size 512
aws lambda wait function-active-v2 --function-name <fn>
The default timeout is 3 seconds, which is why "Task timed out after 3.00 seconds" is the most common first error. The maximum is 900 seconds. Memory is 128–10,240 MB (CPU is allocated in proportion to memory: UNVERIFIED in the collected sources). arm64 is optional; it must match any native modules you bundle (UNVERIFIED cost/performance claims are left out here).
Wait for the function to become Active before invoking or updating it. Calls made while it is Pending fail with ResourceConflictException … The function is currently in the following state: Pending.
5. Update
aws lambda update-function-code --function-name <fn> --zip-file fileb://function.zip
aws lambda wait function-updated-v2 --function-name <fn>
aws lambda update-function-configuration --function-name <fn> --environment 'Variables={STAGE=prod}'
Code and configuration updates cannot overlap. Wait between them, or the second call gets ResourceConflictException. Environment variables are limited to 4 KB in total; put secrets in Secrets Manager or Parameter Store, not in variables.
6. Test
aws lambda invoke --function-name <fn> --cli-binary-format raw-in-base64-out \
--payload '{"Records":[]}' --log-type Tail out.json \
--query 'LogResult' --output text | base64 -d
A StatusCode of 200 does not mean success: check for FunctionError in the response (reference/lambda/invoke.md).
Choosing the runtime
| Identifier | Status (2026-10-17) |
|---|---|
nodejs24.x | Supported; deprecation 2028-04-30 |
nodejs22.x | Supported; deprecation 2027-04-30, create blocked 2027-07-29, update blocked 2027-08-31 |
nodejs26.x | Preview; GA targeted November 2026 |
nodejs20.x and older | Deprecated |
Dates move. Check reference/lambda/runtimes-and-limits.md before relying on them.
Infrastructure as code
AWS SAM and the AWS CDK both bundle TypeScript with esbuild. The same rules apply: the target matches the runtime, the role trusts lambda.amazonaws.com, and the handler path points at the bundled file.
If it fails
guides/lambda/debug-failing-function.md: the function deploys but invocations fail.reference/lambda/errors.md: look up the exact message.reference/lambda/permissions.md: the function's own policy, for other services that invoke it.