ha-frontend-query-params
Skill5.8k starsChanged 6 days agoApache-2.0View on GitHub
Home Assistant frontend URL query param patterns. Use when keeping page state such as filters, search, sorting or grouping in the URL, adding or reading typed query params, using QueryParamsController or the data table's sync-url and column urlName, building in-app links with a query string, or adding params to My Home Assistant redirects.
We split this skill into the rules it gives an agent
12 rules. About 0 are common, 4 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
Use this skill when implementing or reviewing state kept in the URL's query string, or links that set it.
- Distinctive: line 41
When a router reuses one element for different items, such as one app's tab after another's, key it on the item so the controller binds …
- Distinctive: line 47
Leave empty and default values out of the URL, such as an empty search or ascending direction.
- Very specific: line 52
Use one comma-separated param for several values (?status=new,installed), which list params do.
- Very specific: line 53
Use the names other pages already use for the same thing: search, sort, direction and group for the table, and floor, area, device, label, …
- Very specific: line 65
When a column definition is shared between pages, set its urlName where the shared column is defined, so every page uses the same name.
- Distinctive: line 74
Before calling a page done:
- Very specific: line 77
Search the codebase for in-app links and navigate() calls into the page with a query string, and move them to the current param names and …
- Very specific: line 81
When a page gains params, add them to its redirect in getMyRedirects() in src/panels/my/ha-panel-my.ts:
- Distinctive: line 85
Don't add legacy param names.
- Very specific: line 86
Add the same params to the redirect's entry in redirect.json in home-assistant/my.home-assistant.io, in a separate pull request that links …
- Very specific: line 91
Follow test/common/url/query-params.test.ts, test/common/url/data-table-query-params.test.ts and …
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
HA Frontend Query Params
Use this skill when implementing or reviewing state kept in the URL's query string, or links that set it. Cross-load ha-frontend-lit for the controller and lifecycle work, ha-frontend-testing for running tests, and ha-frontend-review when reviewing.
Typed Params
src/common/url/query-params.ts turns a typed config into values and back:
QueryParamConfiglists keys by type:string,list(one comma-separated param,?label=a,b),date(ISO string) andboolean({ key, trueValue }, true only when the param equalstrueValue).QueryParamValues<typeof config>is the matching partial values type.decodeQueryParams(source, config)reads aSearchParamsSource(aURLSearchParams, a plain record or a query string). Empty values are skipped.createQueryString(values, config)writes the values, leaving out empty strings, empty lists and false booleans.updateQueryString(current, values, config)replaces only the config's params in an existing query string and keeps every other param exactly as written.hasQueryParams(source, config)tells whether a URL has any of the config's params, which is how a page knows whether a link asked for anything.serviceTargetFromQueryParamsandqueryParamsFromServiceTargetmapHassServiceTargetkeys to list params.
Give each feature its own *-query-params.ts in src/common/url/ holding its config (as const satisfies QueryParamConfig), its values type, and the helpers that turn params into page state and back. Follow the existing ones: data-table-query-params.ts, marketplace-query-params.ts, history-logbook-query-params.ts and todo-query-params.ts. Helpers in src/data/ that build outside links use a local config the same way, as src/data/integration.ts and src/data/system_log_report.ts do. In new code, prefer these over hand-built URLSearchParams, string concatenation or extractSearchParam reads from src/common/url/search-params.ts.
QueryParamsController
src/common/controllers/query-params-controller.ts keeps part of a page's state in the URL. Construct it with the host, the feature's config and options:
enabled(): whether the host syncs right now. It can change, as on the Marketplace dashboard, which turns it off on the Discover tab.navigated(): called when the page's URL changes while the host is connected (location-changedorpopstate), and when the host reconnects after its first update. Read the URL withsearchParams(), apply it, thenwrite()the state back.
Methods:
searchParams()returns the current params, orundefinedwhen the controller is disabled or the URL is on another path.write(values, debounced?)updates only the config's params withreplaceCurrentUrl, so it replaces the current history entry. Passdebouncedfor values that change while typing; the write lands 300 ms after the last one and any later write replaces it.flush()writes a pending debounced write now. The data table calls it when the search box loses focus. Call it before anything that adds its own history entry, such as a dialog.
The controller binds to currentPath() when the host connects and does nothing while the URL is on another path. Navigating away drops a pending write. When a page is reached through a router defaultPage (such as /marketplace landing on /marketplace/browse), check that a link to the bare path still syncs, since the redirect may land after the host connects.
Read the URL yourself on the first render, after the initial state is set (in willUpdate before hasUpdated, or firstUpdated), and leave later navigations to navigated(). ha-marketplace-dashboard.ts and lorawan-devices.ts show both. When moving a page to the controller, remove its own location-changed and popstate listeners and any search kept in history state, since the controller replaces them.
When a router reuses one element for different items, such as one app's tab after another's, key it on the item so the controller binds again to the new path.
URL Rules
- Writes replace the current history entry, so Back leaves the page instead of undoing each change. Push a new entry only for a deliberate navigation, such as picking another log provider.
- Debounce search writes. Write sort, group and filter changes straight away.
- Leave empty and default values out of the URL, such as an empty search or ascending direction.
- A param in the URL wins over stored preferences. A link with none of the page's params keeps what the user picked before;
hasQueryParamsmakes that check. - A link with any of the page's params replaces the search and every filter, since a filter left over from before would hide part of what the link shows. Show what the link applied, but don't store it: links often carry someone else's choices, such as a My Home Assistant link made with
m. Store only what the user changes on the page, and only that setting. - The URL is the whole state. Don't add filters while applying a link; a link that needs one, such as showing disabled devices, says so in its params (
status=disabled). - A link to a page that is already open must apply the new URL. Handle
navigated(), not only the first render. The router often keeps a page alive, so this is the common case, not an edge case. - Use one comma-separated param for several values (
?status=new,installed), whichlistparams do. - Use the names other pages already use for the same thing:
search,sort,directionandgroupfor the table, andfloor,area,device,label,config_entry,sub_entryandassistantsfor the shared filters. - A search box's text is always
search, neverfilterorq. - Keep existing param names so existing links and My Home Assistant links keep working. When renaming, read the old name as well for about six months, with a code comment naming the release it can be removed in, and write only the new name. Keep the old name in the config, use it only as a fallback when reading, and leave it out of the values you write, so the next write removes it from the URL.
- Pages opt in. Some use params for other things, such as
?domain=starting the add integration flow on the integrations dashboard, so check what a page already reads before syncing it. - Collapsed groups, column order and hidden columns stay in storage (
@storage), not the URL. - Link to a page by its main path, not an alias, so a link to an open page lands on the same path and applies.
Data Tables
hass-tabs-subpage-data-table with the sync-url attribute keeps search, sort, direction and group in the URL through dataTableQueryParamsFromState and dataTableQueryStateFromUrl. A linked search replaces the current one, and sorting or grouping a link leaves out keeps what was picked before. The table fires search-changed, sorting-changed and grouping-changed when a link changes them, with link set, so the page shows them without storing them. Use DataTableSettingsController (src/common/controllers/data-table-settings-controller.ts) for the stored search, sorting and grouping: give it the page's storage keys, bind its search, sorting and grouping to the table, and its searchChanged, sortingChanged and groupingChanged to the table's events. Call showSearch for a search the page reads from a link itself. A page the router keeps open shows what the user stored when it's opened again without params, like from the sidebar: the controller and table restore the search, sorting and grouping, and a page with filters restores its stored filters when its URL has no params.
- Give sortable and groupable columns a short public
urlName(for exampletype, nottranslated_category). Sorting falls back to the column id without one; grouping is only written for columns that have one, so internal ids don't end up in links. - When a column definition is shared between pages, set its
urlNamewhere the shared column is defined, so every page uses the same name. - The table reads the URL once, as soon as it has columns. When a column only exists once data has loaded, such as Filename on the dashboards page, turn syncing on only after loading succeeds (
?sync-url=${!this._loading && !this._loadFailed}), or a link that sorts or groups by that column is dropped. - The page owns its filters. Give it a filter config in its
*-query-params.ts, a helper that turns filter values into params, and one that turns a link back into the search and filter values. The link helper returnsundefinedwhen the URL has none of the filter params and none of the table's (dataTableQueryParamConfig), so stored choices stay. Use a second controller on the page for them, asha-marketplace-dashboard.tsandlorawan-devices.tsdo.browseUrlandbrowseLinkFromUrlinsrc/panels/marketplace/dashboards/dashboard-repositories.tsshow the pair. - Filter components also send the value the page gave them once they have looked up their items, so a filter event isn't always the user's. Store a filter only when it changes what the URL holds, comparing the filters' query strings before and after, as
helpersFilterQueryStringdoes, and store only that filter. - A memoized filter that also reads async state (anything loaded after the page connects) must take that state as a memoize argument, or a linked filter shows stale results until something else changes.
- A filter component that looks up related items asynchronously should drop a lookup whose value has since changed, or two links landing within one lookup apply the older one.
Coverage Check
Before calling a page done:
- List every filter, sortable column, groupable column, the search box and any view toggle (such as a tab, raw view or provider). Confirm each one is written to the URL and read back from a link, both on first render and on a page that is already open.
- Search the codebase for in-app links and
navigate()calls into the page with a query string, and move them to the current param names and the page's main path.lorawan-connections.tsandlorawan-overview.tslink intolorawan-devices.tsthis way.
My Home Assistant Links
When a page gains params, add them to its redirect in getMyRedirects() in src/panels/my/ha-panel-my.ts:
- List them under
paramsas"string?", matching what the page reads. For a data table page that includessearch,sort,directionandgroupas well as its filters. Keep the list in a const next to the others, such asmarketplaceTableParams, which several redirects share. - Point the redirect at the page's main path, the one the page's controller binds to.
- Don't add legacy param names.
- Add the same params to the redirect's entry in
redirect.jsoninhome-assistant/my.home-assistant.io, in a separate pull request that links the frontend pull request. Merge it only once the frontend pull request is indev. - In the frontend pull request, link the My Home Assistant pull request under "Additional information":
- Link to My Home Assistant pull request: home-assistant/my.home-assistant.io#<n>.
Tests
Follow test/common/url/query-params.test.ts, test/common/url/data-table-query-params.test.ts and test/common/controllers/query-params-controller.test.ts. For a feature's params file, cover the encode and decode round trip, defaults being left out, and legacy names still being read. Test the helpers, not the page rendering, and only where the logic could break without anyone noticing.
Trending
Quiet since September.about the whole repository
Who's talking about home-assistant/frontend
- home-assistant/core“Built-in map now displays "API KEY REQUIRED" watermark from CARTO”GitHub · Aug 26, 2026 · 196 reactions
- home-assistant/core“Add label support”GitHub · Apr 13, 2022 · 70 reactions
- home-assistant/core“Add webauthn auth provider”GitHub · Jul 28, 2024 · 49 reactions
- home-assistant/iOS“Can't copy/paste in Files Editor add-on in macOS app (Safari works fine)”GitHub · Oct 4, 2020 · 44 reactions
- home-assistant/core“Add Marketplace integration”GitHub · Sep 28, 2026 · 41 reactions
The biggest 5 of 40 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 40
Talked about on GitHub (40 times).
On GitHub · 40
- Aug 26, 2026Built-in map now displays "API KEY REQUIRED" watermark from CARTO — home-assistant/core196 reactions · 9 comments
- Apr 13, 2022Add label support — home-assistant/core70 reactions · 23 comments
- Jul 28, 2024Add webauthn auth provider — home-assistant/core49 reactions · 5 comments
- Oct 4, 2020Can't copy/paste in Files Editor add-on in macOS app (Safari works fine) — home-assistant/iOS44 reactions · 55 comments
- Sep 28, 2026Add Marketplace integration — home-assistant/core41 reactions · 4 comments
35 more on GitHub
- Sep 2, 2026Home Assistant iOS 2026.9.0 Breaks "Perform Action" iOS Shortcuts — home-assistant/iOS34 reactions · 15 comments
- Jun 6, 2023Popup Title Styles Changed in 2023.6.0 — thomasloven/hass-browser_mod27 reactions · 24 comments
- Aug 12, 2023Home Assistant Companion on macOS randomly opens on its own — home-assistant/iOS24 reactions · 52 comments
- Jul 6, 2025Add two way audio support — home-assistant/core21 reactions · 24 comments
- Oct 28, 2021fix icons not showing in new HA release — nervetattoo/simple-thermostat18 reactions · 10 comments
- Aug 20, 2020Add zwave to ozw migration — home-assistant/core17 reactions · 7 comments
- Jul 26, 2025Companion app v2025.7.1 auto launches randomly on iPad Pro — home-assistant/iOS16 reactions · 65 comments
- Mar 18, 2020Support openid connect like authentication providers — home-assistant/core16 reactions · 37 comments
- Mar 11, 2026Every field can explain itself — OpenHomeFoundation/roadmap15 reactions · 3 comments
- Jun 28, 2020Add progress bar to hassio update/install — home-assistant/supervisor15 reactions · 9 comments
- Aug 7, 2025Recreate entity_id button is not changing entity_ids of disabled entities — home-assistant/core14 reactions · 17 comments
- Jul 7, 2021Received invalid command: mobile_app/push_notification_channel — home-assistant/iOS14 reactions · 76 comments
- Sep 18, 2020App does not remember the window size — home-assistant/iOS14 reactions · 21 comments
- Feb 14, 2022Add webhook trigger allowed_methods/local_only options — home-assistant/core11 reactions · 32 comments
- May 18, 2024Add a syntax for merging lists of triggers — home-assistant/core8 reactions · 10 comments
- Jun 14, 2021Add Select entity component platform — home-assistant/core7 reactions · 1 comment
- Oct 3, 2020Implement time tracking in templates — home-assistant/core7 reactions · 25 comments
- Jul 13, 2020Drop white blacklist pt1 — home-assistant/core7 reactions
- Apr 24, 2020Add NOT condition helper — home-assistant/core7 reactions · 3 comments
- Aug 14, 2026Add local disk mounts to the mount API — home-assistant/supervisor6 reactions · 10 comments
- Feb 24, 2026Add brands system integration to proxy brand images through local API — home-assistant/core6 reactions
- Nov 18, 2025Introduce Home Assistant Labs — home-assistant/core6 reactions · 1 comment
- Jul 23, 2025Add clean_area action to vacuum — home-assistant/core6 reactions · 9 comments
- May 26, 2025Add initial_color property to CalendarEntity — home-assistant/core6 reactions · 25 comments
- Jan 12, 2021Add support for discovery via DHCP — home-assistant/core6 reactions · 5 comments
- Nov 9, 2025Add 'Collection image' integration — home-assistant/core5 reactions · 21 comments
- Jun 11, 2025Use new frontend entity more info deeplink — home-assistant/android5 reactions · 2 comments
- Oct 29, 2024Support readonly selectors in config_flows — home-assistant/core5 reactions · 19 comments
- Oct 28, 2021fix icons not showing in new HA version — artem-sedykh/mini-climate-card5 reactions · 9 comments
- Sep 16, 2021Apply templated style to elements, both for element and card — iantrich/config-template-card5 reactions · 1 comment
- Apr 25, 2021Better ZHA device reconfiguration — home-assistant/core5 reactions · 4 comments
- Aug 19, 2020Add api to reload config entries — home-assistant/core5 reactions
- Jul 18, 2020Allow to set default dark theme — home-assistant/core5 reactions · 3 comments
- Jun 13, 2020Fix history graphs with mysql/mariadb — home-assistant/core5 reactions · 3 comments
- Apr 15, 2026Add SerialSelector — home-assistant/core4 reactions · 5 comments
In this repository13 other files it gives its agents.
- AGENTS.mdAGENTS.md
- ha-frontend-componentsSkill
- ha-frontend-contextsSkill
- ha-frontend-demoSkill
- ha-frontend-eventsSkill
- ha-frontend-gallerySkill
- ha-frontend-litSkill
- ha-frontend-reviewSkill
- ha-frontend-stylingSkill
- ha-frontend-testingSkill
- ha-frontend-typesSkill
- ha-frontend-user-facing-textSkill
- ha-frontend-ux-readinessSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

