HTML-Berichte

Zuletzt aktualisiert 25 May 2026

Eine Flexie-HTML-Berichtsvorlage, die als eigene KPI-Dashboard-Seite mit Karten und einem Diagramm gerendert wird

Wenn Sie HTML als Ausgabeformat wählen, ist der Körper des Berichts keine SQL-Abfrage. Es ist eine Vorlage, die Sie selbst schreiben: in HTML, mit CSS für die Gestaltung, JavaScript für Interaktivität und Flexie Scripting-Token, um Live-Daten zu ziehen und zu formen. Was immer Sie schreiben, ist das, was der Bericht anzeigt.

Beginnen Sie unter Berichte (/reports), klicken Sie auf Neu und setzen Sie das Ausgabeformat auf HTML.

Wann Sie es einsetzen

HTML-Berichte sind das richtige Werkzeug für alles, was das Datengitter nicht darstellen kann:

  • Ein Dashboard auf einer Seite mit KPI-Karten, Abschnitten in Reitern und bedingter Formatierung.
  • Ein Board im Kanban-Stil, gerendert aus einer Liste von Datensätzen.
  • Ein druckfähiges Dokument (Kontoauszug, Zusammenfassung, Profil), genau so gestaltet, wie Sie es möchten.
  • Eine Mini-App, die die gespeicherten Daten-Lookups aufruft, die Ergebnisse formatiert und ihre eigenen Visualisierungen zeichnet.

In Kombination mit Dashboard-Widgets machen HTML-Berichte fast jedes vorstellbare Widget möglich: Sie binden den HTML-Bericht als Widget ein, und das Dashboard rendert Ihr eigenes Layout.

VORLAGE <style> .kpi { ... } </style> <div class="kpi-row"> {{ findCount("deal", "is_won", 1) }} </div> {% set rows = query(...) %} <table> {% for r in rows %} <tr>...</tr> {% endfor %} </table> Team-Überblick 432 GEWONNENE DEALS 18 OFFENE TICKETS 7 ÜBERFÄLLIG Top-Abschlüsse John D. €48k Jon D. €36k Jane D. €24k Letzte Aktivität

Die Berichtseinstellungen

Dieselben wie bei Datengitter-Berichten, mit folgenden Ausnahmen:

  • Ausgabeformat ist auf HTML gesetzt.
  • Abfrage wird durch Vorlage ersetzt, den HTML-Körper des Berichts. Für das Vorlagenfeld selbst gibt es keine Zeilenobergrenze; die Rendergeschwindigkeit hängt von den darin enthaltenen Daten-Lookups ab (die sehr wohl begrenzt sind, siehe unten).
  • Filter können weiterhin definiert werden, aber wie sie angewendet werden, liegt bei Ihnen (Sie lesen die übermittelten Filterwerte innerhalb Ihrer Vorlage aus).
  • Standard-Sortierspalte und -richtung gelten für HTML-Berichte nicht.

Was Sie schreiben können

Alles, was in einem Browser gerendert wird:

  • HTML für die Struktur.
  • CSS für die Gestaltung, inline, in einem <style>-Tag oder referenziert.
  • JavaScript für Interaktivität, in eingebetteten <script>-Blöcken oder über eine <script src>-Referenz auf eine externe Datei.
  • Flexie Scripting für Daten. Jede Funktion und jeder Filter, die in der Flexie-Scripting-Referenz aufgeführt sind, stehen innerhalb der Vorlage zur Verfügung.

Ein minimales Beispiel:

<style>
  .card { padding: 1rem; border: 1px solid #eee; border-radius: 8px;
          background: #fff; margin-bottom: .75rem; }
  .num  { font-size: 2rem; font-weight: 600; color: #1a73e8; }
  .lbl  { font-size: .85rem; color: #555; text-transform: uppercase; }
</style>

<h2>This week at a glance</h2>

<div class="card">
  <div class="num">{{ findCount("deal", "is_won", 1) }}</div>
  <div class="lbl">Deals won (all-time)</div>
</div>

<div class="card">
  <div class="num">{{ findCount("case", "status", "open") }}</div>
  <div class="lbl">Open cases</div>
</div>

Öffnen Sie den Bericht, und Sie erhalten zwei KPI-Karten, gespeist aus Live-Daten. Dieselbe Idee skaliert bis hin zu einer vollständig eigenen Seite.

Daten ziehen, dann zusammenführen

Da Flexie Scripting innerhalb der Vorlage verfügbar ist, können Sie mehrere Lookups ausführen und sie nach Belieben kombinieren:

{% set winners = query("
  SELECT u.full_name, SUM(d.amount) AS total
  FROM deals d
  JOIN users u ON u.id = d.owner_id
  WHERE d.is_won = 1
  GROUP BY u.id
  ORDER BY total DESC
  LIMIT 10
") %}

<h2>Top closers</h2>
<ol>
  {% for row in winners %}
    <li>
      <strong>{{ row.full_name }}</strong> —
      {{ row.total | number_format(0) }}
    </li>
  {% endfor %}
</ol>

Oder rufen Sie eine Liste von Datensätzen ab und rendern Sie sie als Karten, als Tabelle, als Kanban: ganz wie Sie möchten.

Schleifen, Bedingungen, Formatierung

Alles, was Sie von den Flexie-Scripting-Grundlagen erwarten, funktioniert:

{% set deals = query("
  SELECT id, name, amount, is_won, is_lost
  FROM deals
  WHERE owner_id = {user_id}
  ORDER BY date_modified DESC
  LIMIT 50
") %}

<table>
  <thead><tr><th>Deal</th><th>Amount</th><th>Status</th></tr></thead>
  <tbody>
    {% for d in deals %}
      {% set status = d.is_won ? "Won" : (d.is_lost ? "Lost" : "Open") %}
      {% set colour = d.is_won ? "#0a8a3f" : (d.is_lost ? "#c32c2c" : "#888") %}
      <tr>
        <td>{{ d.name }}</td>
        <td>{{ d.amount | number_format(2) }}</td>
        <td style="color: {{ colour }}">{{ status }}</td>
      </tr>
    {% endfor %}
  </tbody>
</table>

Dieselben Platzhaltervariablen, die in Datengitter-Abfragen funktionieren ({user_id}, {group_id}, {role_id}, {timezone}), funktionieren auch innerhalb von query(...)-Aufrufen in HTML-Berichten.

Was die Vorlage nutzen kann

Das vollständige Flexie-Scripting-Toolkit steht in HTML-Berichten zur Verfügung:

  • Daten-Lookups: findOne, findMany, findCount, findSum, findMin, findMax, findDeals, findCases, findDealsValue, getSmartListRecords, getAccountContacts, getUserById, getOrganization, query (nur lesend, SQL).
  • Statistik-Pakete: getDealStats, getInvoiceStats, getCaseStats.
  • Rechnen und Formatieren: add, subtract, multiply, divide, numberFormat, formatCurrency, round.
  • Datumsfunktionen: date, now, dateAdd, dateSubtract, dateDiff, daysBetween.
  • Text: truncate, htmlToText, startsWith, endsWith, urlEncode, md5, base64encode.
  • Sammlungen: sumField, avgField, minField, maxField, countDistinct, collectionColumn sowie die Filter group_by / sort_by / unique_by / sum_by.
  • JSON: jsonDecode, jsonPath.
  • Visuell: qrCode.

Kurz gesagt: Alles, was ein normaler Flexie-Scripting-Kontext kann, ist verfügbar, einschließlich der Sicherheits-Helfer (signUrl, jwtEncode, hashHmac) für eingebettete Aktionen und Links.

Einschränkungen gelten weiterhin

HTML-Berichte sind mächtig, aber die zugrunde liegenden Daten-Lookups halten sich weiterhin an die Regeln:

  • query(...) ist nur lesend. Es akzeptiert SELECT und WITH; es weist INSERT, UPDATE, DELETE, DROP, TRUNCATE, CREATE und SELECT * zurück.
  • Obergrenze von 1.000 Zeilen bei jedem findMany(...), query(...) und jeder ähnlichen Daten zurückgebenden Funktion. Aggregieren Sie innerhalb des Aufrufs (SUM, COUNT, GROUP BY), wenn Sie eine Zahl über eine größere Grundgesamtheit benötigen.
  • Eingeschränkte Tabellen (Systemeinstellungen, Metadaten von Anhängen, Postfach- oder SMTP-Zugangsdaten) sind für query(...) tabu, dieselbe Liste wie bei Datengitter-Berichten.
  • Nur die dokumentierten Flexie-Scripting-Funktionen, -Filter und -Tags funktionieren. Siehe die Sicherheits-Sandbox.

JavaScript und CSS, was wo läuft

JavaScript und CSS in Ihrer Vorlage laufen im Browser des Betrachters wie jede andere HTML-Seite. Für die gerenderte Ausgabe gibt es kein Sandboxing. Das bedeutet:

  • Skripte können Ihr eigenes Back-End, Drittanbieter-APIs und Browser-APIs aufrufen, begrenzt durch die normalen Browser- bzw. CORS-Regeln.
  • Stile können auf den Bericht beschränkt werden (verwenden Sie eine umschließende Klasse) oder global für die Seite gelten (verwenden Sie sorgfältige Selektoren).
  • Das Skript läuft nachdem Flexie Scripting aufgelöst wurde. {{ }}-Token werden serverseitig aufgelöst, und das JavaScript sieht die endgültigen Werte als wörtliche Zeichenketten oder Zahlen.

Ein gängiges Muster, JSON ausgeben, mit JS rendern

<div id="chart"></div>

<script>
  const data = {{ query("
    SELECT month, sales
    FROM monthly_sales
    WHERE year = 2026
    ORDER BY month
  ") | json_encode | raw }};

  data.forEach(row => {
    const bar = document.createElement("div");
    bar.style.width = row.sales / 1000 + "px";
    bar.textContent = row.month + ": " + row.sales;
    document.getElementById("chart").appendChild(bar);
  });
</script>

Die Kette | json_encode | raw verwandelt das Abfrageergebnis in ein JSON-Literal, das direkt in das Skript eingebettet wird.

Verwenden Sie | json_encode immer dann, wenn Sie einen Wert innerhalb eines <script>-Tags einbetten. Es setzt die Daten in Anführungszeichen und maskiert sie, sodass ein einzelnes Anführungszeichen oder ein Zeilenumbruch in den Daten Ihr JavaScript nicht zerstören oder eine Injektion ermöglichen kann.

Filter in HTML-Berichten

Filter können auf einem HTML-Bericht weiterhin definiert werden, wobei dieselbe JSON-Form verwendet wird, die unter Berichtsfilter (im Detail) behandelt wird. Ihre übermittelten Werte landen in der Sitzung des Nutzers, und Ihre Vorlage liest sie aus, wie Sie möchten.

In der Praxis verwenden HTML-Berichte oft Flexie-Scripting-Token direkt innerhalb ihrer query(...)-Aufrufe (über dieselben Platzhalter, {user_id}, {group_id}) statt der {filters}-Substitution, die Datengitter-Berichte nutzen. Das reichhaltigere Rendering bedeutet, dass Sie Filterwerte auch mit getQueryString("name") aus dem Query-String der URL lesen können.

Sicherheit, worauf Sie achten sollten

Jeder, der einen HTML-Bericht bearbeiten darf, kann beliebiges HTML, CSS und JavaScript in die gerenderte Seite einsetzen. Jeder, der den Bericht ansehen darf, führt diesen Code in seinem Browser aus. Also:

  • Geben Sie das Recht „Berichte bearbeiten" nur vertrauenswürdigen Nutzern. Eine bösartige oder nachlässige Vorlage kann Zugangsdaten aus dem Browser des Betrachters abgreifen, ihn umleiten oder Daten abziehen. Das Rechtemodell steuert dies; halten Sie das Bearbeiten-Recht eng.
  • Behandeln Sie Daten, die in <script>-Blöcke interpoliert werden, mit Vorsicht. Lassen Sie sie immer durch | json_encode laufen (und danach | raw, damit das resultierende JSON nicht erneut maskiert wird). Für Daten, die als sichtbares HTML eingefügt werden, lassen Sie die Standardmaskierung am besten eingeschaltet ({{ value }} ist bereits maskiert); verwenden Sie | raw nur dann, wenn Sie wirklich „das ist HTML" meinen.
  • Behandeln Sie Daten, die in href / src interpoliert werden, mit Vorsicht. Verwenden Sie | url_encode für Query-String-Werte.

Nächste Schritte