Dashboard cards¶
Cards that put your parcels on a dashboard: what is on its way, from whom, and
when it lands. Each one is pulled straight from the Parcel Aggregator's
examples/dashboards/ folder when this site is
built, so it always matches the version that is actually shipped. For
notifications and other logic, see the automation cookbook.
Like the automations, they are carrier-agnostic: they read the aggregator's merged sensors and the canonical parcel fields, so a card keeps working when you add a carrier — no new rows to write.
Would rather not write YAML at all? Two community projects package all of this into a finished card that finds your sensors by itself — see ready-made parcel cards below.
Not running the aggregator?
These work per carrier too. Swap the aggregator's sensors for the carrier's
own (sensor.postnl_incoming_parcels, …). The parcels attribute holds the
same fields either way — see the contract.
Several carriers also ship dashboard examples of their own, in that repo's
examples/dashboards/ folder.
Where these go
Open your dashboard, ✏️ → + Add card → Manual, and paste over the contents. On a card that already exists: ⋮ → Edit → Show code editor.
Cards¶
Parcels by carrier
Markdown card showing the per-carrier counter breakdown for incoming, en-route-to-pickup-point and recently delivered parcels in one glance. Lives off the by_carrier attribute every aggregator summary sensor exposes.
# Markdown card showing the per-carrier counter breakdown for incoming,
# en-route-to-pickup-point and recently delivered parcels in one glance.
# Lives off the `by_carrier` attribute every aggregator summary sensor
# exposes.
#
# Iteration is driven by the carriers present in the data, so the card
# automatically adapts to which carrier integrations you actually have
# installed — there is no per-carrier setup.
type: markdown
title: Parcels by carrier
content: >-
{% set incoming = state_attr('sensor.parcel_aggregator_incoming_parcels', 'by_carrier') or {} %}
{% set pickup = state_attr('sensor.parcel_aggregator_awaiting_pickup', 'by_carrier') or {} %}
{% set delivered = state_attr('sensor.parcel_aggregator_delivered_parcels', 'by_carrier') or {} %}
{% set carriers = (incoming.keys() | list + pickup.keys() | list + delivered.keys() | list) | unique | list %}
| Carrier | Incoming | At pickup point | Recently delivered |
| --- | ---: | ---: | ---: |
{%- for carrier in carriers %}
| {{ carrier }} | {{ incoming.get(carrier, 0) }} | {{ pickup.get(carrier, 0) }} | {{ delivered.get(carrier, 0) }} |
{%- endfor %}
Next delivery
Next-delivery card across every installed carrier. The aggregator's next-delivery sensor exposes the earliest planned_from datetime as its native value (rendered relatively by HA: "in 3 hours" / "tomorrow 10:00") and the matching parcel under the parcel attribute — so the carrier badge comes for free.
# Next-delivery card across every installed carrier. The aggregator's
# next-delivery sensor exposes the earliest planned_from datetime as
# its native value (rendered relatively by HA: "in 3 hours" /
# "tomorrow 10:00") and the matching parcel under the `parcel` attribute — so the
# carrier badge comes for free.
type: entities
title: Next delivery
entities:
- entity: sensor.parcel_aggregator_next_delivery
name: Expected
secondary_info: last-updated
- type: attribute
entity: sensor.parcel_aggregator_next_delivery
attribute: parcel
name: Parcel
format: relative
- type: custom:template-entity-row
name: Carrier
state: >-
{% set p = state_attr('sensor.parcel_aggregator_next_delivery', 'parcel') %}
{{ p.carrier if p else 'no active parcels' }}
- type: custom:template-entity-row
name: Sender
state: >-
{% set p = state_attr('sensor.parcel_aggregator_next_delivery', 'parcel') %}
{{ p.sender if p and p.sender else '—' }}
# The two `custom:template-entity-row` rows need the Lovelace plugin
# `template-entity-row` from HACS. Without it, swap them for plain
# attribute rows that point at `parcel` and project the keys in a
# separate markdown card — the data is on the same attribute.
Unified active parcels grid
Markdown card that renders every active incoming parcel from every carrier in one table, with carrier, sender (clickable when a tracking URL is available), status, and the expected delivery window. Conditional on count > 0 so it hides itself on empty days.
# Markdown card that renders every active incoming parcel from every
# carrier in one table, with carrier, sender (clickable when a tracking
# URL is available), status, and the expected delivery window.
# Conditional on count > 0 so it hides itself on empty days.
type: conditional
conditions:
- condition: numeric_state
entity: sensor.parcel_aggregator_incoming_parcels
above: 0
card:
type: markdown
title: Active parcels
content: >-
{%- set labels = {
'registered': 'registered',
'in_transit': 'in transit',
'out_for_delivery': 'out for delivery',
'at_pickup_point': 'ready for collection',
'delivered': 'delivered',
'problem': 'problem',
'returning': 'returning to sender',
'unknown': 'status unknown'
} -%}
{%- set ns = namespace(rows=[]) -%}
{%- for p in state_attr('sensor.parcel_aggregator_incoming_parcels', 'parcels') or [] -%}
{%- set sender = p.sender or 'Unknown sender' -%}
{%- set status = labels.get(p.status, p.status) -%}
{%- set sender_cell = '[%s](%s)' | format(sender, p.url) if p.url else sender -%}
{%- if p.planned_from and p.planned_to -%}
{%- set expected = (as_timestamp(p.planned_from) | timestamp_custom('%a %-d %b %H:%M', true)) ~ '–' ~ (as_timestamp(p.planned_to) | timestamp_custom('%H:%M', true)) -%}
{%- elif p.planned_from -%}
{%- set expected = as_timestamp(p.planned_from) | timestamp_custom('%a %-d %b %H:%M', true) -%}
{%- else -%}
{%- set expected = '—' -%}
{%- endif -%}
{%- set ns.rows = ns.rows + ['| %s | %s | %s | %s |' | format(p.carrier, sender_cell, status, expected)] -%}
{%- endfor -%}
{{ '| Carrier | Sender | Status | Expected |\n| :-- | :-- | :-- | :-- |\n' ~ (ns.rows | join('\n')) }}
Ready-made parcel cards¶
The snippets above are deliberately plain — they use cards Home Assistant already ships, so nothing extra has to be installed. If you would rather have a finished parcel card, two community projects build one on top of these same sensors and detect your carriers automatically:
-
HKI Parcels Card — by jonisnet
Every carrier you run in one card, with tabs for in transit, delivered, sent and PostNL letterbox scans, a per-parcel delivery tracker, and a + Add parcel control for the account-less carriers. Visual editor, no YAML.
-
Package Tracker Card — by klaptafel
Drop it on a dashboard with no configuration at all and it finds every parcel sensor you have, deduplicated when the same parcel arrives through two sources, with an expandable event timeline per package and filters per carrier, status or direction.
Install either through HACS → Custom repositories, category Dashboard.
Both are independent projects, built and maintained by their authors and not by this org. The credit for them is theirs — and so are the bug reports and feature requests, which belong in their own trackers rather than in a carrier repo here.
Plugins the snippets use¶
A snippet that needs more than a built-in card reaches for one of these two Lovelace plugins instead. They are helpers inside a card rather than cards of their own, also installed through HACS:
| Plugin | By | What it adds |
|---|---|---|
custom:auto-entities |
Thomas Lovén | Fills a card's entity list from a filter instead of a hand-written list, so parcels that only exist tomorrow still show up |
custom:template-entity-row |
Thomas Lovén | Renders a template as an entities-card row — how you get a value that lives on an attribute onto its own line |
Neither is required. Every snippet that uses one says in its comments what to fall back to, and the parcel data sits on the sensor's attributes either way.