Developer working at standing desk with code on screen
Developer Experience

Building a Custom Texkit Plugin:
From Idea to npm Package

Extend Texkit's capabilities with community plugins. Learn how to package a custom transformer, validator, or formatter for the wider ecosystem.

By Sarah Jenkins • Senior DevRel • October 24, 2023

Compose. Compile. Ship.

The Plugin API

What the API exposes

Texkit is designed to be extensible. The plugin API is built on Node.js, exposing a clear, functional contract for developers to hook into the document lifecycle. You don't need to understand the entire core engine to contribute — you only need to understand where your specific transformation fits.

At its core, the API is a collection of stage hooks. Each hook corresponds to a specific moment in the document pipeline: when it is parsed, when it is validated, when it is transformed, and when it is emitted.

Plugin Anatomy

Transformer, Validator, Formatter

Plugins typically fall into one of three architectural categories, each handling a specific responsibility in the pipeline.

Transformer

Data Manipulation

Transformers modify the document object tree. Use the onTransform hook to rename fields, normalize data types, or parse complex nested structures that aren't supported by the core parser.

Validator

Schema Enforcement

Validators ensure data integrity. Implement onValidate to run JSON Schema checks, custom business rules, or required field presence checks before the document is rendered.

Formatter

Output Generation

Formatters handle the final output. Use onRender to inject custom HTML snippets, generate specific file formats like PDF or SVG, or perform post-processing on the rendered HTML.

Code Walkthrough

Building a CSV-to-Table Transformer

Let's build a simple plugin that reads a CSV file embedded in a document, parses it into a JSON table, and makes it accessible to the template engine.

Create a new file named csv-transformer.js:

// csv-transformer.js
const fs = require('fs');
const { parse } = require('csv-parse/sync');

module.exports = {
  name: 'csv-table-transformer',
  version: '1.0.0',

  // Hook into the transform stage
  onTransform: async (ctx) => {
    // Look for a frontmatter property named 'csvData'
    if (!ctx.meta.csvData) return;

    const filePath = ctx.meta.csvData;
    
    // Check if the file exists locally
    if (!fs.existsSync(filePath)) {
      throw new Error(`CSV file not found at ${filePath}`);
    }

    // Parse the CSV content
    const records = parse(fs.readFileSync(filePath, 'utf-8'), {
      columns: true,
      skip_empty_lines: true
    });

    // Attach the parsed data to the document context
    ctx.document.tables = ctx.document.tables || {};
    ctx.document.tables.csv = records;
  }
};
Testing

Testing with texkit plugin:test

Before publishing, you must ensure your plugin doesn't break the pipeline. Texkit includes a dedicated testing harness.

Run the following command in your plugin directory:

$ texkit plugin:test

This command spins up a sandbox environment, loads your plugin, and executes a suite of synthetic documents to verify that your hooks return valid results and handle edge cases (like missing files or malformed CSVs) gracefully.

Distribution

Publishing to npm

Once your tests pass and your package.json is configured with the correct entry point, you are ready to publish.

Texkit plugins must follow a naming convention to ensure they are discoverable. All community plugins must use the texkit- prefix.

$ npm publish

For example, a plugin named @my-org/my-awesome-plugin would be renamed to texkit-my-awesome-plugin for the public registry.

Community

The Texkit Plugin Registry

After publishing to npm, we recommend submitting your plugin to the official Texkit Community Plugin Registry. This is a curated list of vetted plugins that users can browse directly through the Texkit Cloud dashboard.

Submitting to the registry is free and helps your plugin get found by thousands of developers looking to extend Texkit's capabilities.

Ready to build?

Start creating your own plugins.