ForgeHtmx

A small tool your app uses to bring htmx into its server-rendered pages — load the client, keep CSRF working, and hand your controllers helpers for responding with HTML fragments.

Overview

htmx is an HTML-attribute approach to AJAX: you decorate your markup with attributes like hx-get and hx-target, and the htmx client turns those into requests that swap fragments of the page — no JavaScript framework required. ForgeHtmx is the piece that makes that fit naturally into your app.

It lives as a capability in the capabilities/ directory (the reference layout for reusable building blocks), and is scoped strictly to wiring: inject the client, keep CSRF headers flowing, and give your controllers concise helpers for returning partial HTML and the htmx response headers your interactions need.

What it gives you

  • The htmx client, injected for you
  • Automatic CSRF token wiring
  • Partial-view rendering
  • Response helpers for htmx headers
  • hx-request detection

Prerequisites: ForgeHtmx builds on the routing and view layers of your app, so both need to be present. The page source is in capabilities/ForgeHtmx.

Integration, Not a Rewrite

ForgeHtmx doesn't reimplement htmx. The attribute language itself — every hx-* in your markup and the DOM swaps on the page — is htmx's own client, loaded verbatim. ForgeHtmx only handles the server-side fit:

  • It injects htmx.min.js into your pages so the attributes work out of the box.
  • It notices when a request is an htmx partial (via the HX-Request header), so your app can respond with just the fragment.
  • It keeps CSRF working: every htmx request carries your token automatically.

You write your markup the htmx way, and your controllers the way your app already works — the capability is a thin, predictable seam between the two.

Installation

php forge.php package:install-module --module=ForgeHtmx

Installing links the client asset so htmx.min.js is served to your pages, and registers the request middleware. From there the client is injected and the helpers are available in your controllers.

htmx in Your Views

Once installed, decorate any element with htmx attributes and the client does the rest — a button or link that loads a fragment, a form posted in the background, or a region that refreshes on an interval:

<!-- click the button, swap a comment list into the target, off the request path -->
<button hx-get="/comments" hx-target="#comments" hx-swap="innerHTML">
    Load comments
</button>
<div id="comments"></div>

<!-- submit a form over AJAX and refresh the page if it succeeds -->
<form hx-post="/newsletter" hx-swap="none" hx-on::after-request="location.reload()">
    <input name="email" type="email" />
    <button type="submit">Subscribe</button>
</form>

The attribute set is the standard htmx one — hx-get, hx-post, hx-target, hx-swap, hx-trigger, hx-push-url, and friends. ForgeHtmx is what makes those requests reach your controllers and return clean fragments.

Response Helpers

In a controller, compose the HtmxResponseHelper trait to return the response headers htmx understands. Instead of hand-writing headers, use a method that says what you mean:

use Capability\ForgeHtmx\Traits\HtmxResponseHelper;

final class CommentController
{
    use HtmxResponseHelper;

    public function index(): mixed
    {
        // a plain HTML fragment to swap into the page
        return $this->htmxFragment($this->view('partials/comments', $data));
    }

    public function store(): mixed
    {
        // after a form post, send the browser to a new URL
        return $this->htmxRedirect('/comments');
    }

    public function refresh(): mixed
    {
        // have htmx reload the current page
        return $this->htmxRefresh();
    }
}

The fuller set covers everything htmx can act on from response headers:

  • htmxTrigger(), htmxTriggerAfterSwap(), htmxTriggerAfterSettle() — emit client-side events (with optional detail).
  • htmxLocation() — issue a client-side navigation with context.
  • htmxPushUrl() / htmxReplaceUrl() — update the browser history.
  • htmxRetarget() / htmxReswap() — override where and how the swap happens.
  • htmxStopPolling() — tell a polling region to stop.

Rendering Partials

A common pattern is one endpoint that serves both a full page and the htmx fragment for it. The HtmxViewHelper handles that in one call — full view with layout for normal requests, a focused partial without the layout when htmx asks:

use Capability\ForgeHtmx\Traits\HtmxViewHelper;

final class DashboardController
{
    use HtmxViewHelper;

    public function show()
    {
        $data = ['metrics' => $this->metrics()];

        // full page normally; just the 'partials/dashboard' view on htmx
        return $this->htmxView('pages/dashboard', $data, 'partials/dashboard');
    }
}

When the request carries HX-Request, the layout is suppressed and only the partial view is returned; otherwise your full page renders as usual.

CSRF & Assets

htmx-driven requests would otherwise omit the CSRF protection your app expects. ForgeHtmx handles this automatically: it injects a small script that reads your CSRF token from the page and attaches it as an X-CSRF-TOKEN header on every htmx request, so your existing verification keeps working with no extra ceremony.

Both the client script and this CSRF wiring are injected only into real HTML pages — htmx partial responses and JSON responses pass through clean, without the surrounding page chrome.