Skip to content
FrameworkJavaScript

Uploader API: upload assets by kind

Upload an asset through the pipeline that owns its KIND, instead of whichever tool happens to be asking. Tools call this rather than reaching into their own config.uploader. That is why an audio block's cover art reaches your image pipeline, instead of the audio endpoint that would reject it.

Resolution order for a kind: first the tool whose static assetKind matches (for example tools.image.config.uploader for 'image'), then the editor-level uploader config, then a local fallback. The fallback is a blob: URL for files, and the URL verbatim for links.

The fallback blob: URL gets a safe type. Any type outside a short safe list becomes text/plain, including HTML, XML, zip, Office files and untyped files. An SVG image becomes a data:image/svg+xml URL instead, up to 256 KB. A larger one becomes a text/plain blob and does not show. Read a file's real type from the block's mimeType.

See the storage presets page for ready-made uploader implementations: Supabase, S3-compatible storage, Cloudinary, and IndexedDB. None of them need a backend of your own.

Last updated Nov 3, 2026Edit this page on GitHub

Reaching the editor instance

The methods below run on the editor you created with new Blok(). They are available once editor.isReady resolves.

TypeScript
// You already hold the instance returned by the constructor.
const editor = new Blok({ holder: 'editor' });
await editor.isReady;

// Call any API method on it.
editor.caret.setToLastBlock('end');

Methods

uploader.uploadByFile(file, ctx)

Promise<{ url: string; fileName?: string }>

Store a file and return its URL. ctx is { kind, tool?, onProgress? }, where kind is the ASSET kind, not the requesting tool. The two differ whenever a tool holds an asset outside its own media family.

With no uploader for the kind, it returns a blob: URL with a safe type. Any type outside a short safe list becomes text/plain, such as HTML, XML, zip or Office files.

TypeScript
// Inside a custom block tool that holds a thumbnail
const { url } = await this.api.uploader.uploadByFile(file, {
  kind: 'image',
  tool: 'my-card',
  onProgress: (percent) => this.showProgress(percent),
});

uploader.uploadByUrl(url, ctx)

Promise<{ url: string; fileName?: string }>

Re-host an asset the user supplied by URL, and return the stored URL. Without an uploader for the kind, the URL is stored verbatim. You should configure one if a strict img-src/media-src policy or link rot would break third-party URLs.

TypeScript
const { url } = await this.api.uploader.uploadByUrl(pastedUrl, {
  kind: 'image',
  tool: 'my-card',
});

uploader.isConfigured(kind, method?)

boolean

Whether a host uploader handles this kind. False means the caller would get the local fallback. That helps you decide whether an asset is worth uploading at all, for example inlining small extracted artwork as a data: URL instead.

TypeScript
if (this.api.uploader.isConfigured('image', 'uploadByFile')) {
  const { url } = await this.api.uploader.uploadByFile(artwork, { kind: 'image' });
} else {
  // no image pipeline — keep it inline rather than minting a doomed blob: URL
}