Skip to content
PutPut is shutting down on October 21, 2026. New signups, uploads, and paid plans are closed. Please download your files before then.

SvelteKit

Integrate PutPut with SvelteKit using a server-side load function for presigning and a form action for confirmation.

1. Environment variable

# .env
PUTPUT_TOKEN=pp_guest_...

2. Server endpoint for presign + confirm

// src/routes/api/presign/+server.ts
import { json } from "@sveltejs/kit";
import { PUTPUT_TOKEN } from "$env/static/private";
import type { RequestHandler } from "./$types";

const API = "https://putput.io/api/v1";

export const POST: RequestHandler = async ({ request }) => {
  const body = await request.json();
  const res = await fetch(`${API}/upload/presign`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${PUTPUT_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  return json(await res.json());
};

// src/routes/api/confirm/+server.ts
export const POST: RequestHandler = async ({ request }) => {
  const body = await request.json();
  const res = await fetch(`${API}/upload/confirm`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${PUTPUT_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  return json(await res.json());
};

3. Upload component

<!-- src/routes/upload/+page.svelte -->
<script lang="ts">
  let uploading = $state(false);
  let publicUrl = $state<string | null>(null);

  async function handleFile(e: Event) {
    const input = e.target as HTMLInputElement;
    const file = input.files?.[0];
    if (!file) return;
    uploading = true;

    // 1. Presign
    const presign = await fetch("/api/presign", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        filename: file.name,
        content_type: file.type,
        size_bytes: file.size,
      }),
    }).then((r) => r.json());

    // 2. Upload to R2
    await fetch(presign.presigned_url, {
      method: "PUT",
      headers: { "Content-Type": file.type },
      body: file,
    });

    // 3. Confirm
    const result = await fetch("/api/confirm", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ upload_id: presign.upload_id }),
    }).then((r) => r.json());

    publicUrl = result.file.public_url;
    uploading = false;
  }
</script>

<input type="file" onchange={handleFile} disabled={uploading} />
{#if uploading}<p>Uploading...</p>{/if}
{#if publicUrl}<a href={publicUrl}>{publicUrl}</a>{/if}
v0.4.78