
Managing Filter State for Embedded Dashboards
Dashboard filters are important components that enhance the functionality and interactivity of Apache Superset dashboards. They allow users to quickly interact with the dashboard to fulfill their needs, allowing for a variety of combinations.
Unlike RLS rules, dashboard filters are not meant to restrict access, but instead provide interactivity and allow a single dashboard to be used for various purposes. On top of that, dashboard filters can also be used in combination with Jinja macros for more advanced use cases.
When a filter is configured, users can define its default value, which is then going to be applied when the dashboard is loaded without any context/state applied. In Superset, filter state is managed through permalinks.
What are permalinks, and why use them?
In previous versions of Superset, the entire filter configuration would be managed through URL parameters (check out this page if you’re curious about how it used to work). While URL parameters are easy for quick manipulation, it has a browser-specific limitation on the maximum number URL characters. You would be surprised at how quickly a dashboard can end up with multiple filters (especially with advanced scoping configurations provided by Superset), and users were facing this limitation more and more frequently.
The need for a more robust solution was clear, and that brings us to permalinks. Instead of managing filter state via URL, filters are now stored in the backend:
- The client/frontend sends an API call providing a payload with the desired filter combination.
- This configuration (called "filter state") is stored in the backend, and a corresponding key is returned.
- This key can be used to load the dashboard with the desired filter configuration.
Let’s take a closer look at how to dynamically create filter states and how to apply this to embedded dashboards!
Pre-requisites
This blog post assumes you have experience interacting with the Preset (and Superset) APIs. If you need a refresher, check out our API documentation. If this is your first time interacting with APIs, check our introductory blog post.
This example is implemented using a dashboard built off of the examples
database connection that’s available on every Preset Workspace. Download the ZIP file below and import it into your Workspace to get it quickly deployed on your environment.
📁 Dashboard Filters Example Dashboard
Getting the filter data to use in the API
To interact with this API, we need to know some details about the filter(s) we want to manage. This information is exposed in the native_filter_configuration
configuration. There are two ways to get it:
Via the API
- Use the Get all Dashboards from a Workspace API endpoint to identify this dashboard ID.
- Get this dashboard’s
json_metadata
via the Get a Dashboard API endpoint. - This field is a stringified JSON. Load it as a JSON, and get the
native_filter_configuration
configuration.
Via the UI
- Access the Dashboards menu in the top navigation bar.
- Search for the desired dashboard.
- Hover over it, then click on the pencil icon under the Actions column.
- The Dashboard Properties modal will load. Expand the Advanced section to find the JSON metadata.
- Similar to the API response, this JSON includes the
native_filter_configuration
.
This is the native_filter_configuration
for the example dashboard we’re using:
"native_filter_configuration": [
{
"id": "NATIVE_FILTER-iviyG3v4DbOWJpTnrrZQB",
"controlValues": {"enableEmptyFilter": false},
"name": "Period",
"filterType": "filter_time",
"targets": [{}],
"defaultDataMask": {
"extraFormData": {},
"filterState": {},
"ownState": {}
},
"cascadeParentIds": [],
"scope": {"rootPath": ["ROOT_ID"], "excluded": [125]},
"type": "NATIVE_FILTER",
"description": ""
},
{
"id": "NATIVE_FILTER-dojkIJC0bW3_usrEeMsNK",
"controlValues": {"enableEmptyFilter": true},
"name": "Time Grain",
"filterType": "filter_timegrain",
"targets": [{"datasetId": 47}],
"defaultDataMask": {
"extraFormData": {"time_grain_sqla": "P1D"},
"filterState": {"label": "Day", "value": ["P1D"]}
},
"cascadeParentIds": [],
"scope": {"rootPath": ["ROOT_ID"], "excluded": [124, 125]},
"type": "NATIVE_FILTER",
"description": ""
},
{
"id": "NATIVE_FILTER-PyPFsXwYeMUP5hXhO_UZe",
"controlValues": {"enableEmptyFilter": false},
"name": "Time Column",
"filterType": "filter_timecolumn",
"targets": [{"datasetId": 47}],
"defaultDataMask": {
"extraFormData": {},
"filterState": {},
"ownState": {}
},
"cascadeParentIds": [],
"scope": {"rootPath": ["ROOT_ID"], "excluded": [124, 125]},
"type": "NATIVE_FILTER",
"description": ""
},
{
"id": "NATIVE_FILTER-kBLFbLBmzT8sAAOVVdi8D",
"controlValues": {
"enableEmptyFilter": false,
"defaultToFirstItem": false,
"multiSelect": true,
"searchAllOptions": false,
"inverseSelection": false
},
"name": "Line",
"filterType": "filter_select",
"targets": [{"datasetId": 47, "column": {"name": "product_line"}}],
"defaultDataMask": {
"extraFormData": {},
"filterState": {},
"ownState": {}
},
"cascadeParentIds": [],
"scope": {"rootPath": ["ROOT_ID"], "excluded": []},
"type": "NATIVE_FILTER",
"description": ""
},
{
"id": "NATIVE_FILTER-KKudjvgnhuMq5JYHwNE6L",
"controlValues": {"enableEmptyFilter": false},
"name": "Month",
"filterType": "filter_range",
"targets": [{"datasetId": 47, "column": {"name": "month"}}],
"defaultDataMask": {
"extraFormData": {},
"filterState": {},
"ownState": {}
},
"cascadeParentIds": [],
"scope": {"rootPath": ["ROOT_ID"], "excluded": [123, 124]},
"type": "NATIVE_FILTER",
"description": ""
}
]
We’re looking for below information from each filter:
id
: The unique ID is used to identify each filter when applying the desired configurations.filterType
: Each filter type has unique configuration options.name
: To differentiate filters. This is specially useful if you have multiple filters of the same type in a dashboard.targets
: For filters that are applied to a specific column (like the value filter), thetargets
configuration specifies it.
Let’s take a look at how to configure these different filter types.
Understanding the different filter types
Superset supports below filter types:
Filter type (UI) | filterType (API schema) |
---|---|
Time range | filter_time |
Time grain | filter_timegrain |
Value | filter_select |
Numerical range | filter_range |
Time Column | filter_timecolumn |
The Time Range filter
Filter schema:
"${NATIVE_FILTER_ID}": {
"id": "${NATIVE_FILTER_ID}",
"extraFormData": {
"time_range": "${TIME_RANGE}"
},
"filterState": {
"value": "${TIME_RANGE}"
}
}
Replace:
${NATIVE_FILTER_ID}
with the unique ID associated with the filter.${TIME_RANGE}
with the desired time range.
The syntax used for the ${TIME_RANGE}
should be from_dttm : to_dttm
. Also, the same custom DATETIME
syntax supported by Advanced time range filters is accepted here. Some examples:
DATEADD(DATETIME(\"2025-01-16T00:00:00\"), -7, day) : 2025-01-16T00:00:00
2025-03-01 : 2025-03-08
2025 :
(time range with only afrom_dttm
value).: 2026
(time range with only ato_dttm
value).
The Time Grain filter
Filter schema:
"${NATIVE_FILTER_ID}": {
"id": "${NATIVE_FILTER_ID}",
"extraFormData": {
"time_grain_sqla": "${TIME_GRAIN}"
},
"filterState": {
"label": "${TIME_GRAIN_LABEL}",
"value": ["${TIME_GRAIN}"]
}
}
Replace:
${NATIVE_FILTER_ID}
with the unique ID associated with the filter.${TIME_GRAIN}
and${TIME_GRAIN_LABEL}
with the desired values, according to the table below:
TIME_GRAIN |
TIME_GRAIN_LABEL |
---|---|
PT1S |
Second |
PT1M |
Minute |
PT1H |
Hour |
P1D |
Day |
P1W |
Week |
P1M |
Month |
P3M |
Quarter |
P1Y |
Year |
Note that your database might not support all these time grain options (or it could even support more). It depends on whether the database‘s Engine Specifications in Superset have these defined.
Value
Filter schema:
"${NATIVE_FILTER_ID}": {
"id": "${NATIVE_FILTER_ID}",
"extraFormData": {
"filters": [
{
"col": "${TARGET_COLUMN}",
"op": "${OPERATOR}",
"val": ["${FILTER_VALUES}"]
}
]
},
"filterState": {
"value": ["${FILTER_VALUES}"]
}
}
Replace:
${NATIVE_FILTER_ID}
with the unique ID associated with the filter.${TARGET_COLUMN}
with the dataset column that’s affected by the filter.${OPERATOR}
with eitherIN
orNOT IN
.${FILTER_VALUES}
with the options to apply.
${FILTER_VALUES}
can be single or multiple values. Examples:
"Classic Cars"
“Trains", "Planes"
Numerical Range
Filter schema:
"${NATIVE_FILTER_ID}": {
"id": "${NATIVE_FILTER_ID}",
"extraFormData": {
"filters": [
{
"col": "${TARGET_COLUMN}",
"op": "${MIN_OPERATOR}",
"val": "${MIN_VALUE}"
},
{
"col": "${TARGET_COLUMN}",
"op": "${MAX_OPERATOR}",
"val": "${MAX_VALUE}"
}
]
},
"filterState": {
"value": ["${MIN_VALUE}", "${MAX_VALUE}"],
"label": "${MIN_VALUE} ${MIN_OPERATOR} x ${MAX_OPERATOR} ${MAX_VALUE}"
}
}
Replace:
${NATIVE_FILTER_ID}
with the unique ID associated with the filter.${TARGET_COLUMN}
with the dataset column that’s affected by the filter.${MIN_OPERATOR}
and${MAX_OPERATOR}
with the desired operator. Available options:>
,>=
,<
and<=
.${MIN_VALUE}
and${MAX_VALUE}
with the desired integer values.
Time Column
Filter schema:
"${NATIVE_FILTER_ID}": {
"id": "${NATIVE_FILTER_ID}",
"extra_form_data": {
"granularity_sqla": "${COLUMN_NAME}"
},
"filterState": {
"value": ["${COLUMN_NAME}"]
}
}
Replace:
${NATIVE_FILTER_ID}
with the unique ID associated with the filter.${COLUMN_NAME}
with the column that should be used for filtering.
Let’s put these together in action!
The "Create a Dashboard Permalink" API endpoint
The Create a dashboard Permalink API endpoint payload is simple:
{
"dataMask": {
"${FILTER_CONFIGURATION_JSON}"
}
}
In this context, "${FILTER_CONFIGURATION_JSON}"
is the final JSON for the filter configurations that need to be applied.
For example, we can use the below payload to set:
- The period (time range) filter to:
2025-03-01 : today
. - The time grain filter to:
Week
. - The time column to:
order_date
. - The Line filter to:
Classic Cars, Vintage Cars
. - The month (numerical range) filter to:
4 ≥ x <= 8
.
{
"dataMask": {
"NATIVE_FILTER-iviyG3v4DbOWJpTnrrZQB": {
"id": "NATIVE_FILTER-iviyG3v4DbOWJpTnrrZQB",
"extraFormData": {
"time_range": "2025-03-01 : today"
},
"filterState": {
"value": "2025-03-01 : today"
}
},
"NATIVE_FILTER-dojkIJC0bW3_usrEeMsNK": {
"id": "NATIVE_FILTER-dojkIJC0bW3_usrEeMsNK",
"extraFormData": {
"time_grain_sqla": "P1W"
},
"filterState": {
"label": "Week",
"value": [
"P1W"
]
}
},
"NATIVE_FILTER-PyPFsXwYeMUP5hXhO_UZe": {
"id": "NATIVE_FILTER-PyPFsXwYeMUP5hXhO_UZe",
"extra_form_data": {
"granularity_sqla": "order_date"
},
"filterState": {
"value": [
"order_date"
]
}
},
"NATIVE_FILTER-kBLFbLBmzT8sAAOVVdi8D": {
"id": "NATIVE_FILTER-kBLFbLBmzT8sAAOVVdi8D",
"extraFormData": {
"filters": [
{
"col": "product_line",
"op": "IN",
"val": [
"Classic Cars",
"Vintage Cars"
]
}
]
},
"filterState": {
"value": [
"Classic Cars",
"Vintage Cars"
]
}
},
"NATIVE_FILTER-KKudjvgnhuMq5JYHwNE6L": {
"id": "NATIVE_FILTER-KKudjvgnhuMq5JYHwNE6L",
"extraFormData": {
"filters": [
{
"col": "month",
"op": ">=",
"val": "4"
},
{
"col": "month",
"op": "<",
"val": "8"
}
]
},
"filterState": {
"value": [
"4",
"8"
],
"label": "4 >= x <= 8"
}
}
}
}
The API response includes both the permalink key and the full URL to access the dashboard with this state applied.
Applying a permalink to a dashboard in Embedded mode
To load the dashboard with this state in embedded mode, you just need to pass the permalink key as a URL parameter to the Embedded SDK. For example:
const myEmbeddedDashboard = presetSdk.embedDashboard({
id: dashboardId,
supersetDomain: supersetDomain,
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: async () => fetchGuestTokenFromBackend(),
dashboardUiConfig: {
hideTitle: false,
hideChartControls: true,
urlParams: {
"permalink_key": "aE6zJGOJK3k" // <= Permalink key value retrieved via the API
}
}
});
Next Steps
We’ll be releasing a new blog post soon to talk about how to retrieve filter state for embedded dashboards. Stay tuned, we'll be addind a link right here.