Skip to main content

External Storage

Manage all image assets through your own storage backend using presigned URLs, custom asset browsing, and folder management.

import { useRef } from 'react';
import { PexelizeEditor, PexelizeEditorRef } from '@pexelize/react-editor';

function ExternalStorageEditor() {
const editorRef = useRef<PexelizeEditorRef>(null);

return (
<PexelizeEditor
ref={editorRef}
editorKey="pk_your_key"
editorMode="email"
options={{
assetStorage: {
mode: 'external',
getPresignUrl: async (file) => {
const res = await fetch('/api/storage/presign', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename: file.name, contentType: file.type }),
});
const { uploadUrl, assetUrl } = await res.json();
return { uploadUrl, assetUrl };
},
onUpload: async (file, folder) => {
const formData = new FormData();
formData.append('file', file);
formData.append('folder', folder ?? '/');
const res = await fetch('/api/storage/upload', { method: 'POST', body: formData });
const { url, id } = await res.json();
return { url, id };
},
onLoadAssets: async (folder, page) => {
const res = await fetch(`/api/storage/assets?folder=${folder}&page=${page}`);
return await res.json();
// Expected: { assets: [{ id, url, name, thumbnail }], hasMore: boolean }
},
onDeleteAsset: async (assetId) => {
await fetch(`/api/storage/assets/${assetId}`, { method: 'DELETE' });
},
},
}}
/>
);
}

Presigned URL flow

When getPresignUrl is provided, the editor uploads files directly to your storage:

  1. User selects a file
  2. Editor calls getPresignUrl(file) to get a presigned upload URL
  3. Editor PUTs the file directly to that URL
  4. Editor uses the returned assetUrl to display the image
S3-compatible storage

This pattern works with AWS S3, Google Cloud Storage, Cloudflare R2, and any S3-compatible provider. Generate presigned PUT URLs on your backend.

Folder management

The onLoadAssets callback receives a folder parameter. Return assets filtered by folder to enable browsing:

onLoadAssets: async (folder, page) => {
// folder is '/' for root, '/products/' for a subfolder, etc.
const res = await fetch(`/api/assets?folder=${encodeURIComponent(folder)}&page=${page}`);
const data = await res.json();
return {
assets: data.items, // [{ id, url, name, thumbnail, folder }]
hasMore: data.hasNextPage,
folders: data.subfolders, // ['products', 'banners', 'icons']
};
},
Pagination required

onLoadAssets must support pagination via the page parameter. The editor requests page 1 initially and increments as the user scrolls. Return hasMore: false on the last page.

Callback reference

CallbackParametersReturnsDescription
getPresignUrl(file: File)Promise<{ uploadUrl, assetUrl }>Get a presigned URL for direct upload
onUpload(file: File, folder?: string)Promise<{ url, id }>Alternative: upload through your API
onLoadAssets(folder: string, page: number)Promise<{ assets, hasMore, folders? }>Load assets for the file manager
onDeleteAsset(assetId: string)Promise<void>Delete an asset from storage

Next steps

  • Image Upload — simpler upload configuration without full storage control
  • Custom Fonts — host fonts on your own storage
  • Export HTML — ensure exported HTML references your asset URLs