seaworn/symfony-htmx-bundle

Htmx integration for Symfony

Maintainers

Package info

github.com/seaworn/symfony-htmx

Type:symfony-bundle

pkg:composer/seaworn/symfony-htmx-bundle

Statistics

Installs: 14

Dependents: 0

Suggesters: 0

Stars: 7

Open Issues: 0

dev-main 2026-03-26 05:28 UTC

This package is auto-updated.

Last update: 2026-03-26 05:28:42 UTC


README

Htmx integration for Symfony

  • Symfony >=6.0
  • PHP >=8.0

Install

$ composer require seaworn/symfony-htmx-bundle

Setup

Add the following snippet to the front controller. This will allow Symfony to use a custom Request class

/* public/index.php */

\Symfony\Component\HttpFoundation\Request::setFactory(function (...$args) {
    return new \Seaworn\HtmxBundle\Request\HtmxRequest(...$args);
});

Usage

Extend Seaworn\HtmxBundle\Controller\AbstractController (extends \Symfony\Bundle\FrameworkBundle\Controller\AbstractController)

use Seaworn\HtmxBundle\Request\HtmxRequest;
use Seaworn\HtmxBundle\Response\HtmxResponse;

class HelloController extends \Seaworn\HtmxBundle\Controller\AbstractController{}

or use Seaworn\HtmxBundle\Controller\HtmxControllerTrait

use Seaworn\HtmxBundle\Request\HtmxRequest;
use Seaworn\HtmxBundle\Response\HtmxResponse;

class HelloController extends \Symfony\Bundle\FrameworkBundle\Controller\AbstractController
{
    use \Seaworn\HtmxBundle\Controller\HtmxControllerTrait;
}

Request headers

public function index(HtmxRequest $request): HtmxResponse
{
    // whether the request is made via htmx
    $request->isHxRequest();

    // whether the request is via an element using hx-boost attribute
    $request->isHxBoosted();
    
    // the current browser url
    $request->getHxCurrentUrl();
    
    // whether request is for history restoration after a miss in the local history cache
    $request->isHxHistoryRestoreRequest();
    
    // the user response to an hx-prompt
    $request->getHxPrompt();
    
    // the id of the target element if it exists
    $request->getHxTarget();

    // the id of the triggered element if it exists
    $request->getHxTrigger();
    
    // the name of the triggered element if it exists
    $request->getHxTriggerName();
    
    // ...
}

Response headers

public function index(HtmxRequest $request): HtmxResponse
{
    // ...
    $response = $this->hxRender('index.html.twig'); // or new HtmxResponse()
    // Optional headers
    $response->setHxLocation(
        "/location",
        [
            'source' => '',
            'event' => '',
            'handler' => '',
            'target' => '',
            'swap' => '',
            'select' => '',
            'values' => [],
            'headers' => [],
        ]) // set HX-Location header
        ->setHxPushUrl('/push') // set HX-Push-Url header
        ->setHxReplaceUrl('/replace') // set HX-Replace-Url header
        ->setHxReswap('outerHTML') // set HX-Reswap header
        ->setHxRetarget('#target') // set HX-Retarget header
        ->setHxReselect('#select') // set HX-Reselect header
        ->setHxTrigger('event') // set HX-Trigger header (simple)
        ->setHxTriggerAfterSwap('event1,event2') // set HX-Trigger-After-Swap header (multiple)
        ->setHxTriggerAfterSettle(['event' => ['key' => 'value']]); // set HX-Trigger-After-Settle header (with detail)
    return $response;
}

Render template block

<!-- index.html.twig -->

{% extends 'base.html.twig' %}

{% block block1 %}
    <div id="block1"> 
        Sample content...
    </div>
{% endblock %}
public function index(HtmxRequest $request): HtmxResponse
{
    return $this->hxRenderBlock('index.html.twig', 'block1');
}

Redirect

public function index(HtmxRequest $request): HtmxResponse
{
    return $this->hxRedirect('https://htmx.org/');
}

Refresh

public function index(HtmxRequest $request): HtmxResponse
{
    return $this->hxRefresh();
}

Stop polling

<!-- index.html.twig -->

{% extends 'base.html.twig' %}

{% block content %}
    <div hx-get="/poll" hx-vals="js:{pollingIndex}" hx-trigger="every 2s"></div>
    <script type="text/javascript">
        var pollingIndex = 0;
        document.body.addEventListener("polling", function(e) {
            pollingIndex++;
            console.log("Polling Index:", pollingIndex);
        });
    </script>
{% endblock %}
public function poll(HtmxRequest $request): HtmxResponse
{ 
    $index = $request->get('pollingIndex', 0);
    if ((int)$index >= 10) {
        return new HtmxStopPollingResponse();
    }
    return (new HtmxResponse())->setHxTrigger('polling');
}