---
cuppy_hub: aws
cuppy_path: guides/lambda/deploy-function.md
cuppy_section: docs
cuppy_doc: 1TAT-T4FF
cuppy_doc_uuid: 6abaf61b-e8a8-4ee0-ae90-ae045dec5b99
cuppy_permalink: https://cuppy.io/aws/d/1TAT-T4FF
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/guides/lambda/deploy-function
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 7979818a537480e627f71cb50704fc0f37550690526ee531fadc21b526d4aefd
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Deploy a TypeScript Lambda function

**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

```ts
// 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.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
