# How to Implement a Custom Transformer Plugin for Advanced Image Manipulation in PicList-Core

> Learn to implement a custom transformer plugin for advanced image manipulation in PicList-Core. Create, register, and configure your plugin to extend image processing capabilities.

- Repository: [Kuingsmile/piclist-core](https://github.com/kuingsmile/piclist-core)
- Tags: how-to-guide
- Published: 2026-03-05

---

**To implement a custom transformer plugin, create a module exporting an `IPlugin` object with a `handle` method that mutates `ctx.output`, register it under `ctx.helper.transformer`, and configure PicList-Core to use your transformer via the `transformer` setting.**

PicList-Core is an extensible image upload framework that delegates image processing to a plugin-based architecture. When you need to perform advanced image manipulation—such as resizing, format conversion, or watermarking—you can implement a custom transformer plugin that conforms to the `IPlugin` interface defined in [`src/types/index.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/types/index.ts). This approach allows you to integrate libraries like Sharp or Jimp without modifying the core codebase.

## Understanding the Transformer Architecture

PicList-Core treats image processing steps as plugins that conform to the `IPlugin` contract. A transformer is simply a plugin registered under `ctx.helper.transformer`, which the core invokes during the `transform` lifecycle stage.

The framework ships with two built-in transformers located in `src/plugins/transformer/`:

- **`path`** – Returns the source image path unchanged ([`src/plugins/transformer/path.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/plugins/transformer/path.ts))
- **`base64`** – Converts the image to a base64 string ([`src/plugins/transformer/base64.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/plugins/transformer/base64.ts))

Both are registered in [`src/plugins/transformer/index.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/plugins/transformer/index.ts) using the registry pattern:

```typescript
ctx.helper.transformer.register('path', ImgFromPath)
ctx.helper.transformer.register('base64', ImgFromBase64)

```

When an upload begins, the lifecycle step defined in [`src/core/Lifecycle.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/core/Lifecycle.ts) (lines 495-511) retrieves the transformer name from `picBed.transformer` and executes its `handle` method:

```typescript
let transformer = ctx.helper.transformer.get(type)
if (!transformer) { … }
await transformer?.handle(ctx)

```

## Creating the Transformer Interface

A valid transformer plugin must export an object satisfying the `IPlugin` interface defined at lines 94-99 of [`src/types/index.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/types/index.ts). The contract requires two primary components:

### The `handle` Method

This async method receives the `IPicGo` context object, performs image manipulation, and returns the mutated context:

```typescript
const handle = async (ctx: IPicGo): Promise<IPicGo> => {
  // Access raw input via ctx.input (Buffer[] or string[])
  // Write processed results to ctx.output
  return ctx
}

```

### The Optional `config` Function

If your transformer requires user-configurable options, export a `config` function that returns an array of configuration prompts. PicList-Core uses these definitions to generate CLI and GUI configuration dialogs:

```typescript
const config = (ctx: IPicGo) => [
  {
    name: 'width',
    type: 'input',
    default: 800,
    message: 'Target width',
  }
]

```

## Step-by-Step Implementation

Follow these steps to implement and register your custom transformer:

1. **Create a new TypeScript file** in `src/plugins/transformer/` (for built-in) or as a standalone npm package (for external distribution).

2. **Implement the `handle` method** to process `ctx.input`. This array contains raw image data as Buffers or file paths. Mutate `ctx.output` with the processed results.

3. **Optionally define `config`** if users need to adjust parameters like quality settings or dimensions.

4. **Register the plugin** either by adding it to the built-in registry or loading it as an external plugin via [`src/lib/PluginLoader.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/lib/PluginLoader.ts).

## Complete Example: Sharp-Based Image Resizer

The following implementation creates a transformer that resizes images using the Sharp library. Save this as [`src/plugins/transformer/sharpResize.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/plugins/transformer/sharpResize.ts):

```typescript
import sharp from 'sharp'
import { IPicGo, IPlugin } from '../../types'

const handle = async (ctx: IPicGo): Promise<IPicGo> => {
  // Process each image in the input queue
  const resized = await Promise.all(
    ctx.input.map(async (img) => {
      // Normalize input to Buffer
      const buffer = Buffer.isBuffer(img) ? img : await fs.readFile(img)
      
      // Apply Sharp transformations
      return await sharp(buffer)
        .resize(800, 600, { fit: 'inside' })
        .toBuffer()
    })
  )
  
  // Update the output queue with transformed images
  ctx.output = resized
  return ctx
}

// Configuration schema for CLI/GUI settings
const config = (ctx: IPicGo) => [
  {
    name: 'width',
    type: 'input',
    default: 800,
    message: 'Target width',
  },
  {
    name: 'height',
    type: 'input',
    default: 600,
    message: 'Target height',
  },
]

export default { handle, config } satisfies IPlugin

```

This module satisfies the `IPlugin` contract by exporting both the processing logic and configuration schema. When registered, PicList-Core will invoke `handle(ctx)` during the transform stage, feeding image data through Sharp before passing results to the uploader.

## Registration Methods

You can register your transformer using one of two approaches depending on your distribution strategy.

### Method 1: Built-in Registration

For core modifications or internal forks, import your transformer into [`src/plugins/transformer/index.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/plugins/transformer/index.ts) and register it alongside the built-in options:

```typescript
import SharpResize from './sharpResize'

const buildInTransformers = () => {
  return {
    register(ctx: IPicGo) {
      ctx.helper.transformer.register('path', ImgFromPath)
      ctx.helper.transformer.register('base64', ImgFromBase64)
      ctx.helper.transformer.register('sharpResize', SharpResize) // Your transformer
    },
  }
}

```

After rebuilding, users can select your transformer via:

```bash
picgo set transformer

```

### Method 2: External Plugin Distribution

To distribute your transformer as a standalone package without modifying core files:

1. Package your code as an npm module prefixed with `picgo-plugin-` (e.g., `picgo-plugin-sharp-resize`)
2. Install it via npm: `npm i picgo-plugin-sharp-resize`
3. Load it using the plugin CLI: `picgo plugin add picgo-plugin-sharp-resize`

The `PluginLoader` class in [`src/lib/PluginLoader.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/lib/PluginLoader.ts) (lines 47-66) automatically resolves modules matching the `picgo-plugin-*` pattern, invokes their registration logic, and makes the transformer available in the configuration options.

## Summary

- **Transformer plugins** in PicList-Core implement the `IPlugin` interface with a mandatory `handle` method and optional `config` function.
- **Input data** arrives via `ctx.input` as Buffers or file paths; processed results must be written to `ctx.output`.
- **Registration** occurs through `ctx.helper.transformer.register()` either in [`src/plugins/transformer/index.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/plugins/transformer/index.ts) for built-in plugins or via the external plugin loader in [`src/lib/PluginLoader.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/lib/PluginLoader.ts).
- **Lifecycle execution** happens in [`src/core/Lifecycle.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/core/Lifecycle.ts) (lines 495-511), which retrieves the configured transformer and awaits its `handle` method during the upload process.
- **External libraries** like Sharp, Jimp, or GraphicsMagick can be integrated by processing `ctx.input` within the `handle` method and outputting transformed Buffers.

## Frequently Asked Questions

### What is the `IPlugin` interface in PicList-Core?

The `IPlugin` interface is defined in [`src/types/index.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/types/index.ts) (lines 94-99) and serves as the contract for all plugins. It requires a `handle` method that accepts and returns the `IPicGo` context object, and optionally includes a `config` function that returns an array of configuration option definitions for the CLI and GUI settings panels.

### How do I access image data within a custom transformer?

Image data is available in the `ctx.input` array, which contains either Buffer objects or file path strings depending on how the image was loaded. Your `handle` method must process these entries and assign the resulting Buffers to `ctx.output` for the next stage in the upload lifecycle.

### Can I use external image processing libraries like Sharp or Jimp?

Yes. Because the transformer runs in a Node.js environment, you can import any npm package—such as Sharp, Jimp, or GraphicsMagick—into your transformer module. Process the Buffers from `ctx.input` using your chosen library and return the modified Buffers in `ctx.output`.

### How do I distribute my transformer as an external plugin?

Package your transformer as an npm module with the `picgo-plugin-` prefix, ensuring it exports an object with `handle` and optionally `config`. After installation via `npm install`, users can load it using `picgo plugin add <package-name>`. The `PluginLoader` in [`src/lib/PluginLoader.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/lib/PluginLoader.ts) will automatically register the transformer under `ctx.helper.transformer`, making it selectable via `picgo set transformer` without any core code modifications.