> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://uploadcare.com/docs/llms.txt.

# Extension Points

> Register sources, activities, file actions, file hooks, and config options via pluginApi.registry.

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).

```typescript
pluginApi.registry.registerSource({
  id: 'my-source',
  label: 'my-source.label',  // l10n key or plain string
  icon: 'my-icon',           // name of a registered icon
  onSelect() {
    uploaderApi.setCurrentActivity('my-activity');
    uploaderApi.setModalState(true);
  },
});
```

| Property   | Type                          | Required | Description                                                                                                                                                                                    |
| ---------- | ----------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`       | `string`                      | Yes      | Unique source identifier. Referenced in the [`sourceList`](/docs/file-uploader/options/#source-list) config.                                                                                   |
| `label`    | `string`                      | Yes      | An l10n key registered via [`registerL10n`](/docs/file-uploader/plugins/assets/#registerl10n). Plain strings are also accepted but will produce a console warning. Prefer l10n keys.           |
| `icon`     | `string`                      | No       | Name of a registered icon (see [`registerIcon`](/docs/file-uploader/plugins/assets/#registericon)).                                                                                            |
| `onSelect` | `() => void \| Promise<void>` | Yes      | Called when the user picks this source. Typically calls [`setCurrentActivity`](/docs/file-uploader/api/#set-current-activity) and [`setModalState`](/docs/file-uploader/api/#set-modal-state). |

---

## `registerActivity`

Registers a custom UI panel rendered inside the uploader. Activities are identified by `id` and rendered on demand via [`uploaderApi.setCurrentActivity`](/docs/file-uploader/api/#set-current-activity).

```typescript
pluginApi.registry.registerActivity({
  id: 'my-activity',
  render(host, params) {
    const el = document.createElement('my-activity-element');
    host.append(el);

    // return a dispose function
    return () => {
      host.replaceChildren();
    };
  },
});
```

The `render` function receives:

| Argument | Type                      | Description                                                                         |
| -------- | ------------------------- | ----------------------------------------------------------------------------------- |
| `host`   | `HTMLElement`             | Container element managed by the uploader. Append your UI here.                     |
| `params` | `Record<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.

> **Tip**
>
> **Tip:** 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:

```typescript
declare module '@uploadcare/file-uploader' {
  interface CustomActivities {
    'my-activity': { params: { fileId?: string } };
  }
}
```

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").

```typescript
pluginApi.registry.registerFileAction({
  id: 'my-action',
  icon: 'my-action-icon',
  label: 'Edit',
  shouldRender(fileEntry) {
    return fileEntry.isSuccess && fileEntry.isImage;
  },
  onClick(fileEntry) {
    uploaderApi.setCurrentActivity('my-activity', { fileId: fileEntry.internalId });
    uploaderApi.setModalState(true);
  },
});
```

| Property       | Type                                                    | Required | Description                                                                                                                                                                          |
| -------------- | ------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`           | `string`                                                | Yes      | Unique action identifier.                                                                                                                                                            |
| `icon`         | `string`                                                | Yes      | Name of a registered icon.                                                                                                                                                           |
| `label`        | `string`                                                | Yes      | An l10n key registered via [`registerL10n`](/docs/file-uploader/plugins/assets/#registerl10n). Plain strings are also accepted but will produce a console warning. Prefer l10n keys. |
| `shouldRender` | `(fileEntry: OutputFileEntry) => boolean`               | Yes      | Return `true` to show the action for a given file.                                                                                                                                   |
| `onClick`      | `(fileEntry: OutputFileEntry) => void \| Promise<void>` | Yes      | Called when the user clicks the action.                                                                                                                                              |

`OutputFileEntry` is described in the [API reference](/docs/file-uploader/api/#output-file-entry).

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.

```typescript
pluginApi.registry.registerFileHook({
  type: 'beforeUpload',
  async handler({ file, signal }) {
    const processed = await processFile(file, { signal });
    return { file: processed };
  },
});
```

| Property  | Type / Value                         | Description                                                                                                                |
| --------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `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. |
| `timeout` | `number`                             | Maximum milliseconds to wait for the handler before skipping it. Default: `30000`.                                         |

The handler context contains:

| Property | Type           | Description                                                                                                                                                |
| -------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `file`   | `File \| Blob` | The file to process.                                                                                                                                       |
| `signal` | `AbortSignal`  | Fires 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.

> **Warning**
>
> **Warning:** 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.

> **Note**
>
> **Note:** 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.

```typescript
// onAdd — runs as soon as the file is added to the uploader
pluginApi.registry.registerFileHook({
  type: 'onAdd',
  async handler({ file }) {
    if (file.type) return { file };

    const detected = await detectMimeFromBytes(file);
    const fileName = file instanceof File ? file.name : null

    return {
      file: new File([file], fileName, { type: detected }),
    };
  },
});

// beforeUpload — runs right before upload, with cancellation and custom timeout
pluginApi.registry.registerFileHook({
  type: 'beforeUpload',
  timeout: 60_000, // allow up to 60s for heavy processing
  async handler({ file, signal }) {
    const resized = await resizeImage(file, { maxWidth: 1920, signal });
    return { file: resized };
  },
});
```

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).

```typescript
pluginApi.registry.registerConfig({
  name: 'myApiKey',
  defaultValue: '',
  attribute: true,             // expose as my-api-key="..." HTML attribute (default: true)
  fromAttribute: (val) => val ?? '',
});
```

| Property        | Type                           | Required | Description                                                                                                  |
| --------------- | ------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------ |
| `name`          | `string`                       | Yes      | camelCase option name.                                                                                       |
| `defaultValue`  | `T`                            | Yes      | Value used when not explicitly set.                                                                          |
| `attribute`     | `boolean`                      | No       | Whether to map to/from an HTML attribute. Default: `true`.                                                   |
| `fromAttribute` | `(value: string \| null) => T` | No       | Parse attribute string to config value. Defaults to returning the raw string.                                |
| `toAttribute`   | `(value: T) => string \| null` | No       | Serialize config value to attribute string for reflection.                                                   |
| `normalize`     | `(value: unknown) => T`        | No       | Validate 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:

```typescript
pluginApi.registry.registerConfig({
  name: 'myMaxResults',
  defaultValue: 24,
  fromAttribute: (val) => (val !== null ? parseInt(val, 10) : 24),
  normalize: (val) => (typeof val === 'number' && val > 0 ? val : 24),
});
```

**TypeScript: declaring a custom config key**

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

```typescript
declare module '@uploadcare/file-uploader' {
  interface CustomConfig {
    myApiKey: string;
  }
}
```