Asset storage
Store uploads and AI-generated media in your own storage.
The pictures in a design can live in your own storage. You pass onUploadAsset, the editor gives it each picture as a File, and your code returns the permanent address. The design then refers to that address.
Without onUploadAsset, the pictures are saved inside the design as data: URLs. This works, but it makes each saved template large.
Uploads
onUploadAsset(file, ctx) returns a promise of { url }. The editor calls it when the user saves a template or a draft: once for each picture in the design, and once for the small preview picture of the template.
import { ImageEditor, type UploadAssetHandler } from '@layermetry/media-editor/react';
// Stores one picture in your storage, and returns its permanent address.
const uploadAsset: UploadAssetHandler = async (file, ctx) => {
const body = new FormData();
body.append('file', file, ctx.suggestedPath);
body.append('kind', ctx.type);
// Your own fields from assetMetadata arrive in ctx too.
body.append('workspace', String(ctx.workspace));
const response = await fetch('https://assets.example.com/upload', { method: 'POST', body });
if (!response.ok) throw new Error(`Upload failed: ${response.status}`);
const { url } = await response.json();
console.log(`Stored ${ctx.type} for template ${ctx.templateId}: ${url}`);
return { url };
};
export default function App() {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<ImageEditor
licenseKey="YOUR_LICENSE_KEY"
onClose={() => console.log('The editor was closed')}
onSaveTemplate={async ({ template }) => console.log(`Saved template ${template.id}`)}
onUploadAsset={uploadAsset}
assetMetadata={{ workspace: 'marketing' }}
/>
</div>
);
}Default: none. A rejected promise stops the save, and the user sees "We couldn't store this design's media, so nothing was saved."
ctx describes the file:
| Field | Description |
|---|---|
type | 'image' for a picture, 'thumbnail' for the preview picture of a template. |
itemId | The id of the picture in the design. |
mimeType | The type of the file, for example 'image/png'. |
templateId | The id of the template being saved. |
sceneMode | 'image'. |
suggestedPath | A path you can use as the storage key, for example templates/1790622792246_363bf8c1-…. |
source | 'template' when the user saves, 'generated' for a picture made or changed with AI. |
The context type is the same in both editors, so one onUploadAsset function fits both. It also declares source: 'recorded', take, and punchIn, which only the video editor's voiceover recorder sends. The image editor never sends them.
assetMetadata adds your own text fields to every ctx, for example a workspace or a customer id. The editor's own fields win if a name is the same.
assetMetadata={{ workspace: 'marketing' }}A picture that your storage returns must allow cross-origin requests, because the editor draws it on a canvas.
Loading stored pictures
When a template is loaded, an address that starts with http is used as it is. For other stored values, the editor asks resolveAssetUrl, then tries assetBaseUrl:
| Prop | Type | Description |
|---|---|---|
resolveAssetUrl | (stored, { type }) => Promise<string> | Turns a stored value, such as a storage key, into an address the browser can load. Use it for signed addresses that expire. |
assetBaseUrl | string | The address that a stored path is added to, for example https://cdn.example.com. |
resolveAssetUrl={async (stored) => {
const response = await fetch(`/api/signed-url?key=${encodeURIComponent(stored)}`);
return (await response.json()).url;
}}When onUploadAsset returns a storage key instead of a web address, the picture keeps your key in the design. A later save keeps the key, sends nothing to onUploadAsset again, and writes no picture bytes into the design. This is true for a picture from Edit with AI, from Add to canvas on a generated picture, from Generate background, and for a picture in a design that was saved and opened again. When the design opens again, the editor loads the picture from the key through resolveAssetUrl, or assetBaseUrl.
Keeping pictures inside the design
inlineTemplateMedia saves every picture inside the design, even when you pass onUploadAsset. Default: false.
inlineTemplateMediaGenerated media
AI services may return temporary picture URLs. Generated pictures pass through onUploadAsset with source: 'generated'; the design uses the reference your host returns.
For these uploads, ctx also has:
| Field | Description |
|---|---|
capability | The AI feature: 'image' or 'imageEdit'. |
producedBy | { capability, providerId }: the feature and the model that made the picture. |
suggestedPath | For example generated/imageEdit-1790622792246.png. |
If onUploadAsset is missing or fails, the picture keeps the address of the AI service until the next save. Then the editor stores it like any other picture.
Temporary assets
A picture that the user uploads stays inside the browser as a data: URL until the design is saved. Nothing is sent to your server when the user adds a picture.
When the user saves, each temporary picture, and each picture from an AI service, goes to onUploadAsset. Without onUploadAsset, it is saved inside the design. Pictures that already have an http address from your storage are not uploaded again.
The exported file that onExport receives is separate from these assets. See Export.