Custom Home Assistant integration for Eltern-Portal – the school communication platform used by many schools in Bavaria, Germany.
| Sensor | Description | Icon |
|---|---|---|
| Schulinformationen | School contact details & teacher directory | 🏫 |
| Stundenplan | Timetable with subjects, rooms & teacher assignments | 📅 |
| Schulaufgaben | Exam schedule (Schulaufgaben, Tests, Kurzarbeiten) | 📝 |
| Termine | General school appointments & events | 📆 |
| Schwarzes Brett | Bulletin board messages (active + archived) | 📌 |
| Elternbriefe | Parent letters with unread count & metadata | ✉️ |
| Kommunikation Fachlehrer | Teacher communication messages | 💬 |
| Umfragen | Surveys & polls with voting status | 📊 |
- 🧒 Child name in setup flow – set a name or short code during initial setup for consistent entity IDs
- 🏷️ Smart sensor naming – entity IDs follow
sensor.[slug]_[child]_[sensor]pattern - 👨👩👧👦 Multi-child support – add the integration multiple times, one per child
- ⚙️ Options flow – change display name via UI (Settings → Integrations → Configure)
- 🔄 Manual fetch – data is fetched on demand via
elternportal.fetch_dataservice - 🔐 Secure authentication – CSRF-token based login with session management
- 📏 Recorder-safe – large text fields (body, content) are stripped from attributes to stay under 16KB
- 🛡️ Stale-data protection – sensors keep last good data when the portal is temporarily unreachable
- 🔁 Auto session recovery – automatic re-login and session refresh on connection loss
- Open HACS in Home Assistant
- Click ⋮ (top right) → Custom repositories
- Add
https://github.com/workFLOw42/Elternportal_APIas Integration - Search for ElternPortal API and install
- Restart Home Assistant
- Download the latest release
- Copy the
custom_components/elternportalfolder to yourconfig/custom_components/directory - Restart Home Assistant
Step 1 – Credentials:
- Go to Settings → Devices & Services → Add Integration
- Search for ElternPortal API
- Enter your credentials:
| Field | Description | Example |
|---|---|---|
| School slug | Subdomain of your school's Eltern-Portal URL | gymnasium-musterstadt |
| Username | Your login email | parent@example.com |
| Password | Your login password | •••••••• |
💡 The school slug is the part before
.eltern-portal.orgin your school's URL. For example:https://gymnasium-musterstadt.eltern-portal.org→ slug isgymnasium-musterstadt
Step 2 – Child Name:
- Enter a name or short code for your child
| Input | Entity ID Example | Friendly Name |
|---|---|---|
MAX |
sensor.gymnasium_musterstadt_max_schulaufgaben |
gymnasium-musterstadt MAX Schulaufgaben |
Lisa M |
sensor.gymnasium_musterstadt_lisa_m_schulaufgaben |
gymnasium-musterstadt Lisa M Schulaufgaben |
| (empty) | sensor.gymnasium_musterstadt_schulaufgaben |
gymnasium-musterstadt Schulaufgaben |
⚠️ Entity IDs are set during initial setup and cannot be changed later. Choose wisely – especially when you have multiple children at the same school.
Add the integration once per child:
- Settings → Devices & Services → Add Integration → ElternPortal API
- Use the same credentials but a different child name
- Each child gets its own set of sensors with unique entity IDs
- Go to Settings → Devices & Services
- Find ElternPortal API and click Configure
- Change the display name (affects friendly name only, not entity IDs)
This integration uses manual fetch only (no automatic polling). To fetch data:
service: elternportal.fetch_data
data: {}automation:
- alias: "Fetch ElternPortal data every morning"
trigger:
- platform: time
at: "07:00:00"
action:
- service: elternportal.fetch_dataGo to Developer Tools → Services → search for elternportal.fetch_data → Call Service
The integration includes built-in protection against intermittent connection issues:
- Stale-data protection: If the portal returns empty data but previously had valid entries, the integration keeps the last good data instead of showing empty sensors
- Auto session recovery: When a session expires or drops, the integration automatically creates a fresh session and retries
- Graceful degradation: If individual endpoints fail but others succeed, partial data is preserved
- Max retry threshold: After 3 consecutive empty fetches, the empty data is accepted (handles legitimate cases like end of school year)
Check the Home Assistant logs for messages like:
ElternPortal returned empty critical data (attempt 1/3)– stale-data protection activeFresh session recovered X critical entries!– auto-recovery succeededAuth recovery failed. Keeping last good data.– using cached data
Each sensor shows the number of entries as its state. Attributes contain metadata only – large text fields (body, content, links) are stripped to stay under the Home Assistant recorder limit of 16KB.
State: 22 (number of exams)
Attributes:
entries:
- date: "11.03.2026"
description: "Schulaufgabe in Deutsch (GEN)"
month: "März"
year: "2026"
- date: "20.03.2026"
description: "Schulaufgabe in Mathematik (GEI)"
month: "März"
year: "2026"
child_name: "MAX"
class_name: "7B"
last_fetch: "2026-03-04T10:28:53.344191"State: 94 (number of letters)
Attributes:
entries:
- number: "#94"
title: "Elternabend am 18. März"
date: "03.03.2026 14:08:24"
acknowledged: false
has_file: true
classes: "Klasse/n: 7B"
- number: "#93"
title: "Terminübersicht Elternabende"
date: "27.02.2026 14:53:35"
acknowledged: true
has_file: true
classes: "Klasse/n: 7B"
unread_count: 3
child_name: "MAX"
class_name: "7B"
last_fetch: "2026-03-04T12:41:17.144487"💡 The
unread_countattribute shows the number of unacknowledged letters. Thebodyandlinkfields are intentionally excluded from attributes to prevent recorder database issues.
State: 4 (number of entries)
Attributes:
entries:
- title: "Nachhilfe-Angebot"
date: "11.03.2025 00:00:00"
archived: false
has_attachment: true
attachment: "Nachhilfe.docx herunterladen"
- title: "Leistungsstandbericht"
date: "12.02.2026 - 12.02.2026"
archived: true
child_name: "MAX"
class_name: "7B"
last_fetch: "2026-03-04T12:41:17.144487"💡 The
contentfield is excluded from Schwarzes Brett attributes. Only metadata (title, date, archived status, attachment info) is stored.
type: markdown
title: 📝 Nächste Schulaufgaben
content: >
{% set exams = state_attr('sensor.gymnasium_musterstadt_max_schulaufgaben', 'entries') %}
{% if exams %}
{% for exam in exams[:5] %}
- **{{ exam.date }}** – {{ exam.description }}
{% endfor %}
{% else %}
Keine Schulaufgaben gefunden.
{% endif %}type: markdown
title: ✉️ Ungelesene Elternbriefe
content: >
{% set unread = state_attr('sensor.gymnasium_musterstadt_max_elternbriefe', 'unread_count') %}
{% set letters = state_attr('sensor.gymnasium_musterstadt_max_elternbriefe', 'entries') %}
**{{ unread }} ungelesene Briefe**
{% if letters %}
{% for letter in letters if not letter.acknowledged %}
- **{{ letter.title }}** ({{ letter.date }})
{% endfor %}
{% else %}
Alle Elternbriefe gelesen ✅
{% endif %}type: markdown
title: 📊 Offene Umfragen
content: >
{% set surveys = state_attr('sensor.gymnasium_musterstadt_max_umfragen', 'entries') %}
{% if surveys %}
{% for survey in surveys if not survey.voted %}
- **{{ survey.title }}** (bis {{ survey.end_date }})
{% endfor %}
{% else %}
Keine offenen Umfragen 👍
{% endif %}| Problem | Solution |
|---|---|
| Login fails | Verify your credentials at https://[slug].eltern-portal.org |
| No data after setup | Call the elternportal.fetch_data service manually |
| Sensors show 0 | Check HA logs for "empty critical data" messages – may be a session issue |
| CSRF token error | The portal may be temporarily unavailable – retry later |
| Wrong child name | Entity IDs are set at setup – delete & re-add integration to change |
| Recorder 16KB warning | Should not occur – if it does, check for custom parser changes |
| "Keeping last good data" | Normal behavior – integration auto-recovers on next fetch |
logger:
default: info
logs:
custom_components.elternportal: debug- 🛡️ Stale-data protection – sensors keep last good data when portal returns empty results
- 🔁 Auto session recovery – automatic fresh session creation on connection loss / auth expiry
- ⏱️ Connection timeout – 30s total / 10s connect timeout prevents hanging requests
- 🔍 Total failure detection – raises proper error when ALL endpoints fail (vs. partial success)
- 📝 Enhanced logging – fetch service logs entry counts, coordinator logs recovery attempts
- 🐛 Fixed: Sensors dropping to 0 when ElternPortal session expires mid-operation
- 🧒 Child name in setup flow – set name/short code during initial setup for consistent entity IDs
- 👨👩👧👦 Multi-child support – add integration multiple times with different child names
- 📏 Recorder-safe attributes – large text fields stripped to stay under 16KB limit
- 🆕 Unread count –
unread_countattribute on Elternbriefe sensor - 🐛 Fixed: OptionsFlow compatibility with HA 2024.x+
- 🔧 Complete parser rewrite – all parsers rebuilt based on real HTML structure
- 🆕 Umfragen/Surveys sensor – new sensor for polls and surveys
- 🧒 Automatic child detection – name and class auto-detected from portal navigation
- 🏷️ Smart sensor naming –
[school_slug] [child_name] [sensor_name] - ⚙️ Options flow – configure child name via UI
- 🐛 Fixed: Schulaufgaben/Termine sensors showing privacy policy instead of actual data
- 🐛 Fixed: Schulinformationen parser not matching actual HTML structure
- 🐛 Fixed: Schwarzes Brett parser missing active/archived entries
- 🐛 Fixed: Elternbriefe parser not handling paired-row structure
- Initial release
This project is licensed under the MIT License.
- Built for Home Assistant
- Data provided by Eltern-Portal by art soft and more GmbH
- Inspired by DSB_Mobile_Api and ha-deutsche-ferien