How to set granular S3 permissions per prefix

TL;DR

AWS IAM is the right tool for fine-grained per-prefix access — write a policy with two statements: s3:GetObject on the prefix wildcard, and s3:ListBucket on the bucket itself with a Condition on s3:prefix. The s3:prefix condition on listing is the part most people miss, which is why first-attempt policies let users read but not list. For human collaboration, S3 Viewer offers a simpler alternative: per-bucket roles assigned by email invite — Owner, Admin, Editor, or Viewer — without writing IAM at all.

Steps

Step-by-step.

  1. 01

    Decide the scope

    Bucket, prefix, or single key. The smaller the scope, the safer.
  2. 02

    IAM: write the read statement

    Allow s3:GetObject on every key inside the prefix. Resource uses a wildcard.
    "Resource": "arn:aws:s3:::my-bucket/reports/finance/*"
  3. 03

    IAM: add the listing condition (the part most people miss)

    Listing requires s3:ListBucket on the bucket itself (not the prefix), with a Condition on s3:prefix. This is why most first-attempt IAM policies let users read but not list.
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "ReadInsidePrefix",
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::my-bucket/reports/finance/*"
        },
        {
          "Sid": "ListInsidePrefix",
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": "arn:aws:s3:::my-bucket",
          "Condition": {
            "StringLike": { "s3:prefix": ["reports/finance/*"] }
          }
        }
      ]
    }
  4. 04

    IAM: attach to a user or role and test

    Attach the policy to the IAM user or role and verify with the IAM policy simulator before handing it over. Confirm both list and get work inside the prefix and deny outside.
  5. 05

    Team workflows: S3 Viewer per-bucket roles

    Authoring an IAM policy per teammate is heavy for human collaboration. For team access, connect the bucket once with a least-privilege workspace credential and assign per-bucket roles: Viewer (read), Editor (read + write), Admin (manage members), Owner (full control). The workspace enforces access server-side; for finer per-prefix scoping within a bucket, layer in IAM as above.
  6. 06

    Verify

    Try to list and read outside the prefix. Both should deny. If they don't, the policy is too broad — most likely the Resource wildcard is wrong or the listing Condition is missing.
Under the hood

What's actually happening.

S3 has no folders — “prefix-scoped” access is enforced by IAM policy on the key pattern. The catch is that listing requires a separate s3:ListBucket permission on the bucket ARN scoped via Condition/s3:prefix, which trips up most people writing their first IAM policy. For team collaboration, S3 Viewer offers an alternative: connect the bucket once with a workspace-level key and assign per-bucket roles to invited members. The workspace enforces those roles server-side, so you don't have to write a new IAM policy every time you onboard someone.

FAQ

Common questions.

How do I give someone access to one folder in an S3 bucket?

Write an IAM policy with two statements: `s3:GetObject` on `arn:aws:s3:::bucket/prefix/*` (the read), and `s3:ListBucket` on `arn:aws:s3:::bucket` with a Condition `StringLike: { s3:prefix: ['prefix/*'] }` (the listing). The listing condition is the part most people miss. For team workflows, S3 Viewer offers a simpler primitive — connect the bucket once and grant per-bucket roles to invited members instead of authoring custom IAM per teammate.

Why does my IAM policy let users read S3 files but not list the folder?

You're missing s3:ListBucket with an s3:prefix Condition. In S3, GetObject and ListBucket are separate permissions on different resources — GetObject is on the key (`bucket/prefix/*`), ListBucket is on the bucket (`bucket`). Granting one doesn't grant the other.

What's the difference between s3:ListBucket and s3:GetObject?

s3:ListBucket is permission to list keys in a bucket — it acts on the bucket ARN. s3:GetObject is permission to read the bytes of a specific key — it acts on the key ARN. To browse a folder, a user needs both: ListBucket to see the keys, GetObject to read them.

Can I scope down to a single S3 key?

Yes — set Resource to the exact key ARN with no wildcard, e.g., `arn:aws:s3:::my-bucket/reports/q4-finance.pdf`. Pair it with a ListBucket condition on the parent prefix if the user also needs to see it in a listing.

Do I need a custom IAM policy for every teammate?

Not necessarily. With S3 Viewer's team workspaces, one least-privilege key connects the bucket and four per-bucket roles (Owner, Admin, Editor, Viewer) handle access at the workspace layer. Use IAM when you want fine-grained per-prefix scoping; use workspace roles when you want simple per-bucket access for humans.

Does this work with Cloudflare R2?

R2 has its own token system rather than IAM-style policies. You scope R2 API tokens to specific buckets when you create them in the Cloudflare dashboard. For per-prefix scoping with R2, the workspace-layer approach is usually simpler.
Use S3 Viewer for this

Skip the CLI. Try it in the browser.

S3 Viewer turns the steps above into a single click. Open source, self-hostable, free for personal use.