PHPackages                             preflow/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. [Utility &amp; Helpers](/categories/utility)
4. /
5. preflow/htmx

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

preflow/htmx
============

Preflow HTMX — hypermedia driver, component tokens, component endpoint

v0.13.1(1mo ago)0442MITPHPPHP &gt;=8.4

Since Apr 10Pushed 1mo agoCompare

[ Source](https://github.com/getpreflow/htmx)[ Packagist](https://packagist.org/packages/preflow/htmx)[ RSS](/packages/preflow-htmx/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (3)Versions (32)Used By (2)

preflow/htmx
============

[](#preflowhtmx)

Hypermedia abstraction and component endpoint for Preflow. Ships an HTMX driver out of the box; the `HypermediaDriver` interface allows swapping in alternatives (e.g. Datastar).

Installation
------------

[](#installation)

```
composer require preflow/htmx
```

Requires PHP 8.4+, `ext-sodium`. Twig integration requires `twig/twig ^3.0`.

What it does
------------

[](#what-it-does)

`ComponentToken` issues HMAC-SHA256-signed tokens that encode a component class, props, and action name. `ComponentEndpoint` handles incoming hypermedia requests through five security layers: token verification → class validation → action whitelist → `Guarded` interface → dispatch. `HtmxDriver` generates `hx-*` attributes and sets response headers. The `hd` Twig global surfaces all of this as template helpers.

API
---

[](#api)

### `HtmxDriver`

[](#htmxdriver)

Generates HTML attributes and sets response headers.

```
// Attributes
$driver->actionAttrs(method: 'post', url: $url, targetId: $id, swap: SwapStrategy::OuterHTML): HtmlAttributes
$driver->listenAttrs(event: 'itemSaved', url: $url, targetId: $id): HtmlAttributes

// Response headers (call from within an action)
$driver->triggerEvent('itemSaved')    // HX-Trigger
$driver->redirect('/dashboard')      // HX-Redirect
$driver->pushUrl('/posts/1')         // HX-Push-Url
```

### `ComponentToken`

[](#componenttoken)

```
$token->encode(componentClass: Post::class, props: ['id' => 1], action: 'save'): string
$token->decode(tokenString: $str, maxAge: 86400): TokenPayload
```

Tokens are URL-safe base64 strings. `maxAge` (seconds) enforces expiry.

### `ComponentEndpoint`

[](#componentendpoint)

Universal PSR-7 handler. Mount it at e.g. `/--component/action` and `/--component/render`.

```
$endpoint->handle(ServerRequestInterface $request): ResponseInterface
```

Security layers applied on every request:

1. Token present and base64-decodable
2. HMAC-SHA256 signature valid, optional max-age enforced
3. `componentClass` is a real subclass of `Component`
4. `action` is in `$component->actions()` (or `'render'`)
5. If component implements `Guarded`, `authorize(action, request)` is called

### `Guarded` interface

[](#guarded-interface)

Add component-level authorization without middleware.

```
interface Guarded
{
    public function authorize(string $action, ServerRequestInterface $request): void;
}
```

Throw `ForbiddenHttpException` to deny access.

### `SwapStrategy` enum

[](#swapstrategy-enum)

`OuterHTML`, `InnerHTML`, `BeforeBegin`, `AfterBegin`, `BeforeEnd`, `AfterEnd`, `Delete`, `None`

### Twig `hd` global (`HdExtension`)

[](#twig-hd-global-hdextension)

Available as `hd` in all templates once the extension is registered.

```
{# POST action — renders hx-post, hx-target, hx-swap attributes #}
+1

{# GET action #}

{# Listen for a server-sent event and re-render #}

{# HTMX script tag #}
{{ hd.assetTag() }}
```

Usage
-----

[](#usage)

**Component with an action:**

```
use Preflow\Components\Component;

final class Counter extends Component
{
    public int $count = 0;

    public function resolveState(): void
    {
        $this->count = (int) ($_SESSION['count'] ?? 0);
    }

    public function actions(): array
    {
        return ['increment'];
    }

    public function actionIncrement(array $params): void
    {
        $this->count++;
        $_SESSION['count'] = $this->count;
    }
}
```

**Template** (`Counter.twig`):

```
Count: {{ count }}
+1
```

**Guarded component:**

```
use Preflow\Htmx\Guarded;
use Preflow\Core\Exceptions\ForbiddenHttpException;
use Psr\Http\Message\ServerRequestInterface;

final class AdminPanel extends Component implements Guarded
{
    public function actions(): array
    {
        return ['deleteItem'];
    }

    public function authorize(string $action, ServerRequestInterface $request): void
    {
        $user = $request->getAttribute('user');
        if (!$user?->isAdmin()) {
            throw new ForbiddenHttpException('Admins only.');
        }
    }

    public function actionDeleteItem(array $params): void
    {
        // ...
    }
}
```

**Wire up the endpoint:**

```
use Preflow\Htmx\{ComponentEndpoint, ComponentToken, HtmxDriver, ResponseHeaders};

$token    = new ComponentToken(secretKey: $_ENV['APP_KEY']);
$headers  = new ResponseHeaders();
$driver   = new HtmxDriver($headers);
$endpoint = new ComponentEndpoint(
    token: $token,
    renderer: $renderer,
    driver: $driver,
    componentFactory: fn (string $class, array $props) => new $class(),
);

// Route POST /--component/action and GET /--component/render to:
$response = $endpoint->handle($request);
```

**Add asset tag in your base layout:**

```
{{ hd.assetTag() }}
```

### Asset injection in fragment responses

[](#asset-injection-in-fragment-responses)

`ComponentEndpoint` automatically appends any CSS and JS collected during component rendering to HTMX fragment responses. Styles and scripts defined inside a component with `{% apply css %}` / `{% apply js %}` are included even when the response is a partial swap — no separate asset pipeline step needed.

### Fragment rendering

[](#fragment-rendering)

When the `HX-Target` header does not match the component's own ID, `ComponentEndpoint` returns a fragment response via `renderFragment()` / `renderResolvedFragment()` rather than the full wrapped component. This lets a single component handle both full-page renders and targeted partial swaps.

`ComponentRenderer::renderResolvedFragment()` skips `resolveState()` and renders inner HTML only — used after an action has already mutated state.

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance89

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

31

Last Release

53d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6d8b54efbc79683c32645e3fa8d3590037fa003963a16e0ed989104c6f4a2723?d=identicon)[smyr](/maintainers/smyr)

---

Top Contributors

[![smeyerme](https://avatars.githubusercontent.com/u/1925560?v=4)](https://github.com/smeyerme "smeyerme (14 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/preflow-htmx/health.svg)

```
[![Health](https://phpackages.com/badges/preflow-htmx/health.svg)](https://phpackages.com/packages/preflow-htmx)
```

###  Alternatives

[danielme85/laravel-cconverter

Laravel 5 plug-in for currency conversion

42102.4k](/packages/danielme85-laravel-cconverter)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
