Skip to content

Add template hooks to all filters for customizable UIs (dropdowns, sliders, etc.) - #970

Merged
mmzeynalli merged 4 commits into
smithyhq:mainfrom
fd-oncodna:main
Apr 18, 2026
Merged

Add template hooks to all filters for customizable UIs (dropdowns, sliders, etc.)#970
mmzeynalli merged 4 commits into
smithyhq:mainfrom
fd-oncodna:main

Conversation

@fd-oncodna

@fd-oncodna fd-oncodna commented Dec 2, 2025

Copy link
Copy Markdown
Contributor

Overview

This PR introduces customizable filter templates by adding a template attribute to filters. All built-in filters now define a default template to ensure full backward compatibility.

Filters are rendered through their declared template, with predictable fallbacks, making template overrides a first-class extension point.

Goals

  • Enable fully custom filter UIs (dropdowns, chips, branded widgets) without copying or patching core templates.
  • Provide a stable extension mechanism while preserving current behavior via default templates.

Example

from sqladmin.filters import StaticValuesFilter

class StatusDropdownFilter(StaticValuesFilter):
    template = "filters/status_dropdown_filter.html"

    def __init__(self):
        super().__init__(
            column=Article.status,
            values=[("draft", "Draft"), ("published", "Published")],
            title="Status",
        )

filters/status_dropdown_filter.html

  {% extends "sqladmin/filters/base_filter.html" %}
  {% block filter_body %}
    {% set current_value = request.query_params.get(filter.parameter_name, '') %}
    <form method="get" class="d-flex flex-column" style="gap: 8px;">
      {% for key, value in request.query_params.items() %}
        {% if key != filter.parameter_name %}
        <input type="hidden" name="{{ key }}" value="{{ value }}">
        {% endif %}
      {% endfor %}

      <select name="{{ filter.parameter_name }}" class="form-select form-select-sm">
        {% for value, label in filter.lookups(request, model_view.model, model_view._run_arbitrary_query) %}
          <option value="{{ value }}" {% if current_value == value %}selected{% endif %}>{{ label }}</option>
        {% endfor %}
      </select>

      <div class="d-flex align-items-center" style="gap: 8px;">
        <button type="submit" class="btn btn-sm btn-outline-primary">Apply</button>
        {% if current_value %}
        <a href="{{ request.url.remove_query_params(filter.parameter_name) }}" class="text-decoration-none small">Clear</a>
        {% endif %}
      </div>
    </form>
  {% endblock %}

By subclassing an existing filter and overriding only its template, developers can package and reuse bespoke filter widgets with minimal effort.

@fd-oncodna fd-oncodna changed the title Enable custom column filter templates and document bespoke filter widgets Allow Column Filters to Declare Custom Templates and Override Built-ins Dec 2, 2025
@fd-oncodna fd-oncodna changed the title Allow Column Filters to Declare Custom Templates and Override Built-ins Add template hooks to all filters for customizable UIs (dropdowns, sliders, etc.) Dec 5, 2025

@mmzeynalli mmzeynalli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! Do please update the mentioned part. Also, would be nice if you provided screenshots of vanilla vs custom design filter design, even if minimal.

Comment thread sqladmin/templates/sqladmin/list.html Outdated
filter.template
if filter.template is defined and filter.template
else (
"sqladmin/filters/operation_filter.html"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can skip this part. If user creates new filter, they will inherit the existing one, if they create from scratch, it should be their responsibility. No need for extra has_operator check.

@mmzeynalli mmzeynalli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM @aminalaee

@fd-oncodna

Copy link
Copy Markdown
Contributor Author

Sorry for the delay, here are the screenshots!

Before:

image

After:
image

Code used:

class StatusDropdownFilter(StaticValuesFilter):
    template = "filters/status_dropdown_filter.html"

Template:

{% extends "sqladmin/filters/base_filter.html" %}

{% block filter_body %}
  {% set current_value = request.query_params.get(filter.parameter_name, '') %}
  <form method="get" class="d-flex flex-column" style="gap: 8px;">
    {% for key, value in request.query_params.items() %}
      {% if key != filter.parameter_name %}
        <input type="hidden" name="{{ key }}" value="{{ value }}">
      {% endif %}
    {% endfor %}

    <select name="{{ filter.parameter_name }}" class="form-select form-select-sm">
      {% for value, label in filter.lookups(request, model_view.model, model_view._run_arbitrary_query) %}
        <option value="{{ value }}" {% if current_value == value %}selected{% endif %}>
          {{ label }}
        </option>
      {% endfor %}
    </select>

    <div class="d-flex align-items-center" style="gap: 8px;">
      <button type="submit" class="btn btn-sm btn-outline-primary">Apply</button>
      {% if current_value %}
        <a href="{{ request.url.remove_query_params(filter.parameter_name) }}" class="text-decoration-none small">Clear</a>
      {% endif %}
    </div>
  </form>
{% endblock %}

@mmzeynalli

Copy link
Copy Markdown
Member

@aminalaee ?

@fd-oncodna

Copy link
Copy Markdown
Contributor Author

Hi everyone, happy new year! Any reason why this is not merged yet?

@mmzeynalli

Copy link
Copy Markdown
Member

Hey! @aminalaee seems to be busy lately. I have been trying to help him with reviews and stuff, but still, merging is on his hands.

@smithyhq smithyhq deleted a comment from mmzeynalli Mar 30, 2026
@mmzeynalli mmzeynalli added the ready-to-merge Approved and ready to merge. Will be published with the next release. label Apr 4, 2026
@mmzeynalli
mmzeynalli merged commit a54c013 into smithyhq:main Apr 18, 2026
6 checks passed
@mmzeynalli mmzeynalli mentioned this pull request Apr 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Approved and ready to merge. Will be published with the next release.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants