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

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. 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/:

Both are registered in src/plugins/transformer/index.ts using the registry pattern:

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 (lines 495-511) retrieves the transformer name from picBed.transformer and executes its handle method:

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

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:

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.

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:

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 and register it alongside the built-in options:

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:

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 (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 for built-in plugins or via the external plugin loader in src/lib/PluginLoader.ts.
  • Lifecycle execution happens in 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 (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 will automatically register the transformer under ctx.helper.transformer, making it selectable via picgo set transformer without any core code modifications.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →