---
name: s3-nextjs
description: "Provisions an S3 bucket with keys scoped to it for a Next.js app with `npx extraorbital add s3` (no signup, no card) and wires it into Next.js with `@aws-sdk/client-s3`. Use when a Next.js project needs S3 storage, object storage, file uploads, a bucket, blob storage, R2, B2 or S3_BUCKET, or when S3_BUCKET is missing or unset in .env."
---

# S3 storage in Next.js

<!-- Generated from ExtraOrbital's service registry by `pnpm skills:build` in platform/app. Edit the generator, not this file. -->

ExtraOrbital provisions an S3 bucket with keys scoped to it in one command and writes the credentials into the project's env file. For anything this recipe does not cover, use the `extraorbital` skill or https://extraorbital.dev/llms.txt.

## 1. Provision

```bash
npx extraorbital add s3
```

Run it from the project root. It is idempotent (a second run returns the same resource) and writes to `.env.local` if the project has one, otherwise `.env`, keeping that file out of git. Add `--json` to parse the result; credential values are redacted there, and you should never print them either.

| Variable | |
| --- | --- |
| `S3_BUCKET` |  |
| `S3_ENDPOINT` |  |
| `S3_REGION` |  |
| `S3_ACCESS_KEY_ID` |  |
| `S3_SECRET_ACCESS_KEY` |  |

Free on the Prototype plan: 1 GB, 10K simple + 2K advanced ops/mo.

## 2. Install the client

```bash
npm install @aws-sdk/client-s3
```

## 3. Connect

Next.js loads both `.env.local` and `.env`, so either file ExtraOrbital writes is read on the next `next dev`.

**`lib/storage.ts`**

```ts
import { S3Client } from "@aws-sdk/client-s3";

export const s3 = new S3Client({
  endpoint: process.env.S3_ENDPOINT!,
  region: process.env.S3_REGION!,
  // Path-style addressing works on AWS and on S3-compatible endpoints alike.
  forcePathStyle: true,
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY_ID!,
    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
  },
});
export const s3Bucket = process.env.S3_BUCKET!;
```

## 4. Use it

**`app/api/s3/route.ts`**

```ts
import { ListObjectsV2Command, PutObjectCommand } from "@aws-sdk/client-s3";
import { s3, s3Bucket } from "@/lib/storage";

export async function GET() {
  await s3.send(new PutObjectCommand({ Bucket: s3Bucket, Key: "hello.txt", Body: "hi" }));
  const listed = await s3.send(new ListObjectsV2Command({ Bucket: s3Bucket, MaxKeys: 10 }));
  return Response.json({ keys: listed.Contents?.map((o) => o.Key) ?? [] });
}
```

## 5. Verify

```bash
npx extraorbital list        # the resource is listed as active
npm run dev
curl http://localhost:3000/api/s3
```

A JSON answer means the credentials, the client and the route all work. Delete the route afterwards if the app does not need it.

## S3-compatible alternatives

Same client, same code; only the variable names differ. Pick one when the project has a reason to:

- `npx extraorbital add r2` — Cloudflare R2: no egress fees; region `auto`; on the free plan files expire 30 days after upload. Writes `R2_BUCKET`, `R2_ENDPOINT`, `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`.
- `npx extraorbital add b2` — Backblaze B2: cheaper per stored byte. Writes `B2_BUCKET`, `B2_ENDPOINT`, `B2_REGION`, `B2_ACCESS_KEY_ID`, `B2_SECRET_ACCESS_KEY`.

## Pitfalls

- Objects are private unless the bucket was created with `--public` (`npx extraorbital add s3 --public`); options apply at creation only.
- For browser uploads, hand out a presigned URL (`@aws-sdk/s3-request-presigner`) rather than the keys.
- The keys reach this bucket only: listing buckets or creating new ones is refused.
- Import the module only from server code (route handlers, server components, server actions). Only `NEXT_PUBLIC_*` variables reach the browser, and nothing else should.
- With a `src/` directory the module goes in `src/lib/`; without the `@/*` path alias, import it relatively.
- Restart `next dev` after the env file changes: it is read at startup.
- Exit code 3 means the account is past its free allowance: show the human the link the command printed and wait. Do not retry or work around it.

Docs: https://extraorbital.dev/docs/resources/s3.md
