Хранилища для загрузок
Готовые загрузчики из @bloklabs/presets — чтобы Blok было куда сохранять загруженные файлы без необходимости писать собственный обработчик загрузки. Каждый из них подключается напрямую через опцию конфигурации uploader.
| Пресет | Повторно размещает файл по URL? | Готов к продакшену? |
|---|---|---|
| Fetch endpoint | Да | Да |
| Supabase | Нет | Да |
| Presigned URLs | Нет | Да |
| Cloudinary | Да | Да |
| IndexedDB | Нет | Нет |
Fetch endpoint
Talks to a backend you already have. Every upload goes through your server, which decides where the bytes actually land.
Повторное размещение файла по URL
Yes — your endpoint does the fetch server-side, so the browser never has to reach a third-party URL itself.
Конфигурация
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
baseUrl | string | (required) | Origin of your upload service, e.g. "https://api.myapp.com". |
field | string | "file" | Multipart field name the endpoint reads for POST /upload. |
headers | Record<string, string> | (() => Promise<Record<string, string>>) | undefined | Extra headers on every request. Pass a function to mint a fresh access token per upload. |
Настройка хранилища
- A server that answers POST {baseUrl}/upload (multipart) and POST {baseUrl}/upload-by-url ({ url }) with { url, fileName? } JSON.
- Where the endpoint stores the file — S3, a disk volume, Supabase — is entirely the endpoint's decision; this preset never sees it.
- The URL your /upload-by-url endpoint receives is user-supplied — validate it server-side and block internal/private addresses before fetching it, or you have built an SSRF vector.
Пример использования
import { fetchStorage } from '@bloklabs/presets';
new Blok({
holder: 'editor',
uploader: fetchStorage({ baseUrl: 'https://api.myapp.com' }),
});Supabase
Uploads straight to a Supabase Storage bucket using the client you already initialize.
Повторное размещение файла по URL
No — re-hosting a remote URL needs a server-side fetch, and the browser can't make one on Supabase's behalf. uploadByUrl is left undefined so Blok stores the URL verbatim instead of pretending it was re-hosted.
Конфигурация
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
bucket | string | ((kind: AssetKind) => string) | "blok" | Bucket name, or a function of the asset kind for per-kind buckets. |
path | (file: File, ctx: UploadContext) => string | random name, original extension kept | Object path within the bucket. |
Настройка хранилища
- Create the bucket in the Supabase dashboard (default name "blok", or whatever `bucket` resolves to).
- Make the bucket public, or add a Storage policy granting anon SELECT — this preset returns getPublicUrl() directly, so an unreadable object is a broken image.
- Add a Storage INSERT policy for whichever role the client authenticates as.
Пример использования
import { createClient } from '@supabase/supabase-js';
import { supabaseStorage } from '@bloklabs/presets';
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
new Blok({ holder: 'editor', uploader: supabaseStorage(supabase, { bucket: 'blok' }) });Presigned URLs
For S3 and S3-compatible storage (R2, MinIO, GCS). Your backend mints a short-lived signed URL; the browser PUTs the file straight to it.
Повторное размещение файла по URL
No — re-hosting a remote URL needs a server-side fetch, which is outside what a presigned PUT URL can do. uploadByUrl is left undefined so Blok stores the URL verbatim instead.
Конфигурация
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
sign | (request: SignRequest) => Promise<SignedTarget> | (required) | Called with { fileName, mimeType, size, kind }; must return { uploadUrl, publicUrl, headers? } from your backend. |
Настройка хранилища
- A backend endpoint that mints a presigned PUT URL (e.g. S3 PutObjectCommand) and returns it as SignedTarget.
- A CORS rule on the bucket allowing PUT (and the headers this preset sends, notably Content-Type) from your app's origin — this is what actually blocks people, since the browser PUTs directly to the bucket.
Пример использования
import { presignedStorage } from '@bloklabs/presets';
new Blok({
holder: 'editor',
uploader: presignedStorage({ sign: (request) => api.sign(request) }),
});Cloudinary
Uploads directly to Cloudinary using an unsigned upload preset — no backend required.
Повторное размещение файла по URL
Yes — Cloudinary fetches the remote URL itself once the browser hands it over, so re-hosting works without any server of the consumer's.
Конфигурация
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
cloudName | string | (required) | Your Cloudinary cloud name. |
uploadPreset | string | (required) | Must be an UNSIGNED upload preset — a signed one would need a server. |
folder | string | undefined | Optional folder to upload into. |
Настройка хранилища
- A Cloudinary account and its cloud name.
- An upload preset with signing mode set to Unsigned (Settings → Upload → Upload presets) — a signed preset silently fails from the browser since there is no server to sign the request.
- Audio assets ride Cloudinary's video pipeline by Cloudinary's own design; this preset maps kinds onto resource types accordingly, no setup needed for that part.
Пример использования
import { cloudinaryStorage } from '@bloklabs/presets';
new Blok({
holder: 'editor',
uploader: cloudinaryStorage({ cloudName: 'my-cloud', uploadPreset: 'blok-unsigned' }),
});IndexedDB
Stores uploaded files as blobs in the browser's own IndexedDB — nothing leaves the device.
Повторное размещение файла по URL
No — there is nothing to re-host to; a remote URL just gets stored as-is. IndexedDB only has an uploadByFile.
Не для продакшена
Demo and prototyping only. The bytes live in one visitor's browser: they're gone on another device, in another browser, or the moment the user clears site data. It exists because Blok's built-in fallback (a blob: URL) doesn't even survive a page reload, which makes demos look broken before you've configured any real storage.
Конфигурация
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
dbName | string | "blok-assets" | IndexedDB database name. |
Настройка хранилища
- None — this preset needs no external service, which is the point.
- Wire the exported resolveBlokObjectUrl(url) helper into wherever you render an uploaded asset: uploadByFile returns a blok:asset/… reference, not a directly usable URL, and nothing resolves it back into a blob: URL for you automatically.
- If you pass a custom dbName to indexedDBStorage(), pass that same dbName as resolveBlokObjectUrl(url, { dbName }) — a mismatch looks up the wrong database and resolves to null with no error, not a thrown exception.
Пример использования
import { indexedDBStorage, resolveBlokObjectUrl } from '@bloklabs/presets';
new Blok({ holder: 'editor', uploader: indexedDBStorage() });
// Wherever you render a stored asset's url, e.g. an <img src>:
const displayUrl = await resolveBlokObjectUrl(asset.url);