Extension Points

The registry API is the main extension point. Call its methods from inside setup to add capabilities to the uploader.

registerSource

Adds an entry to the source picker (the list of upload origins users can choose from).

1pluginApi.registry.registerSource({
2 id: 'my-source',
3 label: 'my-source.label', // l10n key or plain string
4 icon: 'my-icon', // name of a registered icon
5 onSelect() {
6 uploaderApi.setCurrentActivity('my-activity');
7 uploaderApi.setModalState(true);
8 },
9});
PropertyTypeRequiredDescription
idstringYesUnique source identifier. Referenced in the sourceList config.
labelstringYesAn l10n key registered via registerL10n. Plain strings are also accepted but will produce a console warning — prefer l10n keys.
iconstringNoName of a registered icon (see registerIcon).
onSelect() => void | Promise<void>YesCalled when the user picks this source. Typically calls setCurrentActivity and setModalState.

registerActivity

Registers a custom UI panel rendered inside the uploader. Activities are identified by id and rendered on demand via uploaderApi.setCurrentActivity.

1pluginApi.registry.registerActivity({
2 id: 'my-activity',
3 render(host, params) {
4 const el = document.createElement('my-activity-element');
5 host.append(el);
6
7 // return a dispose function
8 return () => {
9 host.replaceChildren();
10 };
11 },
12});

The render function receives:

ArgumentTypeDescription
hostHTMLElementContainer element managed by the uploader. Append your UI here.
paramsRecord<string, unknown>The current params passed when the activity was activated via setCurrentActivity.

Return a cleanup function (or nothing) from render. It is called when the activity unmounts.

The render function receives a plain HTMLElement host, so you can use any library or framework to render your activity UI — React, Vue, Svelte, or vanilla DOM. Just mount into host and unmount in the returned cleanup function.

TypeScript: declaring a custom activity

Augment the CustomActivities interface so setCurrentActivity is typed:

1declare module '@uploadcare/file-uploader' {
2 interface CustomActivities {
3 'my-activity': { params: { fileId?: string } };
4 }
5}

Use never for the params type if your activity takes no parameters.


registerFileAction

Adds a per-file action button to the file list (e.g. “Edit image”).

1pluginApi.registry.registerFileAction({
2 id: 'my-action',
3 icon: 'my-action-icon',
4 label: 'Edit',
5 shouldRender(fileEntry) {
6 return fileEntry.isSuccess && fileEntry.isImage;
7 },
8 onClick(fileEntry) {
9 uploaderApi.setCurrentActivity('my-activity', { fileId: fileEntry.internalId });
10 uploaderApi.setModalState(true);
11 },
12});
PropertyTypeRequiredDescription
idstringYesUnique action identifier.
iconstringYesName of a registered icon.
labelstringYesAn l10n key registered via registerL10n. Plain strings are also accepted but will produce a console warning — prefer l10n keys.
shouldRender(fileEntry: OutputFileEntry) => booleanYesReturn true to show the action for a given file.
onClick(fileEntry: OutputFileEntry) => void | Promise<void>YesCalled when the user clicks the action.

OutputFileEntry is described in the API reference.

File actions from plugins appear to the left of the built-in remove button, in the order their plugins were registered.


registerFileHook

Intercepts files at defined lifecycle points so you can transform them before the uploader continues processing.

1pluginApi.registry.registerFileHook({
2 type: 'beforeUpload',
3 async handler({ file, signal }) {
4 const processed = await processFile(file, { signal });
5 return { file: processed };
6 },
7});
PropertyType / ValueDescription
type'beforeUpload' | 'onAdd'When to run the hook. See below.
handler(ctx) => result | Promise<result>Receives { file, signal } and must return { file }. Return the original file unchanged if no transformation is needed.
timeoutnumberMaximum milliseconds to wait for the handler before skipping it. Default: 30000.

The handler context contains:

PropertyTypeDescription
fileFile | BlobThe file to process.
signalAbortSignalFires when the operation is cancelled — e.g. the upload was aborted or the file was removed. Pass it through to any async work that supports cancellation.

After a hook returns a different file, the uploader recalculates derived metadata such as file size, MIME type, image detection, and file name.

'onAdd' runs right after a file enters the uploader collection. Use it for light normalization that should happen as early as possible.

'beforeUpload' runs right before the file is sent to the Uploadcare API. This is the better place for heavier async work such as resizing, compression, or format conversion.

If a hook does not resolve within its timeout (default 30 seconds), the uploader skips it and continues with the original file. A timed-out hook is logged via debugPrint but does not block the upload.

The helper functions in the examples below, such as detectMimeFromBytes and resizeImage, are illustrative placeholders. They are not provided by the uploader, so you need to implement them in your own code.

1// onAdd — runs as soon as the file is added to the uploader
2pluginApi.registry.registerFileHook({
3 type: 'onAdd',
4 async handler({ file }) {
5 if (file.type) return { file };
6
7 const detected = await detectMimeFromBytes(file);
8 const fileName = file instanceof File ? file.name : null
9
10 return {
11 file: new File([file], fileName, { type: detected }),
12 };
13 },
14});
15
16// beforeUpload — runs right before upload, with cancellation and custom timeout
17pluginApi.registry.registerFileHook({
18 type: 'beforeUpload',
19 timeout: 60_000, // allow up to 60s for heavy processing
20 async handler({ file, signal }) {
21 const resized = await resizeImage(file, { maxWidth: 1920, signal });
22 return { file: resized };
23 },
24});

Multiple hooks of the same type are chained in registration order. Each handler receives the output of the previous one.


registerConfig

Declares a custom config option that becomes available on <uc-config> as a property (and optionally as an HTML attribute).

1pluginApi.registry.registerConfig({
2 name: 'myApiKey',
3 defaultValue: '',
4 attribute: true, // expose as my-api-key="..." HTML attribute (default: true)
5 fromAttribute: (val) => val ?? '',
6});
PropertyTypeRequiredDescription
namestringYescamelCase option name.
defaultValueTYesValue used when not explicitly set.
attributebooleanNoWhether to map to/from an HTML attribute. Default: true.
fromAttribute(value: string | null) => TNoParse attribute string to config value. Defaults to returning the raw string.
toAttribute(value: T) => string | nullNoSerialize config value to attribute string for reflection.
normalize(value: unknown) => TNoValidate or coerce values assigned via JavaScript property. Useful for clamping numbers or converting types.

Non-string config options

Use fromAttribute and normalize when the config value is not a plain string:

1pluginApi.registry.registerConfig({
2 name: 'myMaxResults',
3 defaultValue: 24,
4 fromAttribute: (val) => (val !== null ? parseInt(val, 10) : 24),
5 normalize: (val) => (typeof val === 'number' && val > 0 ? val : 24),
6});

TypeScript: declaring a custom config key

Augment the CustomConfig interface so pluginApi.config.get and .subscribe are typed:

1declare module '@uploadcare/file-uploader' {
2 interface CustomConfig {
3 myApiKey: string;
4 }
5}