> ## Documentation Index
> Fetch the complete documentation index at: https://upstash.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Upstash Blob Quickstart

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

Building with an AI coding agent?

Run `npx upstash` to install the Upstash [MCP server](https://upstash.com/docs/agent-resources/mcp.md) and
[skills](https://upstash.com/docs/agent-resources/skills.md) in Claude Code, Cursor, Codex, and other agents.

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](https://upstash.com/docs/blob/sdks/ts/upload-handler#other-frameworks) in TypeScript, [Mounting it](https://upstash.com/docs/blob/sdks/py/upload-handler#mounting-it) in Python and [Mounting it](https://upstash.com/docs/blob/sdks/go/upload-handler#mounting-it) in Go.

***

## Setup

```bash npm theme={"system"}
npm install @upstash/blob
```

```bash pnpm theme={"system"}
pnpm add @upstash/blob
```

```bash yarn theme={"system"}
yarn add @upstash/blob
```

```bash bun theme={"system"}
bun add @upstash/blob
```

```bash pip theme={"system"}
pip install upstash-blob
```

```bash uv theme={"system"}
uv add upstash-blob
```

```bash go theme={"system"}
go get github.com/upstash/blob-go
```

`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 theme={"system"}
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](https://upstash.com/docs/blob/bucket/reading#signed-read-urls). For user documents, invoices, anything that must not be guessable.

***

## Upload from your server

```ts TypeScript theme={"system"}
// 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 theme={"system"}
# 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 theme={"system"}
// 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
}
```

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](https://upstash.com/docs/blob/bucket/writing), and the [TypeScript SDK](https://upstash.com/docs/blob/sdks/ts/overview), [Python SDK](https://upstash.com/docs/blob/sdks/py/overview) or [Go SDK](https://upstash.com/docs/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.

**1. Write the upload handler**

```ts TypeScript theme={"system"}
// 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 theme={"system"}
# 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 theme={"system"}
// 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
	},
})
```

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](https://upstash.com/docs/blob/uploads/upload-handler) adds the auth check and the completion callback.

**2. Mount it as a route**

```ts TypeScript theme={"system"}
// app/api/upload/route.ts
import { uploads } from "@/lib/uploads"

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

```python Python theme={"system"}
# 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 theme={"system"}
// main.go
http.Handle("/api/upload", uploads)
log.Fatal(http.ListenAndServe(":8080", nil))
```

`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.

**3. Bind the hooks to the handler**

```ts lib/upload-hooks.ts theme={"system"}
"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](https://upstash.com/docs/blob/recipes/fastapi-react) has the whole app, including the dev proxy that puts `/api` on the page's origin.

**4. Upload a file**

```tsx app/page.tsx theme={"system"}
"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>
  )
}
```

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](https://upstash.com/docs/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](https://upstash.com/docs/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

> [card] [Upload handler](https://upstash.com/docs/blob/uploads/upload-handler)
>
> Auth, routes, and the completion callback in full.

> [card] [Upload client](https://upstash.com/docs/blob/uploads/upload-client)
>
> `useUpload`, the record it renders, and the non-React client.

> [card] [Writing](https://upstash.com/docs/blob/bucket/writing)
>
> `put`, metadata, conditional writes and multipart from the server.

> [card] [Recipes](https://upstash.com/docs/blob/recipes/avatars)
>
> Avatars, attachments and private documents, wired end to end.


## Related topics

- [Upstash Realtime Quickstart](https://upstash.com/docs/realtime/overall/quickstart.md)
- [Upstash Box Quickstart](https://upstash.com/docs/box/overall/quickstart.md)
- [Realtime Quickstart](https://upstash.com/docs/workflow/howto/realtime/basic.md)
- [Blob Recipes Overview](https://upstash.com/docs/blob/recipes/overview.md)
- [Docs Search Quickstart](https://upstash.com/docs/search/tutorials/buildsearchbar.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.