DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
Skip to content
Amazon S3

How to Save Generated PDFs to Amazon S3 in Go

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use AWS SDK for Go v2 and send the generated document as an io.Reader in an S3 PutObject request. For a PDF held in memory, wrap its bytes with bytes.NewReader, provide the destination bucket and key, and set ContentType to application/pdf. For large documents or high-throughput workloads, use the SDK transfer manager for bounded multipart uploads.

The upload boundary: PDF bytes to an S3 object

Keep PDF generation and object storage as separate concerns. Your chosen generator can return []byte, write to an io.Writer, or expose a seekable stream; the storage function only needs a body that the AWS SDK can read. This avoids coupling S3 code to a particular PDF package.

The minimum destination inputs are:

  • Bucket: the S3 bucket that receives the object.
  • Key: the complete object name, including any prefix such as reports/2026/09/invoice-123.pdf.
  • Body: the PDF data as an io.Reader.

Set the MIME metadata explicitly. S3’s Content-Type describes the representation stored in the object, so a PDF should be uploaded with application/pdf. If browsers or download clients need a particular filename, add Content-Disposition as well.

Prerequisites and AWS client setup

  • Go and the AWS SDK for Go v2 in your module.
  • An AWS identity permitted to upload to the target bucket (at minimum, an appropriate s3:PutObject permission).
  • A bucket name, region, and an object-key policy that matches your overwrite and retention requirements.

Use the SDK’s default credential and region chain rather than embedding secrets in source code. In production, that normally means an IAM role, workload identity, or another managed provider. The exact provider depends on where your Go service runs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
go mod init example.com/pdf-uploader
go get github.com/aws/aws-sdk-go-v2
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/s3

The client can then be created with the default configuration:

cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region))
if err != nil {
    return fmt.Errorf("load AWS configuration: %w", err)
}
client := s3.NewFromConfig(cfg)

Upload a generated PDF held in memory

This is the direct path for modest documents. The generator produces bytes, and bytes.NewReader adapts those bytes to the SDK’s reader-based body field.

package storage

import (
    "bytes"
    "context"
    "fmt"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

// SavePDF uploads one complete PDF object to Amazon S3.
func SavePDF(ctx context.Context, client *s3.Client, bucket, key string, pdf []byte) error {
    if len(pdf) == 0 {
        return fmt.Errorf("refusing to upload an empty PDF")
    }
    if bucket == "" || key == "" {
        return fmt.Errorf("bucket and key are required")
    }

    _, err := client.PutObject(ctx, &s3.PutObjectInput{
        Bucket:      aws.String(bucket),
        Key:         aws.String(key),
        Body:        bytes.NewReader(pdf),
        ContentType: aws.String("application/pdf"),
        // ContentDisposition: aws.String(`attachment; filename="report.pdf"`),
    })
    if err != nil {
        return fmt.Errorf("put PDF %s/%s: %w", bucket, key, err)
    }
    return nil
}

Call the function only after generation succeeds:

pdfBytes, err := buildReport(ctx, report)
if err != nil {
    return fmt.Errorf("generate report: %w", err)
}

key := fmt.Sprintf("reports/%s.pdf", report.ID)
if err := SavePDF(ctx, client, os.Getenv("PDF_BUCKET"), key, pdfBytes); err != nil {
    return err
}

Do not announce a successful save until PutObject returns without an error. The returned error is the upload result; preserve it so callers, metrics, and retry logic can distinguish generation failures from storage failures.

Stream or upload a file without coupling to a generator

If a generator writes to disk, open the file and pass the file handle as the body. This avoids loading the entire document into a second byte slice.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
func UploadPDFFile(ctx context.Context, client *s3.Client, bucket, key, path string) error {
    f, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("open PDF: %w", err)
    }
    defer f.Close()

    _, err = client.PutObject(ctx, &s3.PutObjectInput{
        Bucket:      aws.String(bucket),
        Key:         aws.String(key),
        Body:        f,
        ContentType: aws.String("application/pdf"),
    })
    if err != nil {
        return fmt.Errorf("upload PDF: %w", err)
    }
    return nil
}

The same boundary works for a generator that writes to an io.Pipe, but coordinate errors from both the producer and consumer carefully. If the producer fails after the upload starts, close the pipe with that error so the S3 operation does not appear to have completed normally.

Choosing and validating object keys

A key is not a local path; it is the exact object identifier within the bucket. Decide whether a repeated key should overwrite an earlier report or whether every output needs a unique key.

Requirement Key strategy Operational consequence
Latest report only reports/current.pdf New uploads replace the prior object.
Immutable report history reports/{reportID}/{version}.pdf Each version remains addressable; cleanup must be planned.
Collision resistance for retries Include a stable report ID and attempt or version Retries can be made idempotent without accidentally replacing another report.

Normalize values that enter keys, reject empty IDs, and avoid allowing an untrusted request to select arbitrary prefixes. Bucket versioning can provide an additional recovery layer, but it does not replace a deliberate key policy.

When multipart transfer is the better fit

Direct PutObject is the simplest choice for ordinary PDF outputs. For large PDFs or many concurrent uploads, the AWS SDK for Go v2 transfer manager can split the body into parts and upload those parts concurrently.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Axis Direct PutObject Transfer manager
Input Reader or file passed directly to one operation. Body supplied to a manager that can divide it into parts.
Complexity Low. Requires part-size and concurrency decisions.
Throughput One request path. Concurrent multipart parts can improve transfer throughput.
Resource control Few parallel-transfer knobs. Bound concurrent uploads and account for memory and network use.

AWS documents a 5 MiB minimum part size for multipart uploads and warns that applications should limit concurrent calls to avoid resource exhaustion. There is no universal safe concurrency number: choose one using your service’s memory, bandwidth, request rate, and workload, then measure it in your environment.

Use multipart for a known large-output class rather than enabling it reflexively for every PDF. A small document gains complexity without a meaningful benefit.

Metadata, completion, and verification

Content type and download behavior

ContentType: aws.String("application/pdf") lets clients handle the object as a PDF. Add ContentDisposition when the response should download with a controlled name, for example attachment; filename="invoice-123.pdf". Treat filenames derived from user input as untrusted and quote or sanitize them.

Wait only when your workflow needs confirmation

S3 upload completion is normally represented by a successful SDK response. If a subsequent workflow must verify that the object can be found, use an S3 object-exists waiter after the upload. Do not add a waiter to every request merely as a substitute for checking the original error; it adds latency and another API call.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Cancellation and retries

Pass a request-scoped context with a deadline. Cancellation should stop work when the caller disappears. Retry transient failures according to the SDK’s behavior and your service’s idempotency policy; a deterministic key makes a retry safe when replacing the same logical report is intended.

Performance and reliability checklist

  • Generate in memory when the PDF is modest and the extra byte allocation is acceptable.
  • Use a file or streaming reader when buffering a large document would pressure heap memory.
  • For multipart transfer, bound concurrent parts and select a part size that fits available memory.
  • Reuse the S3 client; do not construct a new client for every PDF.
  • Attach deadlines and return wrapped errors with bucket and key context.
  • Use stable keys for idempotent retries, or unique keys when every attempt must remain distinct.
  • Record object size, elapsed time, and failure category in application telemetry.
  • Keep credentials out of logs, source code, and generated PDF metadata.

Troubleshooting common failures

AccessDenied

The runtime identity or bucket policy does not allow the operation, or the key is outside an allowed prefix. Confirm the effective IAM policy, bucket policy, account, and region. The error is authorization-related; changing the PDF bytes will not fix it.

NoSuchBucket or wrong region

Check the bucket spelling and the region used to load the SDK configuration. A region mismatch can also surface as redirect or signing errors. Configure the client for the bucket’s actual region.

Signature or credential errors

Verify that the process can obtain credentials from its configured provider and that the system clock is reasonably accurate. Do not paste secret keys into source or error reports.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Empty or corrupt object

Check the generator result before calling S3, ensure the reader has not already been consumed, and do not reuse a reader whose cursor is at the end. For a file, open it before upload and keep it open until the SDK call returns.

Out-of-memory or slow large uploads

Stop converting large output to multiple byte slices. Prefer a file-backed or streaming body, or use the transfer manager with bounded concurrency and an appropriate part size.

Success reported too early

Return the SDK error to the caller and mark the job successful only after the call completes. If another system immediately needs to locate the object, add the targeted existence check described above.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If the PDF is produced from a web page rather than a Go PDF library, ScreenshotNeo can return a clean PDF through one HTTP request. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server provides capture_pdf, take_screenshot, and get_page_info tools for Claude, Cursor, and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o report.pdf

See the ScreenshotNeo documentation for request options. You can then pass report.pdf to the file-upload function above. ScreenshotNeo includes 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Can I upload a PDF with only an io.Reader?

Yes. The SDK’s PutObjectInput.Body is an io.Reader, so bytes, files, pipes, and other reader implementations can be used.

Should every PDF use multipart upload?

No. Use direct PutObject for ordinary outputs; reserve multipart transfer for files or workloads where its concurrency and part-management benefits justify the added operational choices.

Does setting ContentType change the PDF itself?

No. It sets object metadata that tells clients how to interpret the already-generated bytes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What PDF generator should a Go project choose?

The title does not establish a best library. Choose based on layout needs, fonts, forms, licensing, and whether the generator can produce the reader or byte representation your storage boundary accepts.

Frequently Asked Questions

Can an upload function accept both generated bytes and files?

Yes. Define the boundary around an io.Reader, then adapt []byte with bytes.NewReader or pass an open *os.File.

When is an S3 waiter useful?

Use an object-exists waiter only when a following workflow needs an explicit lookup confirmation; a successful PutObject response is otherwise the normal completion signal.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.