> ## Documentation index
> Fetch the complete documentation index at: https://upstash.com/docs/llms.txt
> Ask it with GET https://upstash.com/docs/ask?q=<keywords or question>. Short keywords rank best.
> Use these to discover all available pages before exploring further.
> Feature requests: https://upstash.com/docs/common/help/feature-requests.md lists what users asked for and how to vote for a request or add one.
> Page rating: `GET https://upstash.com/docs/feedback?path=/docs/blob/overall/quickstart&helpful=true` (or `false`) records whether this page helped. An optional `comment` (URL-encoded, at most 500 characters) says what was missing or wrong. It is anonymous and needs no key. Comments hold no code, secrets or personal data.

# Upstash Blob Quickstart

> Create a bucket, upload a file straight from the browser, and read it back.

<AgentSetup product="Blob" />

Upstash Blob is S3-compatible object storage. Each server SDK has a bucket client and an upload handler, and `@upstash/blob` has React hooks that upload from the browser straight to storage. This page builds a working file picker in React. The server code is Next.js App Router in TypeScript, FastAPI in Python, or `net/http` in Go. Other frameworks work the same way; see [Other frameworks](/blob/sdks/ts/upload-handler#other-frameworks) in TypeScript, [Mounting it](/blob/sdks/py/upload-handler#mounting-it) in Python and [Mounting it](/blob/sdks/go/upload-handler#mounting-it) in Go.

---

## Setup

<CodeGroup>
```bash npm
npm install @upstash/blob
```

```bash pnpm
pnpm add @upstash/blob
```

```bash yarn
yarn add @upstash/blob
```

```bash bun
bun add @upstash/blob
```

```bash pip
pip install upstash-blob
```

```bash uv
uv add upstash-blob
```

```bash go
go get github.com/upstash/blob-go
```
</CodeGroup>

`upstash-blob` is the Python server SDK and `github.com/upstash/blob-go` the Go one. The browser side is `@upstash/blob` whichever language runs the server, so a Python or Go app installs both.

Create a bucket in the [Upstash Console](https://console.upstash.com) and put its token in your environment.

```bash .env
UPSTASH_BLOB_TOKEN=...
```

The console asks whether the bucket is public or private:

- **Public**: every object has a public URL. For avatars, product images, anything a page links to directly.
- **Private**: no public URL. Every read goes through a time-limited [signed URL](/blob/bucket/reading#signed-read-urls). For user documents, invoices, anything that must not be guessable.

---

## Upload from your server

<CodeGroup>
```ts TypeScript
// lib/reports.ts
import { Bucket } from "@upstash/blob"

const bucket = Bucket.fromEnv()

export async function saveReport(file: Blob) {
  const blob = await bucket.put("reports/2026-01.pdf", file, {
    contentType: "application/pdf",
  })
  return blob.url
}
```

```python Python
# app/reports.py
from pathlib import Path

from upstash_blob import Bucket

bucket = Bucket.from_env()


def save_report(pdf: Path) -> str | None:
    blob = bucket.put("reports/2026-01.pdf", pdf, content_type="application/pdf")
    return blob.url
```

```go Go
// reports.go
package reports

import (
	"context"
	"os"

	blob "github.com/upstash/blob-go"
)

func SaveReport(
	ctx context.Context, bucket *blob.Bucket, pdf *os.File,
) (string, error) {
	b, err := bucket.Put(ctx, "reports/2026-01.pdf", pdf, &blob.PutOptions{
		ContentType: "application/pdf",
	})
	if err != nil {
		return "", err
	}
	return b.URL, nil
}
```
</CodeGroup>

Bytes already on your server go to the bucket with `put`. The returned record's URL is the public object URL, not set on a private bucket. See [Writing](/blob/bucket/writing), and the [TypeScript SDK](/blob/sdks/ts/overview), [Python SDK](/blob/sdks/py/overview) or [Go SDK](/blob/sdks/go/overview) for setup details.

---

## Upload from the browser

Files a user picks go straight from the browser to storage. Your server only authorizes the upload and records the result, so the bytes never pass through it.

<Steps>

<Step title="Write the upload handler">

<CodeGroup>
```ts TypeScript
// lib/uploads.ts
import "server-only"
import { uploadHandler } from "@upstash/blob"

export const uploads = uploadHandler({
  constraints: {
    maxSize: "20mb",
    contentTypes: ["image/*", "application/pdf"],
  },

  onBeforeUpload: ({ file }) => ({ path: `images/${file.name}` }),
})
```

```python Python
# app/uploads.py
from upstash_blob import AsyncUploadHandler, BeforeUploadResult

uploads = AsyncUploadHandler(
    max_size="20mb",
    content_types=["image/*", "application/pdf"],
    on_before_upload=lambda args: BeforeUploadResult(path=f"images/{args.file.name}"),
)
```

```go Go
// uploads.go
uploads, err := blob.NewUploadHandler(blob.UploadHandlerOptions[struct{}]{
	MaxSize:      20 * blob.MB,
	ContentTypes: []string{"image/*", "application/pdf"},

	OnBeforeUpload: func(
		a blob.BeforeUploadArgs[struct{}],
	) (blob.BeforeUploadResult, error) {
		return blob.BeforeUploadResult{Path: "images/" + a.File.Name}, nil
	},
})
```
</CodeGroup>

The handler runs on your server. It decides who may upload, where the object goes, and what happens once it lands. It never sees the bytes.

This one accepts anyone. [Upload handler](/blob/uploads/upload-handler) adds the auth check and the completion callback.

</Step>

<Step title="Mount it as a route">

<CodeGroup>
```ts TypeScript
// app/api/upload/route.ts
import { uploads } from "@/lib/uploads"

export const { GET, POST } = uploads
```

```python Python
# app/main.py (FastAPI)
from fastapi import FastAPI
from upstash_blob.fastapi import upload_view

from app.uploads import uploads

app = FastAPI()
app.add_api_route("/api/upload", upload_view(uploads), methods=["GET", "POST"])
```

```go Go
// main.go
http.Handle("/api/upload", uploads)
log.Fatal(http.ListenAndServe(":8080", nil))
```
</CodeGroup>

`POST` runs the upload and `GET` serves the route's constraints. The hooks look at `/api/upload` by default, so nothing else has to name a URL.

</Step>

<Step title="Bind the hooks to the handler">

```ts lib/upload-hooks.ts
"use client"

import { uploadHooks } from "@upstash/blob/react"
import type { uploads } from "./uploads"

export const { useUpload } = uploadHooks<typeof uploads>()
```

`uploadHooks<typeof uploads>()` reads the handler's type, so route names and completion data are checked at compile time. The `import type` is erased at build time and never pulls server code into the browser bundle.

A Python or Go handler has no type to bind to. Call `uploadHooks()` with no type parameter and pass the URL, `useUpload("/api/upload")`, in the next step. [FastAPI + React](/blob/recipes/fastapi-react) has the whole app, including the dev proxy that puts `/api` on the page's origin.

</Step>

<Step title="Upload a file">

```tsx app/page.tsx
"use client"

import { useUpload } from "@/lib/upload-hooks"

export default function Page() {
  const { start, upload, accept } = useUpload()

  return (
    <div>
      <input
        type="file"
        accept={accept}
        onChange={(e) => start({ file: e.target.files?.[0] })}
      />

      {upload && <p>{upload.status}</p>}
      {upload?.pending && <progress value={upload.percent} max={100} />}
      {upload?.status === "done" && (
        <a href={upload.blob.url}>{upload.blob.path}</a>
      )}
      {upload?.status === "error" && <p>{upload.error.message}</p>}
    </div>
  )
}
```

</Step>

</Steps>

You get these without more code:

- **Multipart for large files.** Past 16 MB the SDK switches to parts, with pause, resume and per-part retry. See [Large files](/blob/uploads/large-files).
- **Retries.** Failed parts back off and retry, and an expired signature is refreshed mid-upload.
- **A picker that matches the server.** `accept` comes from the route's own `GET`, so an oversized file is refused before any request goes out. See [Constraints](/blob/uploads/constraints).
- **Progress.** `percent`, `status` and `pending` read the same for one PUT or 200 parts.
- **Types end to end.** With a TypeScript handler, whatever `onUploadComplete` returns is `upload.blob.data` on the client, typed.

---

## Next steps

<CardGroup cols={2}>
  <Card title="Upload handler" href="/blob/uploads/upload-handler">
    Auth, routes, and the completion callback in full.
  </Card>

  <Card title="Upload client" href="/blob/uploads/upload-client">
    `useUpload`, the record it renders, and the non-React client.
  </Card>

  <Card title="Writing" href="/blob/bucket/writing">
    `put`, metadata, conditional writes and multipart from the server.
  </Card>

  <Card title="Recipes" href="/blob/recipes/avatars">
    Avatars, attachments and private documents, wired end to end.
  </Card>
</CardGroup>
