All Products
Search
Document Center

Object Storage Service:Form upload

Last Updated:Mar 20, 2026

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

  1. 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.

  2. Your server signs the policy using the HMAC-SHA256 algorithm and returns the policy, credentials, and signature to the client.

  3. The browser submits a multipart/form-data POST 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.

FieldDescriptionRequired
keyObject key (name) in the bucket.Yes
policyBase64-encoded upload policy.Yes
x-oss-signature-versionSignature algorithm version. Set to OSS4-HMAC-SHA256.Yes
x-oss-credentialCredentials string in the format {AccessKeyID}/{date}/{region}/oss/aliyun_v4_request.Yes
x-oss-dateRequest date and time in UTC, formatted as 20060102T150405Z.Yes
x-oss-signatureHMAC-SHA256 signature of the Base64-encoded policy, in hexadecimal.Yes
fileThe 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:

TypeSyntaxExample
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(&region, "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