Plugin Development

Most teams never need this page. If the built-in plugins already generate what you need, use them.

Plugin development is the advanced path for building Typeweaver plugins that hook into the generation lifecycle and emit new artifact families from the same spec: framework adapters, infra helpers, custom clients, internal SDK glue, and similar outputs.

When to build a plugin

Build a plugin when all of these are true:

  • the output should stay tied to the Typeweaver contract
  • regeneration should happen as part of the normal typeweaver generate workflow
  • the output is broad enough that templates and file generation pay off

Do not start with a plugin if a small post-processing script or a few handwritten helpers will do.

Install the generation package

Plugin authors need the generation package in addition to core:

npm install -D @rexeus/typeweaver-gen

The public building blocks

The main APIs plugin authors touch are:

  • BasePlugin from @rexeus/typeweaver-gen
  • GeneratorContext
  • NormalizedSpec
  • renderTemplate(...)
  • writeFile(...)
  • copyLibFiles(...)

That is enough for most generators.

Plugin lifecycle

Plugins run through four phases:

PhasePurpose
initialize(context)Validate prerequisites or read config before generation starts
collectResources(normalizedSpec)Inspect or transform the normalized resource model
generate(context)Render templates and write generated files
finalize(context)Do any cleanup or last-step output work

You only need to implement the phases your plugin actually uses.

Minimal plugin shape

import { BasePlugin, type GeneratorContext } from "@rexeus/typeweaver-gen";

export class MyPlugin extends BasePlugin {
  public name = "my-plugin";

  public override generate(context: GeneratorContext): void {
    for (const resource of context.normalizedSpec.resources) {
      const content = context.renderTemplate("Resource.ejs", {
        resource,
        coreDir: context.coreDir,
      });

      context.writeFile(`${resource.name}/${resource.name}Artifacts.ts`, content);
    }
  }
}

GeneratorContext in practice

GeneratorContext gives your plugin the normalized contract plus file-writing helpers.

The pieces you will use most often are:

  • normalizedSpec.resources for resource and operation iteration
  • normalizedSpec.responses for canonical response definitions
  • writeFile(relativePath, content) to emit tracked files
  • renderTemplate(templatePath, data) to render EJS templates
  • getResourceOutputDir(resourceName) and related path helpers

The normalized model keeps plugin code focused on generation, not on spec parsing.

Templates

Templates are usually the cleanest way to keep generators readable.

const content = context.renderTemplate("HttpAdapter.ejs", {
  resource,
  operation,
  coreDir: context.coreDir,
});

The template receives the data object as locals. Keep template inputs small and explicit.

Shipping runtime helpers with copyLibFiles

If your generated files depend on shared runtime code, place those helper files in your plugin's lib output and copy them into the generated folder.

import path from "node:path";
import { fileURLToPath } from "node:url";
import { BasePlugin, type GeneratorContext } from "@rexeus/typeweaver-gen";

const moduleDir = path.dirname(fileURLToPath(import.meta.url));

export class MyPlugin extends BasePlugin {
  public name = "my-plugin";

  public override generate(context: GeneratorContext): void {
    this.copyLibFiles(context, path.join(moduleDir, "lib"), this.name);
  }
}

That is how plugins such as aws-cdk ship reusable runtime helpers alongside generated files.

Dependency ordering

Plugins can declare dependencies with depends.

export class AwsLikePlugin extends BasePlugin {
  public name = "aws-like";
  public override depends = ["types"];
}

The CLI loads plugins in dependency order. If a dependency is missing, generation fails early instead of producing partial output.

Guardrails for good plugins

Keep plugin scope tight:

  • one plugin should usually solve one generation concern
  • prefer reading NormalizedSpec over re-parsing authoring files
  • prefer writeFile(...) over manual file writes so output stays tracked
  • keep generated code deterministic so repeated runs produce stable diffs
  • do not hide contract changes behind custom conventions that spec authors cannot see

What to avoid

Avoid plugins that:

  • duplicate existing built-in output with only naming tweaks
  • depend on private normalization details that are not part of the documented surface
  • mix unrelated concerns such as clients, infrastructure, and runtime bootstrapping in one generator

If your goal is just a project-specific artifact, Custom Generators is usually the better starting point.

Was this page helpful?