Plugin System Overview

Extend tulip with TypeScript/JavaScript plugins that hook into every stage of the build pipeline.

How plugins work

Tulip plugins are TypeScript or JavaScript modules with typed contracts. Each plugin lives in its own directory under plugins/ and declares the lifecycle hooks it needs. The engine loads plugins in the order they appear in tulip.toml and calls each hook at the appropriate stage of the build.

Plugins run inside tulip's built-in JavaScript engine (boa). No Node.js or npm needed.

Plugin structure

Every plugin follows a standard layout:

plugins/seo/
  plugin.toml     # metadata and default config
  index.ts        # entry point (TulipPlugin interface)
  types.d.ts      # TypeScript type definitions
  README.md       # documentation
  • plugin.toml — declares the plugin name, version, description, author, and default configuration values.
  • index.ts — exports a TulipPlugin object with one or more lifecycle hooks.
  • types.d.ts — provides TypeScript definitions for the plugin contract.

Installing plugins

Install a plugin from a GitHub repository:

tulip plugin add https://github.com/user/tulip-plugin-seo@v0.1.0

This downloads the plugin, reads the name from plugin.toml, installs to plugins/{name}/, and saves the source URL in tulip.toml. Pin a version with @tag.

After cloning a project, install all missing plugins:

tulip install

You can also scaffold a new plugin locally:

tulip plugin new seo

Lifecycle hooks

Plugins can implement any combination of hooks. All hooks are optional:

HookWhen it fires
setup(config)Once when the plugin is loaded
beforeBuild(ctx)Before the build starts
transformMarkdown(content, page)Before markdown is converted to HTML
transformContext(ctx, page)Before template rendering
transformHtml(html, page)After template rendering
head()Collect tags to inject into the HTML head
generatePages(ctx)Create new files (RSS, sitemap, etc.)
afterBuild(ctx)After the build completes

Execution order

Plugins run in the order they are declared in tulip.toml. For hooks that transform content, the output of one plugin is passed as input to the next:

[[plugins]]
name = "seo"          # runs first
config = { site_name = "My Site" }

[[plugins]]
name = "analytics"    # runs second
config = { id = "G-XXXXX" }

Per-hook timing

Every hook reports its execution time in the build output. Hooks exceeding 500ms are flagged as slow:

  plugin: seo.setup               0.1ms
  plugin: seo.beforeBuild         0.2ms
  plugin: seo.transformHtml       0.3ms
  plugin: seo.generatePages       0.5ms
  plugin: analytics.transformHtml 0.2ms
  plugin: analytics.afterBuild    1.2ms  (slow)

Configuration

Default config values are defined in plugin.toml. You override them per-project in tulip.toml:

# plugin.toml (defaults)
[config]
site_name = "My Site"
twitter_handle = ""

# tulip.toml (project overrides)
[[plugins]]
name = "seo"
config = { site_name = "My Blog", twitter_handle = "@myblog" }

The merged config object is passed to the setup hook when the plugin loads.