Skip to content

Available Hooks

The lifecycle is split into configuration-time hooks, Engine construction, then source/style transforms and store notifications:

PluginLifecyclerawconfigureRawConfigrawDonerawConfigConfiguredraw->rawDoneresolveResolve EngineConfigrawDone->resolveresolvedconfigureResolvedConfigresolve->resolvedengineConstruct Engineresolved->engineconfiguredconfigureEngineengine->configuredtransformsTransform hooksconfigured->transformseventsStore / preflight eventstransforms->events
PluginLifecyclerawconfigureRawConfigrawDonerawConfigConfiguredraw->rawDoneresolveResolve EngineConfigrawDone->resolveresolvedconfigureResolvedConfigresolve->resolvedengineConstruct Engineresolved->engineconfiguredconfigureEngineengine->configuredtransformsTransform hooksconfigured->transformseventsStore / preflight eventstransforms->events

PikaCSS plugins can implement hooks that run at specific points in the engine lifecycle.

Every hook additionally receives a context object as its last parameter (omitted from the signatures below for brevity): { onDiagnostic, state, host }, where state is the plugin's engine-local state declared via createState — see Per-engine state — and host carries host semantic metadata such as host.projectRoot, the engine's effective project root supplied by the bundler integration. Plugins that load project-relative resources should resolve them against host.projectRoot instead of process.cwd().

configureRawConfig

Signature

ts
configureRawConfig?: (config: EngineConfig) => void | EngineConfig | Promise<void | EngineConfig>

When

Called during createEngine() before the raw config is resolved into its final form. Plugins can mutate the config object in-place or return a new one.

Example

ts
defineEnginePlugin({
  name: 'add-layer',
  configureRawConfig: (config) => {
    config.layers ??= {}
    config.layers['my-layer'] = 5
  },
})

rawConfigConfigured

Signature

ts
rawConfigConfigured?: (config: EngineConfig) => void

When

Called after configureRawConfig has run for all plugins. The raw config is finalized — this is a notification hook for reading the final raw config, not for mutation.

Example

ts
defineEnginePlugin({
  name: 'log-config',
  rawConfigConfigured: (config) => {
    console.log('Final raw config:', config)
  },
})

configureResolvedConfig

Signature

ts
configureResolvedConfig?: (config: ResolvedEngineConfig) => void | ResolvedEngineConfig | Promise<void | ResolvedEngineConfig>

When

Called after the raw config is resolved into a ResolvedEngineConfig. Plugins can adjust resolved values such as prefix or layer configuration before the Engine is constructed.

Example

ts
defineEnginePlugin({
  name: 'override-prefix',
  configureResolvedConfig: (config) => {
    config.prefix = 'custom-'
  },
})

configureEngine

Signature

ts
configureEngine?: (engine: EngineConfigurator<State>) => void | Promise<void>

When

Called once while the Engine is being initialized, after resolved configuration exists. The configurator is owner-bound to the current plugin and exposes:

  • engine.runtime — the underlying Engine for stable runtime APIs such as addPreflight() and initialization-time config dependencies.
  • engine.pika — initialization-only ownership capability for first-level static Pika extensions.
  • engine.typegen — initialization-only Typegen contribution capability.
  • engine.state, engine.host, engine.onDiagnostic — the same engine-local plugin context data used by other hooks.

Config-backed semantic domains do not expose runtime .add() producer services. Add selectors, shortcuts, variables, or keyframes in configureRawConfig, then use configureEngine only for capabilities that require an initialized Engine.

Example

ts
defineEnginePlugin({
  name: 'add-base-styles',
  configureRawConfig(config) {
    config.layers ??= {}
    config.layers.base ??= 0
    config.selectors = {
      definitions: [
        ...(config.selectors?.definitions ?? []),
        { name: '@dark', value: 'html.dark $' },
      ],
    }
  },
  configureEngine(engine) {
    engine.runtime.addPreflight({
      layer: 'base',
      preflight: '*, *::before, *::after { box-sizing: border-box; }',
    })
  },
})

The default layers are preflights (weight 1) and utilities (weight 10); registering base at weight 0 places it before both.

transformSelectors

Signature

ts
transformSelectors?: (selectors: string[]) => string[] | void | Promise<string[] | void>

When

Called when selector strings are being resolved during style extraction. Plugins can rewrite, expand, or filter selector values. Return void to leave the current selector list unchanged.

Example

ts
defineEnginePlugin({
  name: 'dark-mode',
  transformSelectors: (selectors) => {
    return selectors.map(s =>
      s === '@dark' ? 'html.dark $' : s
    )
  },
})

transformStyleItems

Signature

ts
transformStyleItems?: (items: StyleItem[]) => StyleItem[] | void | Promise<StyleItem[] | void>

When

Called when style items are being processed in engine.use(). The signature above uses the base exported StyleItem alias for readability, but the runtime payload is the resolved, augmentation-aware style item list after any PikaAugment.StyleItem extensions are applied. Plugins can inject, remove, or rewrite items before they are extracted into atomic styles. Return void to keep the current items unchanged.

Example

ts
defineEnginePlugin({
  name: 'expand-shortcut',
  transformStyleItems: (items) => {
    return items.flatMap(item =>
      item === 'my-shortcut'
        ? [{ display: 'flex' }, { alignItems: 'center' }]
        : [item]
    )
  },
})

transformStyleDefinitions

Signature

ts
transformStyleDefinitions?: (definitions: StyleDefinition[]) => StyleDefinition[] | void | Promise<StyleDefinition[] | void>

When

Called after style items are converted to style definitions. The signature above uses the base exported StyleDefinition alias for readability, but the runtime payload is the resolved, augmentation-aware definition list after any PikaAugment.StyleDefinition extensions are applied. Plugins can transform definitions before they are extracted into atomic CSS contents. Return void to keep the current definitions unchanged.

Example

ts
defineEnginePlugin({
  name: 'auto-prefix',
  transformStyleDefinitions: (definitions) => {
    return definitions
  },
})

transformStyleContents

Signature

ts
transformStyleContents?: (contents: StyleContent[]) => StyleContent[] | void | Promise<StyleContent[] | void>

When

Called after extraction and normalization, but before any atomic style ID is allocated. Each StyleContent is one normalized atomic entry (selector, property, value). This is the last provisional seam: plugins can rewrite entries 1→1 or expand them 1→N — compatibility lowering, logical-property transforms, custom optimizations, or PikaCSS-level prefixing — without consuming IDs before the transformation succeeds. The engine re-deduplicates and recomputes order sensitivity after the hook runs. Throwing aborts preparation with zero committed engine state. Return void to keep the current contents unchanged.

Example

ts
defineEnginePlugin({
  name: 'user-select-lowering',
  transformStyleContents: (contents) => {
    return contents.flatMap(content =>
      content.property === 'user-select'
        ? [{ ...content, property: '-webkit-user-select' }, content]
        : [content]
    )
  },
})

preflightUpdated

Signature

ts
preflightUpdated?: () => void

When

Called whenever a preflight is added or CSS imports change. Use this hook to react to preflight changes.

Example

ts
defineEnginePlugin({
  name: 'preflight-watcher',
  preflightUpdated: () => {
    console.log('Preflights changed')
  },
})

atomicStyleAdded

Signature

ts
atomicStyleAdded?: (atomicStyle: AtomicStyle) => void

When

Called each time a new atomic style is registered in the engine store. Use this for tracking, analysis, or side effects.

Committed notification, not a mutation seam

When this hook fires, the style is already committed: its ID, cache keys, and store indices are established, so mutating the payload is unsupported. A thrown error is reported as a diagnostic but never rolls back the registration — and later plugins' observers are skipped for that one notification, so observers should not throw. Plugins that need to transform styles must use the provisional hooks (transformStyleItems, transformStyleDefinitions, transformSelectors, transformStyleContents) instead.

Example

ts
defineEnginePlugin({
  name: 'style-tracker',
  atomicStyleAdded: (atomicStyle) => {
    console.log(`New style: ${atomicStyle.id}`)
  },
})

Next