Skip to content

Plugin System Overview

PikaCSS has a powerful plugin system that lets you extend the engine's behavior at every stage — from config resolution to style generation. Plugins are plain objects created with the defineEnginePlugin() helper.

EnginePlugin Interface

Every plugin must have a name and can optionally define an order and hook functions:

ts
import type { AtomicStyle, Engine, EngineConfig, ResolvedEngineConfig, ResolvedStyleDefinition, ResolvedStyleItem } from '@pikacss/core'

// This is a simplified view of the EnginePlugin interface.
// See packages/core/src/internal/plugin.ts for the full definition.
export interface EnginePlugin {
	/** Unique plugin name (required) */
	name: string

	/** Execution order: 'pre' (0) → default (1) → 'post' (2) */
	order?: 'pre' | 'post'

	// --- Async hooks (can return modified payload) ---

	/** Modify the raw config before it is resolved */
	configureRawConfig?: (config: EngineConfig) => EngineConfig | void | Promise<EngineConfig | void>
	/** Modify the resolved config */
	configureResolvedConfig?: (resolvedConfig: ResolvedEngineConfig) => ResolvedEngineConfig | void | Promise<ResolvedEngineConfig | void>
	/** Modify the engine instance after creation */
	configureEngine?: (engine: Engine) => Engine | void | Promise<Engine | void>
	/** Transform selectors during style extraction */
	transformSelectors?: (selectors: string[]) => string[] | void | Promise<string[] | void>
	/** Transform style items during engine.use() */
	transformStyleItems?: (styleItems: ResolvedStyleItem[]) => ResolvedStyleItem[] | void | Promise<ResolvedStyleItem[] | void>
	/** Transform style definitions during style extraction */
	transformStyleDefinitions?: (styleDefinitions: ResolvedStyleDefinition[]) => ResolvedStyleDefinition[] | void | Promise<ResolvedStyleDefinition[] | void>

	// --- Sync hooks (notification only) ---

	/** Called after the raw config is settled */
	rawConfigConfigured?: (config: EngineConfig) => void
	/** Called when preflight CSS changes */
	preflightUpdated?: () => void
	/** Called when a new atomic style is generated */
	atomicStyleAdded?: (atomicStyle: AtomicStyle) => void
	/** Called when autocomplete configuration changes */
	autocompleteConfigUpdated?: () => void
}

Minimal Plugin

The simplest possible plugin only needs a name:

ts
import { defineEnginePlugin } from '@pikacss/core'

export const myPlugin = defineEnginePlugin({
	name: 'my-plugin',
})

Plugin Ordering

The order property controls when a plugin's hooks execute relative to other plugins:

order valuePriorityRuns
'pre'0First
undefined (default)1Normal
'post'2Last

Within the same priority group, plugins run in the order they are registered. PikaCSS's built-in core plugins (e.g. core:variables, core:keyframes, core:selectors, core:shortcuts, core:important) are loaded before user plugins, then all plugins are sorted together.

ts
import { defineEnginePlugin } from '@pikacss/core'

// Runs first (order: 'pre' → priority 0)
export const earlyPlugin = defineEnginePlugin({
	name: 'early-plugin',
	order: 'pre',
	configureRawConfig(config) {
		// Runs before default and post plugins
		return config
	},
})

// Runs second (order: undefined → priority 1)
export const normalPlugin = defineEnginePlugin({
	name: 'normal-plugin',
	// order is omitted — defaults to normal priority
	configureRawConfig(config) {
		return config
	},
})

// Runs last (order: 'post' → priority 2)
export const latePlugin = defineEnginePlugin({
	name: 'late-plugin',
	order: 'post',
	configureRawConfig(config) {
		// Runs after pre and default plugins
		return config
	},
})

Hook Lifecycle

During createEngine(config), hooks are invoked in this order:

createEngine(config)

├─ 1. configureRawConfig    (async)  — Modify the raw config
├─ 2. rawConfigConfigured   (sync)   — Notification: raw config settled
├─ 3. configureResolvedConfig (async) — Modify the resolved config
├─ 4. configureEngine        (async)  — Modify/set up the engine instance

└─ Engine is ready

   ├─ During engine.use(...):
   │   ├─ 5. transformStyleItems       (async) — Transform style items
   │   ├─ 6. transformSelectors        (async) — Transform selectors
   │   └─ 7. transformStyleDefinitions (async) — Transform style definitions

   ├─ When preflights change:
   │   └─ 8. preflightUpdated          (sync)  — Notification

   ├─ When atomic style is generated:
   │   └─ 9. atomicStyleAdded          (sync)  — Notification

   └─ When autocomplete config changes:
       └─ 10. autocompleteConfigUpdated (sync)  — Notification

Hooks 1–4 run once during engine creation. Hooks 5–10 run at runtime whenever the corresponding event occurs.

Async Hooks (Transform)

Async hooks receive a payload and can return a modified version. The modified payload is then passed to the next plugin in order. If a hook returns void or undefined, the current payload is kept unchanged.

ts
import { defineEnginePlugin } from '@pikacss/core'

export const asyncHookPlugin = defineEnginePlugin({
	name: 'async-hook-example',

	// Async hook: modify the raw config before it is resolved.
	// Return the modified config to pass it to the next plugin,
	// or return void/undefined to keep the current value.
	configureRawConfig(config) {
		config.prefix = 'pk-'
		return config
	},

	// Async hook: transform selectors during style extraction.
	// The returned array replaces the input for the next plugin.
	transformSelectors(selectors) {
		return selectors.map(s => s.replace('$hover', '&:hover'))
	},
})

configureRawConfig

  • When: During createEngine(), before config resolution
  • Receives: config: EngineConfig — the raw user config
  • Returns: EngineConfig | void
  • Purpose: Add plugins, modify prefix, add preflights, or set any config option before resolution

configureResolvedConfig

  • When: During createEngine(), after config resolution
  • Receives: resolvedConfig: ResolvedEngineConfig — the fully resolved config
  • Returns: ResolvedEngineConfig | void
  • Purpose: Modify resolved values like the autocomplete config or preflights list

configureEngine

  • When: During createEngine(), after the engine instance is created
  • Receives: engine: Engine — the engine instance
  • Returns: Engine | void
  • Purpose: Set up runtime features, add preflights, configure autocomplete, or attach custom properties to the engine

transformSelectors

  • When: During style extraction (triggered by engine.use() and preflight rendering)
  • Receives: selectors: string[] — the selector chain being processed
  • Returns: string[] | void
  • Purpose: Rewrite, expand, or replace selectors (e.g. mapping $hover to &:hover)

transformStyleItems

  • When: During engine.use(), before style items are resolved
  • Receives: styleItems: ResolvedStyleItem[] — the list of style items
  • Returns: ResolvedStyleItem[] | void
  • Purpose: Add, remove, or transform style items (e.g. expanding shortcuts)

transformStyleDefinitions

  • When: During style extraction, when processing nested style definitions
  • Receives: styleDefinitions: ResolvedStyleDefinition[] — the list of style definitions
  • Returns: ResolvedStyleDefinition[] | void
  • Purpose: Modify style definition objects before they are extracted into atomic styles

Sync Hooks (Notification)

Sync hooks are notification-only — they inform plugins that something happened. They should not return a value.

ts
import { defineEnginePlugin } from '@pikacss/core'

export const syncHookPlugin = defineEnginePlugin({
	name: 'sync-hook-example',

	// Sync notification hook: called after the raw config is settled.
	// Use it to read the config — do NOT return a value.
	rawConfigConfigured(config) {
		console.log('Config settled with prefix:', config.prefix)
	},

	// Sync notification hook: called when a new atomic style is added.
	// Useful for tracking, logging, or collecting generated styles.
	atomicStyleAdded(atomicStyle) {
		console.log('New atomic style:', atomicStyle.id)
	},

	// Sync notification hook: called when preflight CSS changes.
	preflightUpdated() {
		console.log('Preflight updated')
	},

	// Sync notification hook: called when autocomplete config changes.
	autocompleteConfigUpdated() {
		console.log('Autocomplete config updated')
	},
})

rawConfigConfigured

  • When: During createEngine(), after configureRawConfig completes
  • Receives: config: EngineConfig — the settled raw config
  • Purpose: Read the final raw config (e.g. to cache values for later use in other hooks)

preflightUpdated

  • When: Whenever a preflight is added or modified
  • Receives: nothing
  • Purpose: React to preflight changes (e.g. trigger a rebuild)

atomicStyleAdded

  • When: Whenever a new atomic style is generated and stored
  • Receives: atomicStyle: AtomicStyle{ id: string, content: StyleContent }
  • Purpose: Track or log generated atomic styles

autocompleteConfigUpdated

  • When: Whenever the autocomplete configuration changes
  • Receives: nothing
  • Purpose: React to autocomplete changes (e.g. rebuild completion data)

Hook Execution Model

All hooks — both async and sync — follow the same execution rules:

  1. Plugin order: Hooks run plugin-by-plugin in sorted order (pre → default → post)
  2. Payload chaining: For async hooks, if a plugin returns a non-nullish value, that value replaces the payload for the next plugin
  3. Error isolation: If a plugin's hook throws an error, the error is caught and logged. Execution continues with the next plugin — one failing plugin does not break the chain
  4. Skipping: If a plugin does not define a particular hook, it is simply skipped

Complete Example

A full plugin using all available hooks:

ts
import { defineEnginePlugin } from '@pikacss/core'

export function createMyPlugin(options: { prefix?: string } = {}) {
	const { prefix = 'my' } = options

	return defineEnginePlugin({
		name: `${prefix}-plugin`,
		order: 'pre',

		// --- Async hooks (transform) ---

		configureRawConfig(config) {
			// Modify raw config before resolution
			config.prefix = config.prefix || prefix
			return config
		},

		configureResolvedConfig(resolvedConfig) {
			// Modify resolved config after resolution
			return resolvedConfig
		},

		async configureEngine(engine) {
			// Set up engine features, add preflights, etc.
			engine.addPreflight('/* my-plugin preflight */')
			engine.appendAutocompleteExtraProperties('__myProp')
		},

		transformSelectors(selectors) {
			// Transform selectors during style extraction
			return selectors
		},

		transformStyleItems(styleItems) {
			// Transform style items during engine.use()
			return styleItems
		},

		transformStyleDefinitions(styleDefinitions) {
			// Transform style definitions during style extraction
			return styleDefinitions
		},

		// --- Sync hooks (notification) ---

		rawConfigConfigured(_config) {
			// Read-only access to settled raw config
		},

		preflightUpdated() {
			// React to preflight changes
		},

		atomicStyleAdded(_atomicStyle) {
			// React to new atomic styles
		},

		autocompleteConfigUpdated() {
			// React to autocomplete config changes
		},
	})
}

Source Reference

  • packages/core/src/internal/plugin.tsEnginePlugin interface, defineEnginePlugin, hook execution, plugin sorting
  • packages/core/src/internal/engine.tscreateEngine, hook invocation during engine lifecycle

Next

  • Continue to Create Plugin for a step-by-step guide to building your own plugin