Writing Plugins

The package’s engine export re-exposes the vendored Datastar plugin API — attribute(), action(), effect(), signal() and friends. A custom attribute plugin looks like this:

import { attribute, effect } from '@wrux/astro-datastar/engine';

attribute({
  name: 'my-plugin', // matched as data-my-plugin
  returnsValue: true, // the attribute value is an expression
  apply({ el, rx }) {
    // rx() evaluates the attribute expression; effect() re-runs when
    // signals the expression reads change.
    return effect(() => {
      const value = rx();
      // …update el…
    });
  },
});

Register plugins by importing their modules from your app-local entrypoint (see Integration Options). This site’s own plugins are working references:

  • collapse — an effect()-driven animation.
  • cloak — a boot-time attribute.
  • combobox — a larger behavioural plugin with keyboard navigation and live DOM lookups.

The package’s own replace-url plugin is the smallest full example:

packages/astro-datastar/src/plugins/replace-url.ts
import { attribute, effect } from '../engine';

/**
 * `data-replace-url="<expression>"` keeps the address bar in sync with
 * signal state: whenever signals the expression reads change, the current
 * history entry is replaced with the evaluated URL (relative or absolute,
 * same-origin only).
 *
 * ```html
 * <div data-replace-url="'/businesses' + ($q ? '?q=' + $q : '')"></div>
 * ```
 *
 * Upstream Datastar 1.0 moved its ReplaceUrl plugin to the paid Pro
 * bundle; this is our own minimal equivalent with the same attribute name.
 */
attribute({
  name: 'replace-url',
  requirement: { key: 'denied', value: 'must' },
  returnsValue: true,
  apply({ rx }) {
    return effect(() => {
      const url = rx();
      if (typeof url !== 'string' || url === '') return;
      const resolved = new URL(url, window.location.origin);
      if (resolved.origin !== window.location.origin) return;
      history.replaceState(
        history.state,
        '',
        resolved.pathname + resolved.search + resolved.hash,
      );
    });
  },
});