Skip to main content

POST Strategy

WIP

Presigned multipart/form-data uploads for small and medium files.

PostStrategy sends a file as a single multipart/form-data request using presigned form fields (S3 POST policy style). It is the simplest option and is not resumable — a failure restarts from zero.

import { PostStrategy } from '@gentleduck/upload/strategies'

strategies.set(PostStrategy())

PostStrategy() takes no runtime arguments; the four type parameters are inferred from the registry.

Intent shape

Your createIntent returns PostStrategy.Intent:

type Intent = {
  strategy: 'post'
  fileId: string
  url: string                       // presigned POST endpoint (form action)
  fields: Record<string, string>    // presigned policy fields (key, policy, signature…)
  expiresAt?: string                // optional expiry hint
}

Cursor shape

There is nothing to resume, so the cursor is empty:

type Cursor = Record<string, never>
type Cursors = { post?: PostStrategy.Cursor }

How it runs

start() hands the file and presigned fields to ctx.transport.postForm, which builds the FormData (fields first, file last, as S3 requires), POSTs it, and streams progress:

async start(ctx) {
  await ctx.transport.postForm({
    url: ctx.intent.url,
    file: ctx.file,
    fields: ctx.intent.fields,
    filename: ctx.file.name,
    signal: ctx.signal,
    onProgress: (uploadedBytes, totalBytes) => ctx.reportProgress({ uploadedBytes, totalBytes }),
  })
}

If the intent is missing its url, the strategy throws a validation error.

When to use

  • Small to medium files where resumability isn't needed.
  • Backends that issue S3-style presigned POST policies.
  • The simplest possible upload path.

For large files or unreliable connections, reach for the Multipart Strategy instead. A common pattern is to register both and let createIntent choose per file by size.