A generic contao filter.

1.23.7 2023-01-27 09:50 UTC

This package is auto-updated.

Last update: 2023-01-27 09:50:13 UTC


Build Status Coverage Status

This bundle offers a generic filter module to use with arbitrary contao entities containing standard filter with initial filters and filter form types including symfony form type representations.


  • codefog/tags-bundle integration
  • heimrichhannot/contao-categories-bundle integration
  • Form handling using symfony form component
  • Form rendering by using symfony form templates (currently available: bootstrap 3, bootstrap 4, foundation, div, table)
  • Numerous symfony form types supported (see: http://symfony.com/doc/3.4/reference/forms/types.html)
  • Highly customizable and detached from tl_module table
  • Label/Message handling using symfony translations
  • Render form always empty (without user selection)
  • Merge data over multiple filter forms with same form name
  • Default Values (can be overwritten by user)
  • Initial Values (can`t be overwritten by user)
  • Stores filter data in session (no GET parameter URL remnant)
  • Content element "Filter-Preselect" with optional redirect functionality to preselect filter on given page
  • Content element "Filter-Hyperlink" with filter preselect feature



  1. Install with composer or contao manager

    composer require heimrichhannot/contao-filter-bundle
  2. Update database

We recommend to use this bundle toghether with List Bundle and Reader Bundle.


  1. Create a filter configuration within System -> Filter & sort configuration
  2. Add filter elements to the filter config.
  3. If you want to show the filter somewhere (for example to filter a list), create a filter/sort frontend module.

Wrapper elements (DateRange, ProximitySearch, ...)

The Wrapper element has to be places before the fields associated with them. For example the date_range wrapper element needs to be placed before the two associated date fields.


Filter Bundle Forms are not typical GET-Forms, so it is not possible to simple copy the filter urls to share or bookmark a filtered list. To overcome this limitation, preselect urls can be generated. Preselect urls for the current filter can be found within template variabled, you can create a preselect content element or get the url programmatically from the FilterConfig.

Template variables

If a filter is set, the variable preselectUrl contains the preselection url for the current filter. It's available in the filter templates and the frontend module template.

You can for example create a copy preselect url button:

{% if preselectUrl is defined and preselectUrl is not empty %}
   <div class="col-xd-12 col-md-3">
   <a class="btn btn-primary" onclick="navigator.clipboard.writeText('{{ preselectUrl }}');alert('Copied preselect link!');return false;">Filtervorauswahllink kopieren</a>
{% endif %}

Content element

You can use one of the following content elements:

  • "Filter-Preselect" with optional redirect functionality to preselect filter on given page
  • "Filter-Hyperlink" with filter preselect feature


You can generate the preselect link from the FilterConfig instance

use HeimrichHannot\FilterBundle\Manager\FilterManager;

class CustomController {
   private FilterManager $filterManager;
   public function invoke(): string
       $filterConfig = $this->filterManager->findById($this->objModel->filter);
       return !empty($filterConfig->getData()) ? $filterConfig->getPreselectAction($filterConfig->getData(), true) : ''


Insert tag Arguments Description
{{filter_reset_url::*::*}} filter ID :: page ID or alias This tag will be replaced with a reset filter link to an internal page with (replace 1st * with the filter ID, replace 2nd * with the page ID or alias)

Templates (filter)

There are two ways to define your templates.

1. By Prefix

The first one is to simply deploy twig templates inside any templates or bundles views directory with the following prefixes:

** filter template prefixes**

  • filter_

More prefixes can be defined, see 2nd way.

2. By config.yml

The second on is to extend the config.yml and define a strict template:



class Plugin implements BundlePluginInterface, ExtensionPluginInterface
     * {@inheritdoc}
    public function getBundles(ParserInterface $parser)

     * {@inheritdoc}
    public function getExtensionConfig($extensionName, array $extensionConfigs, ContainerBuilder $container)
        return ContainerUtil::mergeConfigFile(
            __DIR__ .'/../Resources/config/config.yml'


      - {name: form_div_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_div_layout.html.twig'}
      - {name: form_table_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_table_layout.html.twig'}
      - {name: bootstrap_3_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_bootstrap_3_layout.html.twig'}
      - {name: bootstrap_3_horizontal_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_bootstrap_3_horizontal_layout.html.twig'}
      - {name: bootstrap_4_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_bootstrap_4_layout.html.twig'}
      - {name: bootstrap_4_horizontal_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_bootstrap_4_horizontal_layout.html.twig'}
      - {name: foundation_5_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_foundation_5_layout.html.twig'}
      - filter_



Event Event ID Description
Adjust filter options huh.filter.event.adjust_filter_options_event
Adjust filter value huh.filter.event.adjust_filter_value_event
FilterConfigInitEvent FilterConfigInitEvent::class Modify config on FilterConfig initialization.
FilterBeforeRenderFilterFormEvent FilterBeforeRenderFilterFormEvent:class Modify the filter form template context before rendering
FilterFormAdjustOptionsEvent FilterFormAdjustOptionsEvent::class Modify form options before building the form.
FilterQueryBuilderComposeEvent FilterQueryBuilderComposeEvent::class Description provided below.
ModifyJsonResponseEvent huh.filter.event.modify_json_response_event Modify the JSON response of async form submits.


In this event you can modify the data before the query for the current element is created and added to the QueryBuilder. It is also possible to add a query within in the event and skip the subsequent query creating.

function __invoke(FilterQueryBuilderComposeEvent $event): void
    // modify values before creating the query
    if ($event->getName() === 'my_field') {
         if ("special_value" === $event->getValue()) {
     // create a custom query and skip the default query creation pars.
     if ($event->getName() === 'totally_custom_field') {
        // do some magic
        $event->getQueryBuilder()->andWhere('custom_table_field REGEXP '.$magicValue);

Bootstrap 4 form snippets

The following bootstrap 4 form theme snippets can be used to generate uncommon, but existing bootstrap 4 form widgets within your custom filter_form_bootstrap4*.html.twig template.

Radio buttons

Replace categories with the name of your custom field. Remove onchange handler if not required. Select fallback can be used on small devices, if too many options, display/hide, using @media breakpoints.

{% if(form.categories is defined) %}
    <div class="disable-hidden-inputs {{ ('form-group' ~ ' ' ~ form.categories.vars.id ~ ' ' ~ form.categories.vars.name ~ ' ' ~ form.categories.vars.attr.class)|trim }}">
        {{ form_label(form.categories) }}
        {% do form.categories.setRendered %}
        <div class="select-fallback">
            <label class="form-control-label" for="{{ form.categories.vars.id }}-select">{{ form.categories.vars.label|trans }}</label>
            <select id="{{ form.categories.vars.id }}-select" onchange="this.form.submit();" name="{{ form.categories.vars.full_name }}" class="form-control">
                {% for key,choice in form.categories.vars.choices %}
                    <option data-icon="category-{{ choice.value }}" value="{{ choice.value }}"{{ (choice.value == form.categories.vars.value ? ' selected' : '') }}>{{ choice.label }}</option>
                {% endfor %}
        <div class="btn-group btn-group-toggle" data-toggle="buttons">
            {% for key,choice in form.categories.vars.choices %}
                <label class="{{ ('btn btn-link' ~ (choice.value == form.categories.vars.value ? ' active' : ''))|trim }}">
                    <input type="radio" id="{{ form.categories.vars.id }}_{{ key }}" {% if choice.value == form.categories.vars.value %}checked{% endif %}
                           autocomplete="off" onchange="this.form.submit();" name="{{ form.categories.vars.full_name }}" value="{{ choice.value }}">
                    {{ choice.label }}
            {% endfor %}
{% endif %}

You must also set the non visible inputs to disabled in order to prevent them from being submitted and overwrite selected value due to same input name. The following script can be used to achieve this behavior.

(function($) {
    function disableHiddenInputs () {
        var $selector = $('.disable-hidden-inputs');
        $selector.find(':hidden').children(':input').prop('disabled', true);
        $selector.find(':not(:hidden)').children(':input').prop('disabled', false);
    $(function() {

    $(window).resize(function() {