Go File Upload with the Filestack Go SDK
Overview
The Filestack Go SDK is the official Go client library for Filestack, a file upload, storage, transformation and delivery API. It lets a Go application accept and store files without writing its own chunking, storage or CDN code.
The SDK covers four areas:
- Uploads – send a local file, a file from an HTTP form, or a remote URL to Filestack storage and receive a filelink.
- Filelinks – reference a stored file by its handle to get its URL, download it or delete it.
- Transformations – build URLs that apply image and file operations such as resize, flip, upscale and black-and-white.
- Workflows – run a sequence of tasks defined on your Filestack account and read the results.
Before you start: you need Go 1.16 or later and a Filestack API key from the Filestack Developer Portal. The examples read their settings from environment variables:
FILESTACK_API_KEY– your API key (all examples).FILESTACK_APP_SECRET– your app secret, used to sign security policies (sections 8 and 13).FILESTACK_HANDLE– the handle of a file you have already uploaded (sections 3, 8, 9, 12 and 14).FILESTACK_WORKFLOW_ID– the ID of a workflow on your account (section 13).
Installing the Filestack Go SDK
Run this inside your Go module:
go get github.com/filestack/filestack-go
If your project does not have a go.mod file yet, create one first:
go mod init example.com/myapp
Import only the packages you use. The client package is always needed; the others are options and helpers:
import (
"github.com/filestack/filestack-go/client"
clientOptions "github.com/filestack/filestack-go/options/client"
uploadOptions "github.com/filestack/filestack-go/options/upload"
uploadURLOptions "github.com/filestack/filestack-go/options/uploadurl"
"github.com/filestack/filestack-go/resource"
"github.com/filestack/filestack-go/security"
"github.com/filestack/filestack-go/store"
"github.com/filestack/filestack-go/transformation/args"
)
Creating a Filestack client
client.NewClient takes your API key and optional client options (a security policy, a custom HTTP client or a limit on parallel uploads) and returns a client and an error. Always check the error before using the client.
A filelink is a reference to a stored file. It is identified by a handle, the short ID Filestack assigns to every uploaded file. fileLink.AsString() returns the file’s CDN URL.
Complete example – create a client and print a filelink URL
package main
import (
"fmt"
"log"
"os"
"github.com/filestack/filestack-go/client"
)
func main() {
cli, err := client.NewClient(os.Getenv("FILESTACK_API_KEY"))
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
fileLink, err := cli.NewFileLink(os.Getenv("FILESTACK_HANDLE"))
if err != nil {
log.Fatalf("failed to create filelink: %v", err)
}
fmt.Println(fileLink.AsString())
}
Uploading a local file
Open the file with os.Open and pass it to cli.Upload. Upload accepts any io.ReadSeeker, so an *os.File, a multipart.File from a form or a *bytes.Reader all work. It returns a filelink; AsString() returns the URL of the stored file.
Complete example – upload a local file
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/filestack/filestack-go/client"
)
func main() {
file, err := os.Open("photo.jpg")
if err != nil {
log.Fatalf("cannot open the file: %v", err)
}
defer file.Close()
cli, err := client.NewClient(os.Getenv("FILESTACK_API_KEY"))
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
fileLink, err := cli.Upload(context.Background(), file)
if err != nil {
log.Fatalf("upload failed: %v", err)
}
fmt.Println(fileLink.AsString())
}
Run it with your API key set:
FILESTACK_API_KEY=your_api_key go run main.go
Upload options: file name, MIME type and large files
Without store parameters, the SDK stores the file as unnamed_file with the MIME type application/octet-stream. Set the name and type with uploadOptions.StoreParams. store.Params also accepts Path, Location, Region, Container, Access and UploadTags to control where and how the file is stored.
For large files, add uploadOptions.Intelligent(). Intelligent upload splits the file into parts and uploads up to five parts at a time by default. Change the limit with clientOptions.MaxConcurrentUploads when you create the client.
Complete example – set the file name and MIME type and upload a large file
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/filestack/filestack-go/client"
uploadOptions "github.com/filestack/filestack-go/options/upload"
"github.com/filestack/filestack-go/store"
)
func main() {
file, err := os.Open("report.pdf")
if err != nil {
log.Fatalf("cannot open the file: %v", err)
}
defer file.Close()
cli, err := client.NewClient(os.Getenv("FILESTACK_API_KEY"))
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
fileLink, err := cli.Upload(
context.Background(),
file,
uploadOptions.StoreParams(&store.Params{
FileName: "report.pdf",
MimeType: "application/pdf",
}),
uploadOptions.Intelligent(), // split large files into parts and upload them in parallel
)
if err != nil {
log.Fatalf("upload failed: %v", err)
}
fmt.Println(fileLink.AsString())
}
Golang file upload server: handling uploads with net/http
To accept uploads from a browser or an API client, read the file from the multipart form with r.FormFile and pass it straight to cli.Upload. multipart.File implements io.ReadSeeker, so no temporary copy on disk is needed. Passing r.Context() stops the upload if the client disconnects.
Complete example – single-file upload handler
package main
import (
"fmt"
"log"
"net/http"
"os"
"github.com/filestack/filestack-go/client"
uploadOptions "github.com/filestack/filestack-go/options/upload"
"github.com/filestack/filestack-go/store"
)
func main() {
cli, err := client.NewClient(os.Getenv("FILESTACK_API_KEY"))
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// Reject request bodies larger than 32 MB.
r.Body = http.MaxBytesReader(w, r.Body, 32<<20)
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "invalid file field", http.StatusBadRequest)
return
}
defer file.Close()
params := &store.Params{
FileName: header.Filename,
MimeType: header.Header.Get("Content-Type"),
}
fileLink, err := cli.Upload(r.Context(), file, uploadOptions.StoreParams(params))
if err != nil {
http.Error(w, "upload failed", http.StatusBadGateway)
return
}
fmt.Fprintln(w, fileLink.AsString())
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Test it with curl:
curl -F "file=@photo.jpg" http://localhost:8080/upload
Or post to it from an HTML form. The form must use enctype=”multipart/form-data”, and the input name must match the field name in r.FormFile:
<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">Upload</button>
</form>
Uploading multiple files from one form
Send every file under the same field name, call r.ParseMultipartForm, then upload each entry in r.MultipartForm.File. This handler returns one URL per line after all files have been uploaded.
package main
import (
"fmt"
"log"
"net/http"
"os"
"github.com/filestack/filestack-go/client"
uploadOptions "github.com/filestack/filestack-go/options/upload"
"github.com/filestack/filestack-go/store"
)
func main() {
cli, err := client.NewClient(os.Getenv("FILESTACK_API_KEY"))
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
http.HandleFunc("/upload-multiple", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// Limit the whole request to 100 MB; keep up to 32 MB in memory.
r.Body = http.MaxBytesReader(w, r.Body, 100<<20)
if err := r.ParseMultipartForm(32 << 20); err != nil {
http.Error(w, "request too large or invalid", http.StatusBadRequest)
return
}
headers := r.MultipartForm.File["files"]
if len(headers) == 0 {
http.Error(w, "no files in the files field", http.StatusBadRequest)
return
}
var links []string
for _, header := range headers {
file, err := header.Open()
if err != nil {
http.Error(w, "cannot read file", http.StatusBadRequest)
return
}
fileLink, err := cli.Upload(r.Context(), file, uploadOptions.StoreParams(&store.Params{
FileName: header.Filename,
MimeType: header.Header.Get("Content-Type"),
}))
file.Close()
if err != nil {
http.Error(w, "upload failed", http.StatusBadGateway)
return
}
links = append(links, fileLink.AsString())
}
// Write the response only after every file has been uploaded.
for _, link := range links {
fmt.Fprintln(w, link)
}
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Test it with curl:
curl -F "files=@a.jpg" -F "files=@b.pdf" http://localhost:8080/upload-multiple
In an HTML form, add the multiple attribute to the file input:
<input type="file" name="files" multiple>
Restricting file size and file type
Do not rely on the Content-Type the browser sends; it can be wrong or spoofed. Limit the request size with http.MaxBytesReader, detect the real type from the first 512 bytes with http.DetectContentType, and reject anything not on your allow list before uploading.
package main
import (
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"os"
"github.com/filestack/filestack-go/client"
uploadOptions "github.com/filestack/filestack-go/options/upload"
"github.com/filestack/filestack-go/store"
)
var allowedTypes = map[string]bool{
"image/jpeg": true,
"image/png": true,
"application/pdf": true,
}
// detectType reads the first 512 bytes to find the real content type,
// then rewinds the file so the whole file can be uploaded.
func detectType(file multipart.File) (string, error) {
buf := make([]byte, 512)
n, err := file.Read(buf)
if err != nil && err != io.EOF {
return "", err
}
if _, err := file.Seek(0, io.SeekStart); err != nil {
return "", err
}
return http.DetectContentType(buf[:n]), nil
}
func main() {
cli, err := client.NewClient(os.Getenv("FILESTACK_API_KEY"))
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
r.Body = http.MaxBytesReader(w, r.Body, 10<<20) // 10 MB limit
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "invalid file field", http.StatusBadRequest)
return
}
defer file.Close()
contentType, err := detectType(file)
if err != nil || !allowedTypes[contentType] {
http.Error(w, "file type not allowed", http.StatusUnsupportedMediaType)
return
}
fileLink, err := cli.Upload(r.Context(), file, uploadOptions.StoreParams(&store.Params{
FileName: header.Filename,
MimeType: contentType,
}))
if err != nil {
http.Error(w, "upload failed", http.StatusBadGateway)
return
}
fmt.Fprintln(w, fileLink.AsString())
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Using Gin, Echo or another framework? The pattern is the same. In Gin and Echo, c.FormFile(“file”) returns a *multipart.FileHeader; call its Open method to get a multipart.File, then pass that file to cli.Upload with the request context.
Uploading a file from a URL
cli.UploadURL asks Filestack to fetch a file from a public URL and store it, so the file never passes through your server. Use uploadURLOptions.StoreParams to set the stored file name or other store parameters.
Complete example – upload a file from a URL
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/filestack/filestack-go/client"
uploadURLOptions "github.com/filestack/filestack-go/options/uploadurl"
"github.com/filestack/filestack-go/store"
)
func main() {
cli, err := client.NewClient(os.Getenv("FILESTACK_API_KEY"))
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
fileLink, err := cli.UploadURL(
context.Background(),
"https://example.com/images/photo.jpg",
uploadURLOptions.StoreParams(&store.Params{
FileName: "photo.jpg",
}),
)
if err != nil {
log.Fatalf("upload from URL failed: %v", err)
}
fmt.Println(fileLink.AsString())
}
Upload sends the bytes of a file you have open; UploadURL asks Filestack to fetch a file from a URL. Both return a filelink.
Securing uploads with a security policy
If security is enabled for your application in the Developer Portal, every request must carry a signed policy. Create one with security.NewSecurity, passing your app secret and a security.Policy. Expiry, a Unix timestamp, is the only required field.
Pass the policy to the client with clientOptions.SecurityPolicy to sign every request, or to a single upload with uploadOptions.SecurityPolicy (uploadURLOptions.SecurityPolicy for UploadURL). Keep the app secret on your server and never send it to a browser or mobile app.
Complete example – client with a 24-hour security policy
package main
import (
"fmt"
"log"
"os"
"time"
"github.com/filestack/filestack-go/client"
clientOptions "github.com/filestack/filestack-go/options/client"
"github.com/filestack/filestack-go/security"
)
func main() {
sec := security.NewSecurity(os.Getenv("FILESTACK_APP_SECRET"), &security.Policy{
Expiry: time.Now().Add(24 * time.Hour).Unix(), // the only required field
})
cli, err := client.NewClient(
os.Getenv("FILESTACK_API_KEY"),
clientOptions.SecurityPolicy(sec),
)
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
fileLink, err := cli.NewFileLink(os.Getenv("FILESTACK_HANDLE"))
if err != nil {
log.Fatalf("failed to create filelink: %v", err)
}
fmt.Println(fileLink.AsString())
}
Replacing the default HTTP client
The SDK uses http.DefaultClient, which has no timeout. Pass your own *http.Client with clientOptions.HTTPClient to set a timeout, a proxy or a custom transport.
Complete example – client with a one-minute timeout
package main
import (
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/filestack/filestack-go/client"
clientOptions "github.com/filestack/filestack-go/options/client"
)
func main() {
httpClient := &http.Client{Timeout: time.Minute}
cli, err := client.NewClient(
os.Getenv("FILESTACK_API_KEY"),
clientOptions.HTTPClient(httpClient),
)
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
fileLink, err := cli.NewFileLink(os.Getenv("FILESTACK_HANDLE"))
if err != nil {
log.Fatalf("failed to create filelink: %v", err)
}
fmt.Println(fileLink.AsString())
}
Plain net/http vs the Filestack SDK
Can I upload files in Go without an SDK?
Yes. Go’s standard library can receive multipart uploads and save them to disk. The Filestack SDK adds cloud storage, CDN delivery and file processing on top.
| Need | Plain net/http | Filestack Go SDK |
|---|---|---|
| Receive a form upload | Yes (r.FormFile) | Yes (same handler, then cli.Upload) |
| Store files | Your own disk or storage code | Filestack storage or your own cloud storage |
| Public file URL and CDN | Build it yourself | Returned as a filelink |
| Large files in parallel parts | Build it yourself | uploadOptions.Intelligent() |
| Upload from a remote URL | Download and re-upload yourself | cli.UploadURL |
| Image and file transformations | Separate libraries | Transformation URLs |
| Signed access rules | Build it yourself | Security policies |
Saving an uploaded file to disk
For comparison, this handler uses only the standard library and saves each upload to a local uploads folder:
package main
import (
"io"
"log"
"net/http"
"os"
"path/filepath"
)
func main() {
// Create the target folder once at startup.
if err := os.MkdirAll("uploads", 0o755); err != nil {
log.Fatalf("cannot create uploads folder: %v", err)
}
http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
r.Body = http.MaxBytesReader(w, r.Body, 32<<20)
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "invalid file field", http.StatusBadRequest)
return
}
defer file.Close()
// filepath.Base strips any directory parts from the client-supplied name.
dst, err := os.Create(filepath.Join("uploads", filepath.Base(header.Filename)))
if err != nil {
http.Error(w, "cannot save file", http.StatusInternalServerError)
return
}
defer dst.Close()
if _, err := io.Copy(dst, file); err != nil {
http.Error(w, "cannot save file", http.StatusInternalServerError)
return
}
w.Write([]byte("uploaded\n"))
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Building and chaining transformations
A transformation builds a Filestack processing URL for a stored file (resource.NewHandle), a public URL (resource.NewExternalURL) or a file in your own storage (resource.NewStorageAlias). Each task method returns the transformation, so tasks can be chained. BuildURL returns the final URL; Filestack processes the file when that URL is requested. To get the result as bytes in Go, call cli.ExecuteTransformation(ctx, transformation).
Complete example – flip and negate an image from a URL
package main
import (
"fmt"
"log"
"os"
"github.com/filestack/filestack-go/client"
"github.com/filestack/filestack-go/resource"
)
func main() {
cli, err := client.NewClient(os.Getenv("FILESTACK_API_KEY"))
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
transformation, err := cli.NewTransformation(
resource.NewExternalURL("[https://example.com/images/photo.jpg](https://example.com/images/photo.jpg)"),
)
if err != nil {
log.Fatalf("failed to create transformation: %v", err)
}
transformation.Flip().Negative() // chain several tasks
fmt.Println(transformation.BuildURL())
}
Transformation arguments
Tasks that take parameters accept an argument value from the transformation/args package. Create it with the matching constructor, such as args.NewUpscaleArgs(), and set options with its setter methods.
Complete example – upscale and convert to black and white
package main
import (
"fmt"
"log"
"os"
"github.com/filestack/filestack-go/client"
"github.com/filestack/filestack-go/resource"
"github.com/filestack/filestack-go/transformation/args"
)
func main() {
cli, err := client.NewClient(os.Getenv("FILESTACK_API_KEY"))
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
transformation, err := cli.NewTransformation(
resource.NewHandle(os.Getenv("FILESTACK_HANDLE")),
)
if err != nil {
log.Fatalf("failed to create transformation: %v", err)
}
transformation.
Upscale(args.NewUpscaleArgs().SetNoise("low")).
BlackWhite(args.NewBlackWhiteArgs().SetThreshold(5))
fmt.Println(transformation.BuildURL())
}
Running a workflow and checking its status
How do I run a Filestack workflow from Go and read the result?
Workflows are sequences of tasks, with optional conditions, defined on your Filestack account and referenced by a workflow ID (see the workflows overview).
cli.RunWorkflow starts a workflow for a file and returns a job ID. Call cli.CheckWorkflowStatus with that ID until the status is Finished, then read the results. Workflows require a security policy on the client; without one, RunWorkflow returns an error.
Complete example – run a workflow and wait for the result
package main
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/filestack/filestack-go/client"
clientOptions "github.com/filestack/filestack-go/options/client"
"github.com/filestack/filestack-go/resource"
"github.com/filestack/filestack-go/security"
)
func main() {
// Workflows require a security policy on the client.
sec := security.NewSecurity(os.Getenv("FILESTACK_APP_SECRET"), &security.Policy{
Expiry: time.Now().Add(24 * time.Hour).Unix(),
})
cli, err := client.NewClient(
os.Getenv("FILESTACK_API_KEY"),
clientOptions.SecurityPolicy(sec),
)
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
ctx := context.Background()
input := resource.NewExternalURL("[https://example.com/images/photo.jpg](https://example.com/images/photo.jpg)")
runResponse, err := cli.RunWorkflow(ctx, os.Getenv("FILESTACK_WORKFLOW_ID"), input)
if err != nil {
log.Fatalf("failed to run workflow: %v", err)
}
fmt.Println("job id:", runResponse.JobID)
// Check every 5 seconds, for up to 5 minutes.
for attempt := 0; attempt < 60; attempt++ {
status, err := cli.CheckWorkflowStatus(ctx, runResponse.JobID)
if err != nil {
log.Fatalf("failed to check workflow status: %v", err)
}
if status.Status == "Finished" {
for name, result := range status.Results {
fmt.Println(name, result.URL, result.MimeType, result.Size)
}
return
}
time.Sleep(5 * time.Second)
}
log.Fatal("workflow did not finish within 5 minutes")
}
Reading metadata and downloading files
cli.GetMetadata returns details of a stored file, including its file name, MIME type, size, upload time and storage location. A filelink can also download the file to a local *os.File with Download, or delete it with Delete(ctx); deleting requires a security policy.
Complete example – read metadata and download a stored file
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/filestack/filestack-go/client"
"github.com/filestack/filestack-go/resource"
)
func main() {
cli, err := client.NewClient(os.Getenv("FILESTACK_API_KEY"))
if err != nil {
log.Fatalf("failed to initialize client: %v", err)
}
ctx := context.Background()
handle := resource.NewHandle(os.Getenv("FILESTACK_HANDLE"))
metadata, err := cli.GetMetadata(ctx, handle)
if err != nil {
log.Fatalf("failed to read metadata: %v", err)
}
fmt.Printf("%+v\n", metadata)
fileLink, err := cli.NewFileLink(os.Getenv("FILESTACK_HANDLE"))
if err != nil {
log.Fatalf("failed to create filelink: %v", err)
}
out, err := os.Create("downloaded-file")
if err != nil {
log.Fatalf("cannot create output file: %v", err)
}
defer out.Close()
if _, err := fileLink.Download(out); err != nil {
log.Fatalf("download failed: %v", err)
}
}
Frequently asked questions
How do I upload a file in Go?
Install the SDK with go get github.com/filestack/filestack-go, create a client with client.NewClient, open the file with os.Open, and call cli.Upload(ctx, file). Upload returns a filelink, and fileLink.AsString() returns the URL of the stored file.
How do I handle a file upload from an HTML form in Go?
In a net/http handler, call r.FormFile(“file”) to get the uploaded multipart.File and its header, then pass the file to cli.Upload with r.Context(). multipart.File implements io.ReadSeeker, so no temporary copy on disk is needed.
How do I upload large files in Go?
Add uploadOptions.Intelligent() to cli.Upload. Intelligent upload splits the file into parts and uploads up to five parts at a time by default. Change the limit with clientOptions.MaxConcurrentUploads.
Why is my uploaded file named unnamed_file?
The SDK uses unnamed_file and application/octet-stream when no file name or MIME type is given. Pass uploadOptions.StoreParams(&store.Params{FileName: …, MimeType: …}) to cli.Upload.
What is the difference between Upload and UploadURL?
Upload(ctx, file) sends the bytes of an open file or any io.ReadSeeker. UploadURL(ctx, url) tells Filestack to fetch a file from a public URL. Both return a filelink and both accept store parameters and a security policy as options.