PHPackages                             trk/processwire-htmx - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. trk/processwire-htmx

ActivePw-module[API Development](/categories/api)

trk/processwire-htmx
====================

State-Aware HTMX 2.x Module offering an elegant Request/Response API natively for ProcessWire.

v1.1.8(2mo ago)74MITJavaScriptPHP &gt;=8.1CI passing

Since Mar 30Pushed 2mo ago2 watchersCompare

[ Source](https://github.com/trk/Htmx)[ Packagist](https://packagist.org/packages/trk/processwire-htmx)[ Docs](https://github.com/trk/Htmx)[ Patreon](https://www.patreon.com/ukyo)[ RSS](/packages/trk-processwire-htmx/feed)WikiDiscussions main Synced 3d ago

READMEChangelog (10)DependenciesVersions (19)Used By (0)

ProcessWire HTMX Module
=======================

[](#processwire-htmx-module)

[![ProcessWire](https://camo.githubusercontent.com/34449e2237c61f5c40a0cbf06273d9f22ee4b9ba45442c77b6554c874927b1ea/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f50726f63657373576972652d332e782d6f72616e67652e737667)](https://camo.githubusercontent.com/34449e2237c61f5c40a0cbf06273d9f22ee4b9ba45442c77b6554c874927b1ea/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f50726f63657373576972652d332e782d6f72616e67652e737667)[![HTMX](https://camo.githubusercontent.com/4cd539801e7d76a22830071f7185169d5eadc4b3aaceeea60d5008a0ad2ee5c4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f48544d582d322e782d626c75652e737667)](https://camo.githubusercontent.com/4cd539801e7d76a22830071f7185169d5eadc4b3aaceeea60d5008a0ad2ee5c4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f48544d582d322e782d626c75652e737667)[![Status](https://camo.githubusercontent.com/bca963f487cdc6830ef86660ada348593e0261edbf67440c47cca0cc418d25a3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5374617475732d50726f64756374696f6e25323052656164792d737563636573732e737667)](https://camo.githubusercontent.com/bca963f487cdc6830ef86660ada348593e0261edbf67440c47cca0cc418d25a3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5374617475732d50726f64756374696f6e25323052656164792d737563636573732e737667)[![Security](https://camo.githubusercontent.com/5dbcd384ba719e561d966a2803ac53f60ad22aca066c456057c9f2b8c923b77d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53656375726974792d484d41432d2d5348413235362d637269746963616c2e737667)](https://camo.githubusercontent.com/5dbcd384ba719e561d966a2803ac53f60ad22aca066c456057c9f2b8c923b77d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53656375726974792d484d41432d2d5348413235362d637269746963616c2e737667)

A powerful HTMX integration module designed specifically for ProcessWire. It bridges the gap between client-side reactivity and server-side authority, enabling you to build SPA-like interactions without abandoning traditional PHP rendering.

By unifying **State-Aware Components**, **HMAC-SHA256 Payload Security**, and ProcessWire's native architecture, this module provides the ultimate Developer Experience (DX) inspired by frameworks like Livewire—exclusively for ProcessWire.

---

🏗 Executive Overview
--------------------

[](#-executive-overview)

- **Natively Bundled:** Supplies HTMX 2.x, WebSockets (`ws.js`), Server-Sent Events (`sse.js`), along with key extensions and `_hyperscript`, completely zero-dependency.
- **State-Aware Component Architecture:** Seamless data hydration and dehydration between requests.
- **Object Synthesis:** Magically serialize full ProcessWire objects (like `Page`) down to lightweight secure IDs, restoring them effortlessly on the next request.
- **Cryptographic Security:** End-to-end state manipulation protection with built-in HMAC-SHA256 signatures and TTL-based Replay Protection.
- **Fluent Request/Response API:** Intercept, retarget, flash messages, and trigger custom JS events directly from ProcessWire controllers.

---

⚙️ Installation
---------------

[](#️-installation)

**Via Composer (Recommended):**

```
composer require trk/processwire-htmx
```

**Via Manual Download:**

1. Clone or extract into `site/modules/Htmx/`.
2. In the ProcessWire Admin, log in and navigate to **Modules &gt; Refresh**.
3. Install **HTMX**.

---

🧠 Core Architecture: Component Lifecycle
----------------------------------------

[](#-core-architecture-component-lifecycle)

The true power of this module lies in the `Component` class. It shifts PHP from a "fire-and-forget" mentality to a persistent, stateful application logic container.

 ```
sequenceDiagram
    participant Browser
    participant Htmx as HTMX Module
    participant Component as Component Instance
    participant DB as ProcessWire API

    Browser->>Htmx: Initial Page Load (GET)
    Htmx->>Component: mount() & fill()
    Component->>Browser: Render UI + Encrypted hx__state payload

    Note over Browser, Component: User interacts (e.g. Clicks Button)

    Browser->>Htmx: HX-Request (POST) + hx__state
    Htmx->>Component: hydrate(hx__state)
    Component->>DB: Object Synthesis (Fetch Pages by ID)
    Htmx->>Component: executeAction('like')
    Component->>Browser: Re-render UI + New Encrypted hx__state
```

      Loading ---

🧩 Advanced: State Payload Keys &amp; Multiple Instances
-------------------------------------------------------

[](#-advanced-state-payload-keys--multiple-instances)

By default, state is transported via a signed payload named **`hx__state`** (rendered by `Component::renderStatePayload()`).

If you embed **multiple HTMX components inside the same ProcessWire form**, it is possible to end up with multiple `hx__state` inputs in the POST. In certain contexts, ProcessWire may also represent this value as an array.

This module supports **instance-specific state keys** to avoid collisions:

- Use `Component::setStateKey('hx__state__{targetId}')` during render.
- Ensure your HTMX request uses a matching `hx-target` (so the server can resolve the correct payload).
- The endpoint will prefer `hx__state__{HX-Target}` when present, and fall back to `hx__state`.

### Best Practice Snippet (Multiple Components in One Form)

[](#best-practice-snippet-multiple-components-in-one-form)

```
// Example: render two independent components inside the same ProcessWire form.
// Key idea: make hx-target unique and set an instance-specific state key that matches it.

$targetId = 'blocks-editor-body';

/** @var \Htmx\Component\SomeComponent $cmp */
$cmp = new \Htmx\Component\SomeComponent();
$cmp->setStateKey('hx__state__' . $targetId);

echo "";
echo "";
echo $cmp->renderStatePayload();
echo $cmp; // or $htmx->renderComponent(...)
echo "";
echo "";
```

🌐 Subdirectory Installs &amp; Endpoint URLs
-------------------------------------------

[](#-subdirectory-installs--endpoint-urls)

For ProcessWire installs living in a subdirectory (where `config()->urls->root` is not `/`), `Component::requestUrl()` will automatically prefix the endpoint with the install root for client-side URL correctness (unless you provide an absolute URL).

🛡 Admin Safety: Full-Document Swap Guard
----------------------------------------

[](#-admin-safety-full-document-swap-guard)

In the ProcessWire admin, the module loads `resources/assets/js/pw-htmx-guard.js` to prevent accidental swaps where an HTMX request returns a full HTML document (common causes: wrong endpoint, login redirect, CSRF issues).

- If a full document is detected, the swap is cancelled.
- You can override the alert message by defining `window.__pwHtmxGuardMessage` before the swap occurs.

🐞 Debugging with TracyDebugger (Debug Mode Only)
------------------------------------------------

[](#-debugging-with-tracydebugger-debug-mode-only)

When **ProcessWire debug mode** is enabled and the **TracyDebugger** module is installed, Htmx can provide extra debugging support (enabled by default via the module config option `tracySupport`):

- Adds a **Tracy bar panel** named **HTMX** with request/endpoint details.
- Emits **debug response headers** on HTMX requests (useful in the browser Network tab):
    - `X-PW-HTMX`
    - `X-PW-HTMX-ReqId`
    - `X-PW-HTMX-Component`
    - `X-PW-HTMX-Action`
    - `X-PW-HTMX-Target`
    - `X-PW-HTMX-StateKey`
    - `X-PW-HTMX-OOB`
    - `X-PW-HTMX-Error-Code` (stable error identifiers)
    - `X-PW-HTMX-Exception` (exception class, no message redaction)

### Error Codes (`X-PW-HTMX-Error-Code`)

[](#error-codes-x-pw-htmx-error-code)

These values are designed to be stable and safe to expose in headers (no sensitive payload data):

- `not_post` — endpoint called with a non-POST request
- `missing_state` — missing `hx__state` (or `hx__state__{target}`) payload in POST
- `malformed_state` — state payload did not match the expected `{base64}|{hmac}` format
- `bad_hmac` — HMAC signature did not match (tampering or wrong salt)
- `invalid_state` — decoded state structure missing `__cmp` or invalid payload structure
- `invalid_component` — resolved component class missing or not a valid `Totoglu\Htmx\Component`
- `exception` — unhandled exception during hydrate/action/render

### Client Debug Toggle (Admin / Debug Only)

[](#client-debug-toggle-admin--debug-only)

In admin pages, when `config->debug` is enabled, the module loads `pw-htmx-debug.js`. It is a no-op unless you explicitly enable it:

```
window.__pwHtmxDebug = true;
```

This integration is **never active in production** unless `config->debug` is enabled.

🪄 Zero-Configuration Auto-Discovery
-----------------------------------

[](#-zero-configuration-auto-discovery)

To make the developer experience as seamless as possible, the HTMX module includes automatic namespace registration for your components and UI elements.

### Site-Level Discovery

[](#site-level-discovery)

By default, if the following directories exist in your ProcessWire installation, they are automatically registered with the native `ClassLoader`:

- **`site/components/`** maps to the **`Htmx\Component`** namespace.
- **`site/ui/`** maps to the **`Htmx\Ui`** namespace.

*Note: These directory paths are fully customizable (e.g. `my-components/`) via the Module Configuration screen.*

This means you can place your component class files directly in `site/components/` and simply declare the `namespace Htmx\Component;`, and the module will instantly know how to load and render them during stateless POST requests without requiring custom auto-loaders or `require_once` statements.

### Module-Level Discovery (v1.1.4+)

[](#module-level-discovery-v114)

The HTMX module also automatically discovers components bundled inside **other ProcessWire modules** — no `autoload => true` or manual `registerComponent()` calls required.

**How it works:**

1. When the Htmx module initializes, it scans all installed modules that declare `'requires' => ['Htmx']` in their module info.
2. For each qualifying module, it checks for `components/` and/or `ui/` directories in the module root.
3. If found, those directories are registered with ProcessWire's `ClassLoader` under `Htmx\Component` and/or `Htmx\Ui`.

**Result:** During HTMX AJAX requests to `/hx/req`, the endpoint can resolve and instantiate component classes from any Htmx-dependent module — even if that module is not autoloaded.

The discovery results are cached in `WireCache` for performance. The cache is automatically invalidated when modules are refreshed via the admin UI.

By default, the module securely sandboxes all component rendering strictly within these explicitly defined directories and the native `templates/` folder. This is configurable via the **Allow Component Paths** module setting, balancing optimal Developer Experience (DX) with security.

---

🚀 Quick Start: Building a Stateful Component
--------------------------------------------

[](#-quick-start-building-a-stateful-component)

Let's build a Livewire-style "Like" button that remembers its state, seamlessly tied to a specific ProcessWire Page. We'll utilize the auto-discovery feature by placing our component in `site/components/LikeButton.php`.

### 1. Create Your Component (`site/components/LikeButton.php`)

[](#1-create-your-component-sitecomponentslikebuttonphp)

```
namespace Htmx\Component;

use Totoglu\Htmx\Component;

class LikeButton extends Component {

    // ProcessWire Objects are automatically synthesized!
    public Page $post;

    // Public properties are securely preserved across requests
    public int $likes = 0;

    /**
     * Optional: Hook executed upon initialization
     */
    public function mount() {
        if ($this->likes === 0) {
            $this->likes = $this->post->num_likes ?? 0;
        }
    }

    /**
     * Action triggered from the frontend.
     * Dependencies (like $session) are Auto-Injected!
     */
    public function like(int $step = 1, Session $session) {
        $this->likes += $step;

        // Let's also save to the DB...
        $this->post->of(false);
        $this->post->num_likes = $this->likes;
        $this->post->save('num_likes');

        $session->message("You liked {$this->post->title}!");
    }
}
```

### 2. The Component View (`components/like-button.php`)

[](#2-the-component-view-componentslike-buttonphp)

Build the markup. You have full access to `$this` (the component context).

```
