Object Storage - POST Object

POST Object - Overview

The POST Object operation adds an object to a specified bucket using HTML forms. POST is an alternate form of PUT that enables browser-based uploads as a way of putting objects directly in buckets.

Parameters that are passed to PUT via HTTP Headers are instead passed as form fields to POST in the multipart/form-data encoded message body.

Important: You must have WRITE access on a bucket to add an object to it.

Use Case

Supported Form Fields

fileFile or text contentYes
keyName of the object you are about to create. If set to ${filename}, the key will be replaced by the name of the uploaded fileYes
aclprivate, public-read, …no
Cache-Control no
Content-Type no
Content-Disposition no
Content-Encoding no
Expires no
x-amz-meta-*Any metadata you want to attach to the objectno
success_action_redirect, redirectCustom redirection (303 See Other) if request is successful and URL correct, else ignoredno
success_action_status200, 201 or 204no
policySecurity Policy describing what is permitted in the requestYes
x-amz-credentialAccess KeyYes
x-amz-signatureThe signature of the policy (See below for generation)Yes

Policy, Expiration and Condition Matching

The policy required for making authenticated requests using HTTP POST is a UTF-8 and base64-encoded document written in JavaScript Object Notation (JSON) that specifies conditions that the request must meet. Depending on how you design your policy document, you can control the access granularity.

The POST policy always contains the expiration and conditions elements.


The expiration element specifies the expiration date and time of the POST policy in ISO 8601 GMT date format. For example, 2019-09-19T12:00:00.000Z specifies that the POST policy is not valid after midnight GMT on Septembre 19, 2019.

Condition Matching

Following is a table that describes condition matching types that you can use to specify POST policy conditions (described in the next section). Although you must specify one condition for each form field that you specify in the form, you can create more complex matching criteria by specifying multiple conditions for a form field.

Exact Matches{“acl”: “public-read” }The form field value must match the value specified. This example indicates that the ACL must be set to public-read.
Exact Match[ “eq”, “$acl”, “public-read” ]Same as above
Starts With[“starts-with”, “$field”, “user/user1/”]Fields must starts with
Specifying Ranges[“content-length-range”, 1048576, 10485760]Uploaded file size might be between 1Mo and 10Mo

Every field submitted in the form post mush be supplied to condition matching. All fields must begin with a $. To allow wildcard on a field, use ["starts-with", "$field", ""].

Signing Request

POST Object only supports signature v4.

Here’s how to sign an example policy in python:

import base64
import json
import hmac
import hashlib

# Generate your access key from the console
SECRET_ACCESS_KEY = "110e8400-e29b-11d4-a716-446655440000"

# S3 Region
REGION = "fr-par"

# Example for the demo
DATE = "20190918"
X_AMZ_DATE = "20190919T000000Z"
EXPIRATION = "2019-09-19T02:00:00.000Z"

policy = {
    "expiration": EXPIRATION,
    "conditions": [
        {"x-amz-credential": ACCESS_KEY_ID + "/" + DATE + "/" + REGION + "/s3/aws4_request"},
        {"x-amz-date": X_AMZ_DATE},

stringToSign = base64.b64encode(bytes(json.dumps(policy), encoding='utf8'))
print("Base64 encoded policy:", stringToSign.decode("utf-8"), end="\n\n")
# Base64 encoded policy: eyJleHBpcmF0aW9uIjogIjIwMTktMDktMTlUMDI6MDA6MDAuMDAwWiIsICJjb25kaXRpb25zIjogW3siYnVja2V0IjogIm15YnVja2V0In0sIFsic3RhcnRzLXdpdGgiLCAiJGtleSIsICIiXSwgeyJhY2wiOiAicHVibGljLXJlYWQifSwgeyJ4LWFtei1jcmVkZW50aWFsIjogIlNDV1hYWFhYWFhYWFhYWFhYWFhYLzIwMTkwOTE4L2ZyLXBhci9zMy9hd3M0X3JlcXVlc3QifSwgeyJ4LWFtei1hbGdvcml0aG0iOiAiQVdTNC1ITUFDLVNIQTI1NiJ9LCB7IngtYW16LWRhdGUiOiAiMjAxOTA5MTlUMDAwMDAwWiJ9LCB7InN1Y2Nlc3NfYWN0aW9uX3N0YXR1cyI6ICIyMDQifV19

dateKey = hmac.new(bytes("AWS4" + SECRET_ACCESS_KEY, 'utf-8'), bytes(DATE, 'utf-8'), digestmod=hashlib.sha256).digest()
dateRegionKey = hmac.new(dateKey, bytes(REGION, 'utf-8'), digestmod=hashlib.sha256).digest()
dateRegionServiceKey = hmac.new(dateRegionKey, bytes("s3", 'utf-8'), digestmod=hashlib.sha256).digest()
signinKey = hmac.new(dateRegionServiceKey, bytes("aws4_request", 'utf-8'), digestmod=hashlib.sha256).digest()
print("Signin key:", signinKey.hex(), end="\n\n")
# Signin key: 9c3ad81294a9263e472165307ae6e5c64e738b6837ff688f6e721a5ed53a4873

signature = hmac.new(signinKey, stringToSign, digestmod=hashlib.sha256).digest()
print("Signature:", signature.hex(), end="\n\n")
# Signature: 4d879d70f91e6f2f417163b4a1e90e0e6b32c99cbe3eaa168bc7ab216d33d784



$> npm install aws-s3-form
const express = require("express")
const app = express()
const port = 3000

var AwsS3Form = require("aws-s3-form")



# Generate your access key from the console
var PAR = {
  secret: "110e8400-e29b-11d4-a716-446655440000",
  region: "fr-par",
  bucket: "mybucket"


app.get("/", (req, res) => {
  var formGen = new AwsS3Form({
    accessKeyId: CREDS.access,
    secretAccessKey: CREDS.secret,
    region: CREDS.region,
    bucket: CREDS.bucket,
    redirectUrlTemplate: "http://localhost/",
    acl: "public-read"
  obj = formGen.create("object")

  var html = ""
  html += "<body>"
  html += "Fill the 'key' attribute, chose file and submit"
  html += "<form action='http://s3.fr-par.scw.cloud/mybucket'  method='post' name='form1' enctype='multipart/form-data'>"
  html += "<table>"
  html += "<tr><td><span>Key: <span></td><td><input type='text' name='key'></br></td></tr>"
  html +=
    "<tr><td><span>Signature: <span></td><td><input readonly type='text' name='X-Amz-Signature' value=" +
    obj.fields["X-Amz-Signature"] +
  html += "<tr><td><span>Policy: <span></td><td><input readonly type='text' name='Policy' value=" + obj.fields["Policy"] + "></br></td></tr>"
  html += "<tr><td><span>Acl: <span></td><td><input readonly type='text' name='acl' value=" + obj.fields["acl"] + "></br></td></tr>"
  html +=
    "<tr><td><span>Credential: <span></td><td><input readonly type='text' name='X-Amz-Credential' value=" +
    obj.fields["X-Amz-Credential"] +
  html +=
    "<tr><td><span>Algorithm: <span></td><td><input readonly type='text' name='X-Amz-Algorithm' value=" +
    obj.fields["X-Amz-Algorithm"] +
  html += "<tr><td><span>Date: <span></td><td><input readonly type='text' name='X-Amz-Date' value=" + obj.fields["X-Amz-Date"] + "></br></td></tr>"
  html +=
    "<tr><td><span>Status: <span></td><td><input readonly type='text' name='success_action_status' value=" +
    obj.fields["success_action_status"] +
  html +=
    "<tr><td><span>Meta: <span></td><td><input readonly type='text' name='x-amz-meta-uuid' value=" +
    obj.fields["x-amz-meta-uuid"] +
  html += "<tr><td><span>File</span></td><td><input type='file' name='file'></br></td></tr>"
  html += "<tr><td><input type='submit' value='submit'></br></td></tr>"
  html += "</form>"
  html += "</body>"

app.listen(port, () => console.log(`Example app listening on port ${port}!`))


import logging
import boto3
import requests
from botocore.exceptions import ClientError

# Generate a presigned URL for the S3 object
session = boto3.session.Session()

s3_client = session.client(

bucket_name = "mybucket"
object_name = "myobject"
fields = {
        'acl': 'public-read',
        'Cache-Control': 'nocache',
        'Content-Type': 'image/jpeg'
conditions = [
    {"key": "myobject"}
expiration = 120 # Max two minutes to start upload

    response = s3_client.generate_presigned_post(Bucket=bucket_name,
except ClientError as e:

# The response contains the presigned URL and required fields

with open('myfile', 'rb') as f:
    files = {'file': (object_name, f)}
    http_response = requests.post(response['url'], data=response['fields'], files=files)

Discover the Cloud That Makes Sense