# How File Format Handlers Are Implemented in convert: A Deep Dive into the p2r3/convert Architecture

> Explore how p2r3/convert implements file format handlers using the FormatHandler interface. Learn about standardized init and doConvert methods for WASM and JavaScript conversions.

- Repository: [p2r3/convert](https://github.com/p2r3/convert)
- Tags: architecture
- Published: 2026-02-19

---

**File format handlers in the p2r3/convert repository implement the `FormatHandler` interface defined in [`src/FormatHandler.ts`](https://github.com/p2r3/convert/blob/main/src/FormatHandler.ts), exposing standardized `init()` and `doConvert()` methods that allow the application to register, initialize, and execute conversions across WASM-based tools like FFmpeg and pure-JavaScript implementations.**

The `p2r3/convert` project treats every supported conversion as a modular **file format handler** that follows a strict architectural contract. This design decouples the user interface from underlying conversion tools, enabling support for external WASM binaries alongside native JavaScript implementations.

## The FormatHandler Interface: The Contract for All File Format Handlers

Every handler must satisfy the `FormatHandler` interface located in [`src/FormatHandler.ts`](https://github.com/p2r3/convert/blob/main/src/FormatHandler.ts). This contract ensures that the runtime can initialize, query, and execute any converter without knowing implementation details.

```typescript
export interface FormatHandler {
  /** Human‑readable name (e.g. “FFmpeg”) */
  name: string;
  /** List of formats the handler can read/write */
  supportedFormats?: FileFormat[];
  /** If true the handler can accept *any* input when no direct conversion exists */
  supportAnyInput?: boolean;
  /** Set to true after successful initialization */
  ready: boolean;
  /** Initialise external tools, populate `supportedFormats` */
  init: () => Promise<void>;
  /** Core conversion routine */
  doConvert(
    inputFiles: FileData[],
    inputFormat: FileFormat,
    outputFormat: FileFormat,
    args?: string[]
  ): Promise<FileData[]>;
}

```

The `doConvert` method receives input files, format descriptors, and optional CLI-style arguments, returning a promise that resolves to the converted file data.

## Defining Reusable Format Definitions with CommonFormats

To avoid duplicating MIME types and extensions across handlers, [`src/CommonFormats.ts`](https://github.com/p2r3/convert/blob/main/src/CommonFormats.ts) centralizes format metadata. The `CommonFormats` object provides reusable `FormatDefinition` instances that handlers can specialize.

```typescript
export const Category = { IMAGE: "image", TEXT: "text", … };

export const CommonFormats = {
  PNG: new FormatDefinition(
    "Portable Network Graphics",
    "png", "png", "image/png", Category.IMAGE
  ),
  // … many more definitions
};

```

Handlers consume these definitions through the `supported()` method or the fluent `builder()` API to construct concrete `FileFormat` objects that include directionality flags (`from`, `to`).

## Implementing File Format Handlers: Two Approaches

The architecture supports both WASM-based external tools and pure-JavaScript implementations.

### WASM-Based Handlers: The FFmpeg Example

[`src/handlers/FFmpeg.ts`](https://github.com/p2r3/convert/blob/main/src/handlers/FFmpeg.ts) wraps the FFmpeg WASM binary. The handler initializes the external tool, queries its capabilities, and performs conversions using an in-memory filesystem.

```typescript
class FFmpegHandler implements FormatHandler {
  name = "FFmpeg";
  supportedFormats: FileFormat[] = [];
  ready = false;
  #ffmpeg?: FFmpeg;          // WASM wrapper

  async init() {
    this.#ffmpeg = new FFmpeg();
    await this.#ffmpeg.load({ coreURL: "/convert/wasm/ffmpeg-core.js" });
    // Query FFmpeg for supported codecs, build `supportedFormats`
    …
    this.ready = true;
  }

  async doConvert(inputFiles, inputFormat, outputFormat, args) {
    // Write each input file to the in‑memory FS,
    // Build an FFmpeg command line, run it,
    // Read the output bytes, clean up temporary files.
    …
  }
}

```

The `init()` method populates `supportedFormats` by parsing `ffmpeg -formats` and `ffmpeg -h muxer=…`, while `doConvert()` uses the WASM API methods `writeFile`, `exec`, and `readFile` to shuttle data between the browser and the FFmpeg process.

### Pure-JavaScript Handlers: The TextEncoding Example

[`src/handlers/textEncoding.ts`](https://github.com/p2r3/convert/blob/main/src/handlers/textEncoding.ts) requires no external binaries. It implements encoding detection and transcoding using native browser APIs.

```typescript
export default class TextEncodingHandler implements FormatHandler {
  name = "TextEncoding";
  supportedFormats = [
    CommonFormats.TEXT.supported("txt", true, true, true),
    { name: "Plain Text (UTF‑8 with BOM)", … internal: "utf8WB", … },
    // UTF‑16/32 variants…
  ];
  ready = true;
  init = async () => { this.ready = true };

  async doConvert(inputFiles, inputFormat, outputFormat) {
    // Detect input encoding → decode → re‑encode to the selected output encoding.
    …
  }
}

```

Because this handler relies solely on JavaScript, it sets `ready = true` immediately and implements `init()` as a no-op.

## Registering and Initializing File Format Handlers

The runtime discovers handlers through a barrel file at [`src/handlers/index.ts`](https://github.com/p2r3/convert/blob/main/src/handlers/index.ts). Each instantiation is wrapped in a `try … catch` block to ensure that missing optional dependencies do not crash the application.

```typescript
const handlers: FormatHandler[] = [];
try { handlers.push(new FFmpegHandler()) } catch (_) {}
try { handlers.push(new ImageMagickHandler()) } catch (_) {}
try { handlers.push(new TextEncodingHandler()) } catch (_) {}
// … dozens more handlers
export default handlers;

```

The entry point at [`src/main.ts`](https://github.com/p2r3/convert/blob/main/src/main.ts) imports this list, initializes every handler, and constructs the conversion graph used by the UI.

```typescript
import handlers from "./handlers";

(async () => {
  for (const h of handlers) await h.init();
  window.supportedFormatCache = new Map(
    handlers.flatMap(h => h.supportedFormats?.map(f => [f.format, f]))
  );
  window.traversionGraph.init(window.supportedFormatCache, handlers);
})();

```

## How to Add a New File Format Handler to convert

Extending the system requires implementing the `FormatHandler` interface and registering the class:

1. **Create a class** implementing `FormatHandler` in [`src/handlers/YourHandler.ts`](https://github.com/p2r3/convert/blob/main/src/handlers/YourHandler.ts).
2. **Expose a `name`** and populate `supportedFormats` (use `CommonFormats` when possible).
3. Implement `init()` to populate `supportedFormats` and set `ready`.
4. Implement `doConvert()` to perform the actual conversion logic.
5. **Add the class to [`src/handlers/index.ts`](https://github.com/p2r3/convert/blob/main/src/handlers/index.ts)** inside a `try … catch` block.
6. Run the test suite (`npm test`) to verify integration.

## Summary

- The **file format handler** architecture in `p2r3/convert` centers on the `FormatHandler` interface defined in [`src/FormatHandler.ts`](https://github.com/p2r3/convert/blob/main/src/FormatHandler.ts), which standardizes initialization and conversion across all handlers.
- **Format definitions** are centralized in [`src/CommonFormats.ts`](https://github.com/p2r3/convert/blob/main/src/CommonFormats.ts) to ensure consistent MIME types and extensions.
- Handlers can wrap **WASM tools** like FFmpeg ([`src/handlers/FFmpeg.ts`](https://github.com/p2r3/convert/blob/main/src/handlers/FFmpeg.ts)) or implement **pure-JavaScript** logic like text encoding ([`src/handlers/textEncoding.ts`](https://github.com/p2r3/convert/blob/main/src/handlers/textEncoding.ts)).
- Runtime discovery occurs through [`src/handlers/index.ts`](https://github.com/p2r3/convert/blob/main/src/handlers/index.ts), with initialization orchestrated in [`src/main.ts`](https://github.com/p2r3/convert/blob/main/src/main.ts) to build the conversion graph.

## Frequently Asked Questions

### What is the FormatHandler interface in convert?

The `FormatHandler` interface is the core contract defined in [`src/FormatHandler.ts`](https://github.com/p2r3/convert/blob/main/src/FormatHandler.ts) that every file format handler must implement. It requires a `name` property, a `ready` boolean, an `init()` method for setup, and a `doConvert()` method that performs the actual file transformation. This standardization allows the application to treat WASM-based tools and pure-JavaScript implementations identically.

### How does convert handle different file formats without external tools?

For formats that do not require external binaries, convert uses pure-JavaScript handlers like `TextEncodingHandler` in [`src/handlers/textEncoding.ts`](https://github.com/p2r3/convert/blob/main/src/handlers/textEncoding.ts). These handlers implement the same `FormatHandler` interface but perform conversions using native browser APIs or JavaScript libraries, setting `ready = true` immediately and skipping WASM initialization. This approach works for text encodings, basic image manipulations, and other browser-native capabilities.

### Where are file format handlers registered in the convert codebase?

All handlers are registered in the barrel file [`src/handlers/index.ts`](https://github.com/p2r3/convert/blob/main/src/handlers/index.ts), where each handler class is instantiated inside a `try … catch` block to prevent missing dependencies from crashing the application. The resulting array is exported and consumed by [`src/main.ts`](https://github.com/p2r3/convert/blob/main/src/main.ts), which calls `init()` on each handler and constructs the conversion graph used by the user interface.

### Can I add custom file format handlers to convert?

Yes, you can extend convert by creating a new class that implements the `FormatHandler` interface, implementing the `init()` and `doConvert()` methods, and adding the class to [`src/handlers/index.ts`](https://github.com/p2r3/convert/blob/main/src/handlers/index.ts). The architecture is designed for extensibility, allowing you to wrap new WASM tools or write pure-JavaScript converters while leveraging the existing `CommonFormats` definitions for consistent metadata.