How to change an S3 object's content type or metadata

TL;DR

S3 has no UpdateObject call, so changing a stored header — content type, cache-control, content-disposition, content-language, content-encoding, Expires — or the custom metadata means copying the object onto itself with MetadataDirective set to REPLACE. S3 Viewer's Edit settings does exactly that on every provider, R2 included, guarded by the same reviewed-ETag condition as every other write. Storage class, server-side encryption and, on AWS, ACL grants and tags all carry over; only the headers you changed are new.

Steps

Step-by-step.

  1. 01

    In S3 Viewer: open the object and choose Edit settings

    Open the object and click Edit settings. Change the content type, cache-control, content-disposition, content-language, content-encoding or expires header, add or remove custom metadata keys, and save. S3 Viewer sends a self-copy with MetadataDirective: REPLACE, checks the object hasn't changed since you opened it, and refreshes the details panel once the write lands. Leave a field blank to remove that header entirely.
  2. 02

    AWS CLI: copy-object onto itself

    aws s3api copy-object with the same bucket and key as both source and destination, plus --metadata-directive REPLACE, rewrites the object's headers without touching its bytes.
    aws s3api copy-object \
      --bucket my-bucket \
      --copy-source my-bucket/reports/q3.pdf \
      --key reports/q3.pdf \
      --metadata-directive REPLACE \
      --content-type application/pdf \
      --cache-control "max-age=3600" \
      --metadata reviewed=true
  3. 03

    AWS SDK: copyObject with MetadataDirective REPLACE

    Without MetadataDirective: "REPLACE", copyObject defaults to COPY and keeps the source's headers and metadata untouched — REPLACE is what makes this a settings edit rather than a plain duplicate.
    await s3.copyObject({
      Bucket: 'my-bucket',
      CopySource: 'my-bucket/reports/q3.pdf',
      Key: 'reports/q3.pdf',
      MetadataDirective: 'REPLACE',
      ContentType: 'application/pdf',
      CacheControl: 'max-age=3600',
      Metadata: { reviewed: 'true' },
    });
  4. 04

    Only the source needs to be unchanged, not the destination

    Because the destination key is the source key, there is nothing to check for “already exists” — S3 Viewer's only condition is that the object still matches the ETag you reviewed before opening the dialog. A source that changed underneath you refuses the write with a 409 instead of overwriting whatever is there now.
  5. 05

    Metadata keys and sizes follow S3's own limits

    Custom metadata keys are lower-cased, limited to 64 characters of a-z0-9-, capped at 50 keys, 1 KiB per value and 2 KiB total across all of them — S3's own ceiling for the x-amz-meta-* headers, not something S3 Viewer invented. A key or value outside those limits is rejected before anything is sent.
  6. 06

    Objects over 5 GB copy in parts, automatically

    The same 5 GB ceiling that applies to a plain CopyObject applies to a self-copy. S3 Viewer switches to a multipart copy above that size — initiating the multipart upload with the new headers, then copying each part from the source — so a settings edit has no practical size cap.
Under the hood

What's actually happening.

Every header S3 stores alongside an object's bytes — content type, cache-control, content-disposition, content-language, content-encoding, Expires, and custom x-amz-meta-* metadata — is set once, at write time, and there is no API call that changes it without rewriting the object. CopyObject is the write S3 Viewer reuses for this: point the copy source and destination at the same bucket and key, set MetadataDirective to REPLACE, and the object's bytes are copied onto themselves while every header you pass becomes the new one.


The one thing that has to be handled carefully is the precondition. A normal copy protects the destination with IfNoneMatch — refuse if something is already there. That's meaningless here, since the destination is the source. Instead S3 Viewer conditions the write on CopySourceIfMatch, the ETag you reviewed when you opened the settings dialog, so an object someone else changed in the meantime is never silently overwritten with stale headers.


This works the same way on AWS S3, R2, B2 and MinIO: a self-copy with REPLACE is a standard part of the S3 API, not an AWS extension. Where a provider doesn't enforce the source condition itself, S3 Viewer checks it with a HEAD request immediately beforehand instead — the same conditional-or-checked split every mutation in S3 Viewer follows.

FAQ

Common questions.

Can I change an S3 object's content type after uploading it?

Yes, but not by editing it in place — S3 has no call for that. Copy the object onto itself with copyObject (or aws s3api copy-object) and MetadataDirective set to REPLACE, passing the new ContentType. S3 Viewer's Edit settings does this from the object page on every provider, including R2.

How do I add or remove custom metadata on an S3 object?

Same self-copy, REPLACE directive, with the Metadata (or --metadata) you want the object to end up with — REPLACE means the whole metadata set is what you send, not merged with what was already there. Leaving a standard header like content-disposition blank removes it from the new object.

Does changing metadata re-upload the file?

No. CopyObject is server-side: the bytes are copied inside the storage provider, not downloaded and re-uploaded. Only the request and response travel over the network, which is why a settings edit on a multi-gigabyte file is fast regardless of your connection.

What happens to tags, ACLs and storage class when I edit settings?

S3 Viewer carries storage class and server-side encryption over automatically, and on AWS also preserves object ACL grants and tags via TaggingDirective: COPY, best-effort — a failed read of either just means the new object gets the bucket's defaults instead, never a blocked edit. R2 has no ACL or tag API, so there's nothing to preserve or drop there.

Can I change content type or metadata on a Cloudflare R2 object?

Yes. R2 supports a self-copy with MetadataDirective REPLACE the same way AWS S3 does, so S3 Viewer's Edit settings works identically there.

Why does S3 Viewer ask me to reload after a settings edit fails?

A 409 means the object changed after you opened Edit settings — someone replaced it, or another edit landed first. S3 Viewer's self-copy carries a condition on the ETag you reviewed, so it refuses to overwrite whatever is there now rather than guessing which version's headers you meant to set. Reload the object and try again.
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.