Skip to content

Recipes

Same upload() function, every runtime. Pick the snippet closest to your stack and adjust.

React + input

'use client';
import { useState } from 'react';
import { upload } from '@dropkit/sdk';
export function FileUpload() {
const [status, setStatus] = useState<'idle' | 'uploading' | 'done'>('idle');
const [url, setUrl] = useState<string | null>(null);
async function onChange(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
setStatus('uploading');
const { data, error } = await upload(file, {
key: process.env.NEXT_PUBLIC_DROPKIT_KEY!,
});
if (error) {
alert(error.message);
setStatus('idle');
return;
}
setUrl(data.url);
setStatus('done');
}
return (
<div>
<input type="file" onChange={onChange} disabled={status === 'uploading'} />
{url && <img src={url} alt="" />}
</div>
);
}

Svelte 5

<script lang="ts">
import { upload } from '@dropkit/sdk';
import { env } from '$env/dynamic/public';
let status = $state<'idle' | 'uploading' | 'done'>('idle');
let url = $state<string | null>(null);
async function onChange(e: Event) {
const file = (e.target as HTMLInputElement).files?.[0];
if (!file) return;
status = 'uploading';
const { data, error } = await upload(file, {
key: env.PUBLIC_DROPKIT_KEY!,
});
if (error) {
alert(error.message);
status = 'idle';
return;
}
url = data.url;
status = 'done';
}
</script>
<input type="file" onchange={onChange} disabled={status === 'uploading'} />
{#if url}<img src={url} alt="" />{/if}

Vue 3 composition

<script setup lang="ts">
import { ref } from 'vue';
import { upload } from '@dropkit/sdk';
const status = ref<'idle' | 'uploading' | 'done'>('idle');
const url = ref<string | null>(null);
async function onChange(e: Event) {
const file = (e.target as HTMLInputElement).files?.[0];
if (!file) return;
status.value = 'uploading';
const { data, error } = await upload(file, {
key: import.meta.env.VITE_DROPKIT_KEY,
});
if (error) {
alert(error.message);
status.value = 'idle';
return;
}
url.value = data.url;
status.value = 'done';
}
</script>
<template>
<input type="file" @change="onChange" :disabled="status === 'uploading'" />
<img v-if="url" :src="url" />
</template>

Vanilla HTML, no bundler

<input type="file" id="u" />
<img id="preview" />
<script type="module">
import { upload } from 'https://esm.sh/@dropkit/sdk';
document.getElementById('u').addEventListener('change', async (e) => {
const file = e.target.files[0];
const { data, error } = await upload(file, { key: 'pk_live_...' });
if (error) return alert(error.message);
document.getElementById('preview').src = data.url;
});
</script>

Next App Router server action

Use a server key from your backend so you don’t expose keys to the browser.

app/actions/upload.ts
'use server';
export async function uploadAction(formData: FormData) {
const file = formData.get('file') as File;
const buf = await file.arrayBuffer();
const res = await fetch('https://api.dropkit.app/v1/upload', {
method: 'POST',
headers: {
authorization: `Bearer ${process.env.DROPKIT_SECRET_KEY!}`,
'content-type': file.type,
'x-filename': file.name,
},
body: buf,
});
if (!res.ok) throw new Error(await res.text());
const { url } = (await res.json()) as { url: string };
return url;
}
app/upload/page.tsx
import { uploadAction } from '@/app/actions/upload';
export default function Page() {
return (
<form action={uploadAction}>
<input name="file" type="file" required />
<button type="submit">Upload</button>
</form>
);
}

Remix action

import type { ActionFunctionArgs } from '@remix-run/node';
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const file = formData.get('file') as File;
const buf = await file.arrayBuffer();
const res = await fetch('https://api.dropkit.app/v1/upload', {
method: 'POST',
headers: {
authorization: `Bearer ${process.env.DROPKIT_SECRET_KEY!}`,
'content-type': file.type,
'x-filename': file.name,
},
body: buf,
});
const { url } = await res.json();
return { url };
}

Expo / React Native

import * as DocumentPicker from 'expo-document-picker';
import { upload } from '@dropkit/sdk';
async function pickAndUpload() {
const result = await DocumentPicker.getDocumentAsync({});
if (result.canceled) return;
const asset = result.assets[0];
// Expo gives a URI; turn it into a File for the SDK.
const blob = await fetch(asset.uri).then((r) => r.blob());
const file = new File([blob], asset.name, { type: asset.mimeType ?? 'application/octet-stream' });
const { data, error } = await upload(file, { key: process.env.EXPO_PUBLIC_DROPKIT_KEY! });
if (error) throw new Error(error.message);
return data.url;
}

SolidJS

import { createSignal } from 'solid-js';
import { upload } from '@dropkit/sdk';
export function FileUpload() {
const [url, setUrl] = createSignal<string | null>(null);
async function onChange(e: Event) {
const file = (e.target as HTMLInputElement).files?.[0];
if (!file) return;
const { data, error } = await upload(file, {
key: import.meta.env.VITE_DROPKIT_KEY,
});
if (error) return alert(error.message);
setUrl(data.url);
}
return (
<>
<input type="file" onChange={onChange} />
{url() && <img src={url()!} />}
</>
);
}

Python server

import os
import requests
def upload(path: str) -> str:
with open(path, 'rb') as f:
r = requests.post(
'https://api.dropkit.app/v1/upload',
headers={
'authorization': f'Bearer {os.environ["DROPKIT_SECRET_KEY"]}',
'content-type': 'application/octet-stream',
'x-filename': os.path.basename(path),
},
data=f.read(),
)
r.raise_for_status()
return r.json()['url']

Go server

package main
import (
"bytes"
"encoding/json"
"io"
"net/http"
"os"
"path/filepath"
)
type uploadResp struct{ URL string `json:"url"` }
func UploadFile(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil { return "", err }
req, _ := http.NewRequest("POST", "https://api.dropkit.app/v1/upload", bytes.NewReader(data))
req.Header.Set("authorization", "Bearer "+os.Getenv("DROPKIT_SECRET_KEY"))
req.Header.Set("content-type", "application/octet-stream")
req.Header.Set("x-filename", filepath.Base(path))
res, err := http.DefaultClient.Do(req)
if err != nil { return "", err }
defer res.Body.Close()
if res.StatusCode >= 300 {
body, _ := io.ReadAll(res.Body)
return "", &httpError{res.StatusCode, string(body)}
}
var out uploadResp
if err := json.NewDecoder(res.Body).Decode(&out); err != nil { return "", err }
return out.URL, nil
}

Same pattern everywhere

Notice the shape in all browser-side snippets:

const { data, error } = await upload(file, { key });
if (error) throw new Error(error.message);
use(data.url);

That is the whole SDK. No framework-specific components to learn. Use your own UI, your own state library, your own error handling.