IAM Policy Generator for TypeScript & JavaScript

Paste Node.js code that uses the AWS SDK and get a draft IAM policy listing the actions it calls, ready to tighten and attach to a Lambda, ECS or EC2 role.

Your codeTypeScript / JavaScript
Drop the file to load it
Tab indents · Esc, Tab exits
ResultIAM policy JSON
Your IAM policy JSON will appear hereClick Generate IAM policy or press Ctrl Enter
policy.json
Don't paste secrets

How to generate an IAM policy from TypeScript code

To generate an IAM policy from TypeScript or JavaScript, paste the code that calls the AWS SDK into the editor above and press Generate IAM policy. Each SDK command, such as GetObjectCommand, becomes an IAM action like s3:GetObject, grouped into statements by resource. Replace any wildcards with real ARNs, then validate the policy before you attach it.

  1. Paste the code that calls AWSAdd the file with your SDK clients and every function that sends a command, using Paste, Open file (.ts, .tsx, .js, .mjs, .cjs, .jsx or .txt, up to 500 KB) or drag and drop. Sample loads an order processor that uses S3, DynamoDB and SQS.
  2. Let the secret check runBefore anything is sent, the page scans for AWS access keys, secret keys, private keys and common API tokens, and shows you where they are so you can swap in placeholders.
  3. Generate the policyClick Generate IAM policy or press Ctrl + Enter. The policy JSON streams into the right-hand editor; stop it at any time.
  4. Compare it with your codeCheck that every send() call has a matching action. Calls whose target comes from process.env are the ones most likely to end up as a wildcard or be left out.
  5. Tighten, validate and attachReplace wildcards with real ARNs, run it through IAM Access Analyzer policy validation, then Download it as policy.json and attach it to the role your code runs as.
Four steps to generate an IAM policy from TypeScript: paste code that calls the AWS SDK, local secret check, generate the policy JSON, replace wildcards with ARNs and validate
From SDK calls to a policy you can attach, in four steps.

Why generate an IAM policy from your code

Writing a policy by hand means reading every file, finding each SDK call and looking up the matching action. Most teams skip that and attach AmazonS3FullAccess or "Action": "s3:*" to get unblocked, which is how Lambda functions end up able to delete buckets they only ever read from. Starting from the calls your code actually makes gets you much closer to least privilege.

It helps most when you're creating the execution role for a new Lambda function or ECS task, reviewing a pull request that adds new SDK calls, or replacing a broad policy on an older Node.js service. Because it reads the source rather than your account, you can run it before anything is deployed.

It has limits. The generator only sees the code you paste, not your AWS account, so values that come from environment variables, the account ID and region, and calls made inside other libraries aren't visible. In one of our test runs with the sample, the SQS call whose queue URL came from process.env was left out of the draft entirely. That's why every step on this page ends with review.

What an IAM policy generator can see in TypeScript code, such as SDK commands, literal bucket names and key prefixes, compared with values only known at runtime, such as process.env values, the account ID, region and KMS keys
Literal names become precise ARNs. Runtime values become wildcards, or get missed.

How AWS SDK calls become IAM actions

Most AWS SDK for JavaScript commands map directly to an IAM action: the service prefix plus the operation name. GetObjectCommand needs s3:GetObject, QueryCommand on DynamoDB needs dynamodb:Query, and SendMessageCommand needs sqs:SendMessage. The generator reads the commands and client calls in your code and groups them into policy statements by service and resource.

There are exceptions worth knowing. Listing objects with ListObjectsV2Command needs s3:ListBucket on the bucket ARN, not on the object ARN. Reading an object encrypted with a customer managed KMS key also needs kms:Decrypt, and passing a role to another service needs iam:PassRole. The Service Authorization Reference lists the exact actions, resource types and condition keys for every service.

If your code still uses SDK v2 (s3.getObject().promise()), the actions are the same, since both versions call the same API operations. When you're ready to move off v2, the AWS SDK v2 to v3 converter rewrites the calls, and you can run this generator on the result to confirm nothing changed.

The DynamoDB Document Client (@aws-sdk/lib-dynamodb) needs the same IAM actions as the low-level client. QueryCommand still needs dynamodb:Query, and querying a secondary index needs it on the index ARN too (table/orders/index/*).

Mapping of AWS SDK for JavaScript v3 commands to IAM actions: GetObjectCommand to s3:GetObject, PutObjectCommand to s3:PutObject, QueryCommand to dynamodb:Query, SendMessageCommand to sqs:SendMessage, and ListObjectsV2Command to s3:ListBucket on the bucket ARN
Most commands map straight to an action. ListObjectsV2Command is the classic exception.

AWS SDK v3 commands and the IAM actions they need

A quick reference for the commands Node.js services use most. The resource column shows which ARN the permission applies to.

AWS SDK for JavaScript v3 commands, IAM actions and resource ARNs
CommandIAM actionResource
GetObjectCommands3:GetObjectarn:aws:s3:::bucket/key
PutObjectCommands3:PutObjectarn:aws:s3:::bucket/key
ListObjectsV2Commands3:ListBucketarn:aws:s3:::bucket (the bucket itself)
getSignedUrl(client, GetObjectCommand)s3:GetObjectNeeded by the role that signs the URL
QueryCommand (lib-dynamodb)dynamodb:Querytable/orders, plus table/orders/index/NAME for indexes
PutCommand (lib-dynamodb)dynamodb:PutItemarn:aws:dynamodb:region:account:table/orders
SendMessageCommandsqs:SendMessageThe queue ARN, not its URL
GetSecretValueCommandsecretsmanager:GetSecretValuesecret:NAME-* (secret ARNs end in a random suffix)
InvokeCommand (Lambda)lambda:InvokeFunctionarn:aws:lambda:region:account:function:NAME

Key features

  • Reads SDK v3 and v2 codeCommands sent with client.send() and v2 calls like s3.putObject().promise() both map to the same IAM actions.
  • Statements grouped by resourceActions on the same bucket, table or queue land in one statement, so the policy is short enough to review.
  • Uses the names in your codeLiteral bucket and table names, and key prefixes in template strings, go straight into the ARNs, such as acme-invoices/pending/*.
  • Wildcards only where it can't knowRegion, account ID and values read at runtime become *, which makes the parts you need to fill in easy to spot.
  • No guessed extrasJSON can't hold comments, so anything the model can't infer from the code is left out rather than invented. Compare the draft with your calls.
  • Ready-to-save policy.jsonCopy the policy or download it as policy.json to paste into the IAM console, CDK or Terraform.
  • Secret check before sendingAccess keys, private keys and API tokens are detected in your browser before any code leaves it.

Examples: TypeScript code and the IAM policy it needs

Three common patterns and a correct least-privilege policy for each. Choose Try this example to load the code into the generator and compare its draft with the policy shown here.

List and read uploads under one prefix

Listing needs s3:ListBucket on the bucket, while reading needs s3:GetObject on the objects. Two statements, two different ARNs.

TypeScript / JavaScript
import { S3Client, ListObjectsV2Command, GetObjectCommand } from "@aws-sdk/client-s3";

const s3 = new S3Client({});
const Bucket = "acme-uploads";

export async function readUserFiles(userId: string) {
  const list = await s3.send(new ListObjectsV2Command({ Bucket, Prefix: `users/${userId}/` }));

  const files: string[] = [];
  for (const obj of list.Contents ?? []) {
    const res = await s3.send(new GetObjectCommand({ Bucket, Key: obj.Key! }));
    files.push(await res.Body!.transformToString());
  }
  return files;
}
IAM policy JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::acme-uploads"
    },
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::acme-uploads/users/*"
    }
  ]
}

Query a DynamoDB secondary index

A query on a global secondary index is authorized against the index ARN, so the table ARN alone isn't enough.

TypeScript / JavaScript
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand, PutCommand } from "@aws-sdk/lib-dynamodb";

const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export async function ordersForCustomer(customerId: string) {
  const { Items } = await ddb.send(new QueryCommand({
    TableName: "orders",
    IndexName: "byCustomer",
    KeyConditionExpression: "customerId = :c",
    ExpressionAttributeValues: { ":c": customerId },
  }));
  return Items ?? [];
}

export async function saveOrder(order: Record<string, unknown>) {
  await ddb.send(new PutCommand({ TableName: "orders", Item: order }));
}
IAM policy JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "dynamodb:Query",
      "Resource": "arn:aws:dynamodb:*:*:table/orders/index/byCustomer"
    },
    {
      "Effect": "Allow",
      "Action": "dynamodb:PutItem",
      "Resource": "arn:aws:dynamodb:*:*:table/orders"
    }
  ]
}

Read a secret and publish an event

Secrets Manager adds a random six-character suffix to every secret ARN, so the resource ends in -*. The SNS topic ARN is a constant, so it can be exact.

TypeScript / JavaScript
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
import { SNSClient, PublishCommand } from "@aws-sdk/client-sns";

const secrets = new SecretsManagerClient({});
const sns = new SNSClient({});
const TOPIC_ARN = "arn:aws:sns:us-east-1:123456789012:order-events";

export async function chargeAndNotify(orderId: string) {
  const { SecretString } = await secrets.send(
    new GetSecretValueCommand({ SecretId: "payments/stripe-api-key" })
  );
  // ...charge the card with SecretString...
  await sns.send(new PublishCommand({ TopicArn: TOPIC_ARN, Message: JSON.stringify({ orderId }) }));
}
IAM policy JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:*:*:secret:payments/stripe-api-key-*"
    },
    {
      "Effect": "Allow",
      "Action": "sns:Publish",
      "Resource": "arn:aws:sns:us-east-1:123456789012:order-events"
    }
  ]
}

Tips for better results

  • Paste the code that makes the AWS calls. Include the client setup and every function that sends a command. A wrapper module you import but don't paste is invisible to the generator.
  • Keep literal names in the code you paste. When a bucket or table name is a constant, the generator can put it straight into the ARN. If yours come from process.env, paste a line showing the real value to get exact ARNs.
  • Keep bucket and object permissions apart. s3:ListBucket goes on arn:aws:s3:::bucket; s3:GetObject and s3:PutObject go on arn:aws:s3:::bucket/*.
  • Test before you rely on it. Check specific calls with the IAM policy simulator, then run the code with the policy attached and watch for AccessDenied errors.
  • Cross-check with real activity. Once the service has run in a test account, IAM Access Analyzer policy generation builds a policy from its CloudTrail events, a useful second opinion on what this page produced.

Common mistakes to avoid

  • Using a queue URL as the resource. SQS permissions take the queue ARN (arn:aws:sqs:us-east-1:123456789012:orders), not the https://sqs... URL your code passes.
  • Granting the table but not the index. QueryCommand with an IndexName needs dynamodb:Query on table/NAME/index/INDEX as well.
  • Putting s3:ListBucket on bucket/*. It has no effect there. List permissions apply to the bucket ARN only.
  • Forgetting permissions the code doesn't show. SSE-KMS reads need kms:Decrypt, writes need kms:GenerateDataKey, and handing a role to Lambda or ECS needs iam:PassRole.
  • Attaching the draft without comparing it to the code. Count your send() calls and check each one has an action. Calls built from runtime values are the easiest to miss.
  • Leaving * in production ARNs. Region and account wildcards are fine in a draft but should be replaced before the policy protects anything real.

Frequently asked questions

How do I generate an IAM policy from TypeScript code?

Paste the code that uses the AWS SDK into the editor at the top of this page and press Generate IAM policy. The generator maps each SDK command to its IAM action, groups them by resource and returns policy JSON. Replace wildcards with real ARNs and validate it before attaching it to a role.

Is the generated policy least-privilege?

It's a least-privilege starting point: it lists only the actions your code calls, scoped to the resources it can see. Tighten any wildcards, add conditions if you need them, and check the result with IAM Access Analyzer policy validation before you attach it. Before you attach it, follow the checklist to review a generated IAM policy for least privilege.

Why does the policy contain wildcards?

The generator reads your code, not your AWS account. The region, the account ID and names read from process.env or function arguments aren't in the source, so they appear as *. Object paths after / are also written as wildcards, such as arn:aws:s3:::acme-invoices/pending/*.

Can it miss permissions my code needs?

Yes. It only sees the calls in the code you paste, and it can overlook a call whose target is built at runtime. It also can't see permissions that are triggered indirectly, such as kms:Decrypt for encrypted objects or iam:PassRole. Compare the draft with your SDK calls and test it before relying on it. To check by hand, see how to find the IAM actions your AWS SDK for JavaScript code needs, including the commands that map to differently named actions.

Does it work with AWS SDK v2 code?

Yes. It reads JavaScript and TypeScript written for either version, whether you call s3.getObject().promise() in v2 or s3.send(new GetObjectCommand()) in v3. The IAM actions are the same because both call the same API operations.

How is this different from IAM Access Analyzer policy generation?

Access Analyzer builds a policy from CloudTrail activity after your code has run, so it sees real resources but only the paths that were exercised. This page works from source code before deployment. Using both, and comparing the results, gives you the most complete picture.

Where do I attach the policy?

To the role your code runs as: the Lambda execution role, the ECS task role, or the EC2 instance profile role. You can paste the JSON into the IAM console as an inline or customer managed policy, or copy it into your CDK or Terraform code.

Is my code stored?

No. The code is sent to an AI model hosted on NVIDIA's API only to produce the policy, and ChatWithCloud doesn't store or log it. Your latest draft stays in your browser. Replace real keys and passwords with placeholders first; the page warns you if it spots one. For the full picture, see whether it's safe to paste AWS code into an AI converter.

Is it free, and how much code can I paste?

Yes, it's free. Each run accepts up to 60,000 characters, and files up to 500 KB. Without an account you can generate 6 policies a minute and 60 a day. A free account raises that to 12 a minute and 200 a day and lists your recent runs on your dashboard. The guide to ChatWithCloud free converter limits shows how far they stretch for a real migration.

Related tools and next steps