Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Htmx Laravel Package

mauricius/laravel-htmx

mauricius/laravel-htmx integrates HTMX with Laravel through Blade directives, HTMX-aware middleware, response helpers, and validation support. It helps detect HTMX requests, return partial responses, and build fast, dynamic, server-rendered interfaces with minimal custom JavaScript.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require mauricius/laravel-htmx
php artisan vendor:publish --tag=htmx-config   # config/htmx.php

The service provider and Htmx facade are auto-discovered on Laravel 8+. v0.10.0 only widens the framework constraint to Laravel 13.x — there are no API changes from v0.9.x, so an in-place composer update is safe.

First place to look

  1. config/htmx.php — controls the script version/CDN used by the Blade directive and any middleware wiring. Publish it first so you're not chasing defaults.
  2. The Htmx facade source (src/Htmx.php) — it's a thin wrapper over HTMX's request/response headers, and reading it once tells you everything the package can and can't do.

Include htmx in your layout

<head>
    @htmxScripts
</head>

<body hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'>

First real use case — a live search

<input type="search"
       name="q"
       hx-get="{{ route('search') }}"
       hx-target="#results"
       hx-trigger="keyup changed delay:300ms"
       hx-indicator="#spinner">

<div id="results">@include('partials.results', ['users' => []])</div>
public function search(Request $request)
{
    $users = User::search($request->q)->limit(10)->get();

    // Only the fragment is needed for HTMX; full page for direct visits.
    return Htmx::isHtmxRequest()
        ? view('partials.results', compact('users'))
        : view('search.index', compact('users'));
}

That single branch — full page vs. fragment — is the pattern you'll repeat everywhere. Everything else in the package is about the response headers you attach when the fragment alone isn't enough.


Implementation Patterns

Always branch on Htmx::isHtmxRequest() in the controller. Don't infer it from $request->ajax() (HTMX sets HX-Request, not X-Requested-With) and don't put the branching in Blade — it's a response-shape decision, and keeping it in the controller keeps your partials pure and cacheable.

Extract fragments into resources/views/partials/ and have the HTMX branch render only those. A shared layout plus a fragment view means one source of truth; duplicating markup for full-page and HTMX responses is the single most common way this package gets misused.

Response helpers cover the cases where the fragment isn't enough:

// Full browser navigation from an HTMX request (URL bar updates, page reloads)
return Htmx::redirect(route('users.index'));

// Same, but without a reload — pushes the new URL and issues a follow-up GET
return Htmx::location(route('users.index'));

// Force a hard refresh of the current page
return Htmx::refresh();

// Fire a client-side event; optional detail payload
return Htmx::trigger('user-saved', ['id' => $user->id])
    ->with('status', 'Saved');   // chain back into a normal response

// Override swap / target for this one response
return Htmx::reswap('none')->retarget('#flash');

trigger/reswap/retarget are the escape hatches for "the fragment is right but the swap strategy is wrong" — e.g. a 409 conflict where you want to keep the form and just flash a message via reswap('none').

Out-of-band swaps for multi-region updates. When one action must update several places (e.g. save a row and bump a counter in the header), return the main fragment plus OOB fragments:

{{-- partials/user-row.blade.php --}}
<tr id="user-{{ $user->id }}">...</tr>

<div id="user-count" hx-swap-oob="true">{{ $count }}</div>

This is plain HTMX, not package API, but it's the pattern that removes most of the "I need a full page reload" temptation.

Validation responses. HTMX does not swap on non-2xx by default, so a 422 from $request->validate() will silently no-op. Two clean options:

  • Return a 200 with the form partial + errors and let the fragment re-render.
  • Keep the 422 and add hx-target/hx-swap overrides on the form, or listen for htmx:responseError in JS.

Middleware. If you use Htmx::redirect()/location() anywhere, apply the package's middleware to the relevant group — it's what converts those calls into HX-Redirect/HX-Location headers on the response. Without it, you get a plain 302 and HTMX will follow it transparently inside the XHR, leaving the browser URL stale.

Boost-aware navigation. Htmx::isBoosted() distinguishes boosted full-page navigations from partial requests. If you add hx-boost to your nav, branch on it so boosted requests still return the full layout while genuine fragments don't.


Gotchas and Tips

Redirects don't do what you think. A 302 from a controller is followed silently by the XHR — the URL bar never changes and the user's history is wrong. Use Htmx::redirect() (browser navigation) or Htmx::location() (no reload) for any redirect triggered from an HTMX request. This is the #1 bug people hit.

CSRF tokens must be re-sent. Session-token rotation (login, regenerate()) invalidates the token baked into the page. Put hx-headers on <body> rather than per-form so a single meta refresh fixes everything, and add a htmx:configRequest listener if you rotate tokens mid-page.

HX-Request is case-sensitive in some proxies and stripped by others. If isHtmxRequest() returns false in production but true locally, check for a CDN/WAF or a middleware that whitelists headers. Same for HX-Boosted and HX-History-Restore-Request.

Non-2xx means no swap. Covered above, but it also bites abort(403) and policy failures. Either return a rendered fragment with an error state, or catch it client-side.

Script version is config-driven. @htmxScripts pulls the version from config/htmx.php. If you self-host or use a CSP with a strict script-src, publish the config and point it at your own asset rather than fighting the directive.

Config caching. After bumping config/htmx.php, php artisan config:cache — the usual Laravel trap, but it bites here because the failure mode is "the script tag renders a stale version."

Debugging toolkit. In the browser console: htmx.logAll() shows every request/swap decision; document.body.addEventListener('htmx:responseError', ...) catches the silent failures. On the server side, Htmx::isHtmxRequest() in a dd() is usually enough to confirm the header path is intact before you start doubting your fragment.

Extension points are deliberately small. There are no custom Blade components or macro registration — the package is a thin header wrapper. When you need more, you're writing plain HTMX attributes and hx-* config, not extending the package. Plan for that: don't expect to swizzle its behaviour, and don't wrap the facade in yet another abstraction layer.

Upgrade note for v0.10.0. The release is Laravel 13 support plus maintenance housekeeping — no deprecations, no removed methods, no config key changes. It's a drop-in for v0.9.x. The only thing to watch is that laravel/framework ^13.0 in your own composer.json will now resolve; pin to ^0.9|^0.10 if you're rolling out framework upgrades separately from package upgrades.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo