DynamoDB AccessDeniedException: How to Read and Fix It

AccessDeniedException in DynamoDB is an IAM problem — a missing action, the wrong resource ARN, an index not covered, or a deny policy. How to decode the message and fix each cause.

3 min read· Updated Jul 26, 2026
On this page

AccessDeniedException is always an IAM authorization problem — the caller isn’t allowed to do what it asked. The good news: the message tells you almost exactly what to fix. A typical one reads:

AccessDeniedException: User: arn:aws:iam::123456789012:user/dev is not authorized to
perform: dynamodb:Query on resource: arn:aws:dynamodb:us-east-1:123456789012:table/Orders
because no identity-based policy allows the dynamodb:Query action

Three parts matter:

  1. The principaluser/dev (or a role). Who is being denied.
  2. The actiondynamodb:Query. What they tried to do.
  3. The resourcetable/Orders. Which resource it applies to.

Fix the IAM statement so that principal is allowed that action on that resource. Here are the specific causes.

Cause 1: Missing action

Every DynamoDB API maps to its own IAM action, and they’re not bundled. Granting dynamodb:GetItem does not grant dynamodb:Query, dynamodb:PutItem, dynamodb:BatchWriteItem, or dynamodb:UpdateItem. Read the denied action from the message and add it:

{
  "Effect": "Allow",
  "Action": [
    "dynamodb:GetItem",
    "dynamodb:Query",
    "dynamodb:PutItem",
    "dynamodb:UpdateItem",
    "dynamodb:DeleteItem",
    "dynamodb:BatchGetItem",
    "dynamodb:BatchWriteItem"
  ],
  "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/Orders"
}

Cause 2: Index not covered by the resource ARN

A Global or Local Secondary Index has a distinct ARN:

arn:aws:dynamodb:us-east-1:123456789012:table/Orders/index/GSI1

If your policy’s Resource lists only the table, a Query against GSI1 is denied even though the same query on the base table works. Cover both:

"Resource": [
  "arn:aws:dynamodb:us-east-1:123456789012:table/Orders",
  "arn:aws:dynamodb:us-east-1:123456789012:table/Orders/index/*"
]

See Secondary indexes for how index queries differ from base-table queries.

Cause 3: Wrong resource ARN (region / account / name)

The ARN encodes region and account id. A policy scoped to us-east-1 won’t authorize a call made in us-west-2, and a name typo (Orders vs orders-prod) leaves the real table uncovered. Make the ARN match the table you’re actually calling — including region and account.

Cause 4: An explicit Deny or a permissions boundary

In IAM, an explicit Deny always wins, regardless of any Allow. If everything looks granted but you’re still denied, check for:

  • A Deny statement in the identity policy, an SCP (Service Control Policy) at the org level, or a permissions boundary that caps what the role can do.
  • A resource-based policy (on the table) that denies the principal.

The message hint — “because no identity-based policy allows…” vs “with an explicit deny in…” — tells you which situation you’re in.

Cause 5: The wrong identity entirely

Sometimes the policy is fine but the call runs as an unexpected principal — a Lambda using its execution role instead of the role you assigned, an EC2 instance profile, or a stale local profile. Confirm who you actually are:

aws sts get-caller-identity

Compare that ARN to the principal in the error. If they differ, fix the credential chain, not the policy.

Encryption: KMS denials in disguise

If the table uses a customer-managed KMS key, the caller also needs kms:Decrypt / kms:GenerateDataKey on that key. A missing KMS permission surfaces as an access-denied on the DynamoDB operation. Grant the KMS actions on the key ARN too.

Diagnostic checklist

  1. Read the message: note the principal, action, resource.
  2. Is the action in the policy? (GetItem ≠ Query ≠ BatchWriteItem.)
  3. Querying an index? Add the .../index/* ARN.
  4. Does the ARN’s region, account, name match the real table?
  5. Any explicit Deny / SCP / boundary overriding the allow?
  6. aws sts get-caller-identity — are you the principal you think you are?
  7. Customer-managed KMS key? Add kms:Decrypt + kms:GenerateDataKey.

Test your access quickly

The fastest way to confirm a permission fix worked is to connect the exact profile/role and try the operation. Tablyne connects with your AWS profiles (including SSO and assumed roles) and shows immediately which tables and indexes that identity can actually reach — so you can verify an IAM change without writing a throwaway script. Download Tablyne free.

Frequently asked questions

How do I read a DynamoDB AccessDeniedException message?

The message names the principal, the action, and the resource: 'User: arn:...:user/dev is not authorized to perform: dynamodb:Query on resource: arn:...:table/Orders'. Those three parts tell you exactly which IAM statement to fix — the identity, the missing action, and the resource ARN it must allow.

Why do I get access denied on a Query even though GetItem works?

Each API maps to its own IAM action. A policy that grants dynamodb:GetItem does not grant dynamodb:Query. Grant every action your code uses — GetItem, Query, PutItem, UpdateItem, BatchWriteItem, etc.

My table policy is correct but querying an index is denied. Why?

A secondary index has its own ARN: arn:...:table/Orders/index/GSI1. A resource that lists only the table ARN doesn't cover the index. Add the index ARN (or table/Orders/index/* ) to the policy's Resource.

Keep reading