# Presigned URLs (/docs/adapters/presigned)



`presignedAdapter` implements the generic presigned flow for any provider: Google Cloud
Storage V4 signed URLs, Azure SAS URLs, Supabase signed upload URLs, S3 POST policies,
or your own signing service.

<Flow
  label="Presigned upload"
  steps="[
  &#x22;Select&#x22;,
  &#x22;Validate&#x22;,
  { label: &#x22;getTarget()&#x22;, note: &#x22;your backend signs&#x22; },
  { label: &#x22;PUT / POST&#x22;, note: &#x22;browser → storage&#x22;, highlight: true },
  &#x22;Progress&#x22;,
  &#x22;Done&#x22;,
  { label: &#x22;complete()&#x22;, note: &#x22;optional&#x22; },
]"
/>

```ts
import { presignedAdapter } from "@uploadcn/core"

const adapter = presignedAdapter({
  async getTarget({ file, signal }) {
    const response = await fetch("/api/sign", {
      method: "POST",
      body: JSON.stringify({ name: file.name, type: file.type, size: file.size }),
      signal,
    })
    return response.json() // { url, method?, headers?, fields?, key?, publicUrl? }
  },
  async complete({ target, file }) {
    const response = await fetch("/api/files", {
      method: "POST",
      body: JSON.stringify({ key: target.key, name: file.name }),
    })
    return response.json()
  },
})
```

## Target [#target]

<TypeTable
  type="{
  url: { type: &#x22;string&#x22;, required: true },
  method: { type: '&#x22;PUT&#x22; | &#x22;POST&#x22;', default: '&#x22;PUT&#x22;', description: &#x22;POST sends a multipart form (S3 POST policy).&#x22; },
  headers: { type: &#x22;Record<string, string>&#x22;, description: &#x22;Headers that were signed and must be sent as-is.&#x22; },
  fields: { type: &#x22;Record<string, string>&#x22;, description: &#x22;POST policy form fields, sent before the file.&#x22; },
  key: { type: &#x22;string&#x22;, description: &#x22;Object key, returned in the result.&#x22; },
  publicUrl: { type: &#x22;string&#x22;, description: &#x22;Readable URL, returned in the result.&#x22; },
}"
/>

Without `complete`, the result is `{ key, url }` (the URL without its signature query).

For S3 and R2 you don't need to write the signing route yourself, use
[`s3Adapter`](/docs/adapters/s3) with `@uploadcn/server`.
