External Storage
Manage all image assets through your own storage backend using presigned URLs, custom asset browsing, and folder management.
- React
- Vue
- Angular
- Vanilla JS
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' });
},
},
}}
/>
);
}
<template>
<PexelizeEditor
ref="editorRef"
editor-key="pk_your_key"
editor-mode="email"
:asset-storage="storageConfig"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { PexelizeEditor } from '@pexelize/vue-editor';
const editorRef = ref<InstanceType<typeof PexelizeEditor>>();
const storageConfig = {
mode: 'external' as const,
getPresignUrl: async (file: File) => {
const res = await fetch('/api/storage/presign', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename: file.name, contentType: file.type }),
});
return await res.json(); // { uploadUrl, assetUrl }
},
onLoadAssets: async (folder: string, page: number) => {
const res = await fetch(`/api/storage/assets?folder=${folder}&page=${page}`);
return await res.json();
},
onDeleteAsset: async (assetId: string) => {
await fetch(`/api/storage/assets/${assetId}`, { method: 'DELETE' });
},
};
</script>
import { Component, ViewChild } from '@angular/core';
import { PexelizeEditorComponent } from '@pexelize/angular-editor';
import type { AssetStorageConfig } from '@pexelize/editor-types';
@Component({
selector: 'app-external-storage',
standalone: true,
imports: [PexelizeEditorComponent],
template: `
<pexelize-editor
#editor
editorKey="pk_your_key"
editorMode="email"
[assetStorage]="storageConfig"
></pexelize-editor>
`,
})
export class ExternalStorageComponent {
@ViewChild('editor') editorComp!: PexelizeEditorComponent;
storageConfig: AssetStorageConfig = {
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 }),
});
return await res.json();
},
onLoadAssets: async (folder, page) => {
const res = await fetch(`/api/storage/assets?folder=${folder}&page=${page}`);
return await res.json();
},
onDeleteAsset: async (assetId) => {
await fetch(`/api/storage/assets/${assetId}`, { method: 'DELETE' });
},
};
}
pexelize.init({
containerId: 'editor-container',
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 }),
});
return await res.json();
},
onLoadAssets: async (folder, page) => {
const res = await fetch(`/api/storage/assets?folder=${folder}&page=${page}`);
return await res.json();
},
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:
- User selects a file
- Editor calls
getPresignUrl(file)to get a presigned upload URL - Editor
PUTs the file directly to that URL - Editor uses the returned
assetUrlto 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
| Callback | Parameters | Returns | Description |
|---|---|---|---|
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