Form upload lets you upload objects to Object Storage Service (OSS) directly from a web browser using standard HTML forms, without routing file data through your server. This topic shows how to generate a signature and an upload policy using OSS SDK for Go 2.0, then submit an HTTP POST request to upload an object to OSS.
How it works
Your server generates an upload policy that defines what the POST request is allowed to do: which bucket to upload to, what size range is acceptable, and when the policy expires.
Your server signs the policy using the HMAC-SHA256 algorithm and returns the policy, credentials, and signature to the client.
The browser submits a
multipart/form-dataPOST request directly to OSS with the signed fields. OSS validates the signature and stores the object.
Usage notes
The sample code uses the region ID
cn-hangzhou(China (Hangzhou)) and the public endpoint by default. To access OSS from another Alibaba Cloud service in the same region, use the internal endpoint instead. For details, see Regions and endpoints.Credentials in the sample code come from environment variables. For setup instructions, see Configure access credentials.
The maximum object size for form upload is 5 GB.
Form fields
The POST request body is a multipart/form-data form. The following table lists the fields OSS accepts.
| Field | Description | Required |
|---|---|---|
key | Object key (name) in the bucket. | Yes |
policy | Base64-encoded upload policy. | Yes |
x-oss-signature-version | Signature algorithm version. Set to OSS4-HMAC-SHA256. | Yes |
x-oss-credential | Credentials string in the format {AccessKeyID}/{date}/{region}/oss/aliyun_v4_request. | Yes |
x-oss-date | Request date and time in UTC, formatted as 20060102T150405Z. | Yes |
x-oss-signature | HMAC-SHA256 signature of the Base64-encoded policy, in hexadecimal. | Yes |
file | The data to upload. | Yes |
x-oss-* | Object metadata (for example, x-oss-content-type). | No |
Upload policy conditions
The conditions array in the upload policy controls what OSS accepts in the POST request. Three matching types are supported:
| Type | Syntax | Example |
|---|---|---|
| Exact match | {"field": "value"} or ["eq", "$field", "value"] | {"bucket": "my-bucket"} |
| Prefix match | ["starts-with", "$field", "prefix"] | ["starts-with", "$key", "user/uploads/"] |
| Size range | ["content-length-range", min, max] | ["content-length-range", 1, 10485760] |
Upload an object using form upload
The following example walks through the complete form upload process: creating a signed upload policy on the server side and submitting the POST request to OSS.
Step 1: Create an upload policy
Define the policy with an expiration time and a conditions array. The sample policy expires in 1 hour and restricts the request body to 1–1024 bytes.
utcTime := time.Now().UTC()
date := utcTime.Format("20060102")
expiration := utcTime.Add(1 * time.Hour)
policyMap := map[string]any{
"expiration": expiration.Format("2006-01-02T15:04:05.000Z"),
"conditions": []any{
map[string]string{"bucket": bucketName},
map[string]string{"x-oss-signature-version": "OSS4-HMAC-SHA256"},
map[string]string{"x-oss-credential": fmt.Sprintf("%v/%v/%v/%v/aliyun_v4_request",
cred.AccessKeyID, date, region, product)},
map[string]string{"x-oss-date": utcTime.Format("20060102T150405Z")},
[]any{"content-length-range", 1, 1024},
// Optional conditions:
// []any{"eq", "$success_action_status", "201"},
// []any{"starts-with", "$key", "user/eric/"},
// []any{"in", "$content-type", []string{"image/jpg", "image/png"}},
// []any{"not-in", "$cache-control", []string{"no-cache"}},
},
}Step 2: Serialize and Base64-encode the policy
Convert the policy map to a JSON string, then Base64-encode it. The Base64 string is both the value of the policy form field and the string to sign.
policy, err := json.Marshal(policyMap)
if err != nil {
log.Fatalf("json.Marshal fail, err:%v", err)
}
stringToSign := base64.StdEncoding.EncodeToString([]byte(policy))Step 3: Derive the signing key
Use HMAC-SHA256 to derive a signing key through a chain of four operations: date, region, service, and aliyun_v4_request. The initial key is the string aliyun_v4 prepended to the AccessKey secret.
hmacHash := func() hash.Hash { return sha256.New() }
h1 := hmac.New(hmacHash, []byte("aliyun_v4"+cred.AccessKeySecret))
io.WriteString(h1, date)
h1Key := h1.Sum(nil)
h2 := hmac.New(hmacHash, h1Key)
io.WriteString(h2, region)
h2Key := h2.Sum(nil)
h3 := hmac.New(hmacHash, h2Key)
io.WriteString(h3, product) // "oss"
h3Key := h3.Sum(nil)
h4 := hmac.New(hmacHash, h3Key)
io.WriteString(h4, "aliyun_v4_request")
h4Key := h4.Sum(nil)Step 4: Calculate the signature
Sign the Base64-encoded policy string with the derived key and encode the result as a hexadecimal string.
h := hmac.New(hmacHash, h4Key)
io.WriteString(h, stringToSign)
signature := hex.EncodeToString(h.Sum(nil))Step 5: Build the POST request body
Create a multipart form writer and add all required fields.
bodyBuf := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)
bodyWriter.WriteField("key", objectName)
// bodyWriter.WriteField("x-oss-<metadata-key>", value) // Optional: object metadata
bodyWriter.WriteField("policy", stringToSign)
bodyWriter.WriteField("x-oss-signature-version", "OSS4-HMAC-SHA256")
bodyWriter.WriteField("x-oss-credential", fmt.Sprintf("%v/%v/%v/%v/aliyun_v4_request",
cred.AccessKeyID, date, region, product))
bodyWriter.WriteField("x-oss-date", utcTime.Format("20060102T150405Z"))
bodyWriter.WriteField("x-oss-signature", signature)
w, _ := bodyWriter.CreateFormField("file")
w.Write([]byte(content))
bodyWriter.Close()Step 6: Send the request
Submit the POST request to the bucket endpoint and check the response status code.
req, _ := http.NewRequest("POST",
fmt.Sprintf("http://%v.oss-%v.aliyuncs.com/", bucketName, region),
bodyBuf)
req.Header.Set("Content-Type", bodyWriter.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("Do fail, err:%v", err)
}
defer resp.Body.Close()
if resp.StatusCode/100 != 2 {
log.Fatalf("Post Object fail, status code:%v, reason:%v", resp.StatusCode, resp.Status)
}
log.Printf("post object done, status code:%v, request id:%v\n",
resp.StatusCode, resp.Header.Get("X-Oss-Request-Id"))HTML form example
If you are building a browser-based upload form, the server-generated fields map directly to HTML <input> elements. The following example shows a typical upload form. Replace the placeholder values with the actual values returned by your server.
<form action="http://<bucket>.oss-<region>.aliyuncs.com/" method="post" enctype="multipart/form-data">
<input type="hidden" name="key" value="<object-key>" />
<input type="hidden" name="policy" value="<base64-encoded-policy>" />
<input type="hidden" name="x-oss-signature-version" value="OSS4-HMAC-SHA256" />
<input type="hidden" name="x-oss-credential" value="<AccessKeyID>/<date>/<region>/oss/aliyun_v4_request" />
<input type="hidden" name="x-oss-date" value="<UTC-datetime>" />
<input type="hidden" name="x-oss-signature" value="<hex-signature>" />
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>Complete example
The following is the complete runnable example that combines all steps above.
package main
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"hash"
"io"
"log"
"mime/multipart"
"net/http"
"time"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
)
var (
region string // The region where the bucket is located.
bucketName string // The bucket name.
objectName string // The object key.
product = "oss"
)
func init() {
flag.StringVar(®ion, "region", "", "The region where the bucket is located.")
flag.StringVar(&bucketName, "bucket", "", "The bucket name.")
flag.StringVar(&objectName, "object", "", "The object key.")
}
func main() {
flag.Parse()
if len(bucketName) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, bucket name required")
}
if len(region) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, region required")
}
if len(objectName) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, object name required")
}
// Load credentials from environment variables.
credentialsProvider := credentials.NewEnvironmentVariableCredentialsProvider()
cred, err := credentialsProvider.GetCredentials(context.TODO())
if err != nil {
log.Fatalf("GetCredentials fail, err:%v", err)
}
content := "hi oss"
// Step 1: Create an upload policy.
utcTime := time.Now().UTC()
date := utcTime.Format("20060102")
expiration := utcTime.Add(1 * time.Hour)
policyMap := map[string]any{
"expiration": expiration.Format("2006-01-02T15:04:05.000Z"),
"conditions": []any{
map[string]string{"bucket": bucketName},
map[string]string{"x-oss-signature-version": "OSS4-HMAC-SHA256"},
map[string]string{"x-oss-credential": fmt.Sprintf("%v/%v/%v/%v/aliyun_v4_request",
cred.AccessKeyID, date, region, product)},
map[string]string{"x-oss-date": utcTime.Format("20060102T150405Z")},
[]any{"content-length-range", 1, 1024},
// []any{"eq", "$success_action_status", "201"},
// []any{"starts-with", "$key", "user/eric/"},
// []any{"in", "$content-type", []string{"image/jpg", "image/png"}},
// []any{"not-in", "$cache-control", []string{"no-cache"}},
},
}
// Step 2: Serialize and Base64-encode the policy.
policy, err := json.Marshal(policyMap)
if err != nil {
log.Fatalf("json.Marshal fail, err:%v", err)
}
stringToSign := base64.StdEncoding.EncodeToString([]byte(policy))
// Step 3: Derive the signing key.
hmacHash := func() hash.Hash { return sha256.New() }
h1 := hmac.New(hmacHash, []byte("aliyun_v4"+cred.AccessKeySecret))
io.WriteString(h1, date)
h1Key := h1.Sum(nil)
h2 := hmac.New(hmacHash, h1Key)
io.WriteString(h2, region)
h2Key := h2.Sum(nil)
h3 := hmac.New(hmacHash, h2Key)
io.WriteString(h3, product)
h3Key := h3.Sum(nil)
h4 := hmac.New(hmacHash, h3Key)
io.WriteString(h4, "aliyun_v4_request")
h4Key := h4.Sum(nil)
// Step 4: Calculate the signature.
h := hmac.New(hmacHash, h4Key)
io.WriteString(h, stringToSign)
signature := hex.EncodeToString(h.Sum(nil))
// Step 5: Build the POST request body.
bodyBuf := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)
bodyWriter.WriteField("key", objectName)
// bodyWriter.WriteField("x-oss-<metadata-key>", value)
bodyWriter.WriteField("policy", stringToSign)
bodyWriter.WriteField("x-oss-signature-version", "OSS4-HMAC-SHA256")
bodyWriter.WriteField("x-oss-credential", fmt.Sprintf("%v/%v/%v/%v/aliyun_v4_request",
cred.AccessKeyID, date, region, product))
bodyWriter.WriteField("x-oss-date", utcTime.Format("20060102T150405Z"))
bodyWriter.WriteField("x-oss-signature", signature)
w, _ := bodyWriter.CreateFormField("file")
w.Write([]byte(content))
bodyWriter.Close()
// Step 6: Send the POST request.
req, _ := http.NewRequest("POST",
fmt.Sprintf("http://%v.oss-%v.aliyuncs.com/", bucketName, region),
bodyBuf)
req.Header.Set("Content-Type", bodyWriter.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("Do fail, err:%v", err)
}
defer resp.Body.Close()
if resp.StatusCode/100 != 2 {
log.Fatalf("Post Object fail, status code:%v, reason:%v", resp.StatusCode, resp.Status)
}
log.Printf("post object done, status code:%v, request id:%v\n",
resp.StatusCode, resp.Header.Get("X-Oss-Request-Id"))
}References
For the complete sample code, see post_object.go on GitHub.