---
name: s3-sveltekit
description: "Provisions an S3 bucket with keys scoped to it for a SvelteKit app with `npx extraorbital add s3` (no signup, no card) and wires it into SvelteKit with `@aws-sdk/client-s3`. Use when a SvelteKit 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 SvelteKit

<!-- 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

SvelteKit reads `.env` and `.env.local` into `$env/dynamic/private` in dev; in production the host's environment fills it.

**`src/lib/server/storage.ts`**

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

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

## 4. Use it

**`src/routes/api/s3/+server.ts`**

```ts
import { json } from "@sveltejs/kit";
import { ListObjectsV2Command, PutObjectCommand } from "@aws-sdk/client-s3";
import { s3, s3Bucket } from "$lib/server/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 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:5173/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.
- Read credentials through `$env/dynamic/private`, not `process.env`: Vite does not put `.env` into `process.env` in dev, and `$lib/server/` keeps the module out of the browser bundle.
- `$env/dynamic/private` is empty while a page is prerendered: do not call the client from a prerendered route.
- Only `PUBLIC_*` variables reach the browser. Pass anything public a page needs from a `+page.server.ts` load function instead.
- 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
