Server Logic ist am 6. April 2026 allgemein verfügbar geworden. Microsoft beschreibt die Laufzeitumgebung als natives JavaScript nach dem Standard ECMAScript 2023.
Wer das liest, zieht den naheliegenden Schluss: Was in Node läuft, läuft auch hier.
Tut es nicht. Und wenn es nicht läuft, bekommen Sie einen HTTP 400 ohne Stacktrace, ohne Zeilennummer und ohne Hinweis. Ich habe im Mai in einem produktiven Projekt mehrere Tage damit verbracht, Code zu schreiben, der nach jeder JavaScript-Konvention sauber war und trotzdem abgewiesen wurde. Das Folgende ist das Ergebnis meiner Bisektion, ergänzt um das, was Microsoft inzwischen dokumentiert, und um die Stellen, an denen beides nicht zusammenpasst.
Zuerst: Lesen Sie die Sperrliste
Bevor Sie irgendetwas debuggen, sollten Sie wissen, dass Microsoft eine Liste von Mustern veröffentlicht, die die Plattform grundsätzlich ablehnt. Die Dokumentation hält fest, dass das System Server-Logic-Skripte prüft und solche zurückweist, die bestimmte unsichere oder eingeschränkte Schlüsselwörter enthalten.
Die Liste lohnt das Auswendiglernen, weil einige Einträge überraschen:
__dirname, __filename, import(, import from, eval(, Function(, setTimeout(, setInterval(, setImmediate(, process.exit, process.kill, child_process, fs., require(, constructor.constructor, this.constructor, arguments.callee, with(, delete, Object.getPrototypeOf, Object.setPrototypeOf, Proxy(, Reflect., Symbol.for, __proto__, prototype, debugger
Drei davon treffen ganz normalen Code.
delete ist als Schlüsselwort gesperrt. Deshalb heißt der DELETE-Handler nicht function delete(), sondern function del(). Im Microsoft-Tutorial steht dazu der Kommentar, dass das Schlüsselwort delete in der Skriptdatei nicht verwendet werden soll. Es bedeutet außerdem: delete obj.einSchluessel fällt weg. Setzen Sie stattdessen obj.einSchluessel = null.
prototype ist als Teilzeichenkette gesperrt. Jede Bibliothek und jeder Helfer, der Foo.prototype.bar anfasst, ist damit erledigt.
setTimeout( und die verwandten Funktionen sind gesperrt. Es gibt kein Mittel, um zu warten. Jedes Retry-Muster mit Backoff, das Sie je geschrieben haben, ist hier unmöglich, und Microsoft weist auf diese Konsequenz an keiner Stelle hin.
Ebenfalls dokumentiert: keine Browser-APIs. Die Dokumentation sagt ausdrücklich, dass Server Logic keine browserbasierten APIs oder Bibliotheken unterstützt, als Beispiele werden fetch, XMLHttpRequest und DOM-Funktionen genannt. Für ausgehende Aufrufe nutzen Sie stattdessen Server.Connector.HttpClient.
Diese Liste erklärt einen guten Teil der rätselhaften 400er. Die folgenden vier erklärt sie nicht.
Die vier Grenzen
Alles in diesem Abschnitt habe ich empirisch ermittelt, durch Bisektion, im Mai 2026, auf einer produktiven Site. Nichts davon stand damals in der Sperrliste oder sonst irgendwo auf Microsoft Learn. Behandeln Sie es als Feldbeobachtung, nicht als Dokumentation, und prüfen Sie es in Ihrer Umgebung nach, bevor Sie darauf aufbauen — Grenze 3 hat sich inzwischen nachweislich geändert, dazu unten mehr. Grenze 1 und 2 habe ich am 3. September 2026 live nachgetestet, beide reproduzieren sich exakt, derselbe HTTP 400, dieselbe Fehlermeldung "Exception occurred while processing this request".
| Muster | Ergebnis |
|---|---|
const auf Modulebene mit Function Expression |
HTTP 400 |
| Array-Methoden mit anonymem Callback (map, filter, sort, forEach) | HTTP 400 |
| Server.Logger.Warn(...) und Server.Logger.Error(...) | HTTP 400 im Mai 2026, im September 2026 als behoben bestätigt |
| Rückgabewert von Server.Connector.Dataverse.* | String statt Objekt |
Grenze 1: Function Expressions in einer const
// Wird abgewiesen
const helper = function (x) { return x * 2; };
function get() { return JSON.stringify({ result: helper(5) }); }
// Läuft
function get() {
function helper(x) { return x * 2; }
return JSON.stringify({ result: helper(5) });
}
Eine Function Declaration innerhalb von get() läuft. Eine Function Expression in einer const läuft nicht, egal wo Sie sie hinschreiben.
Der praktische Preis ist spürbar: Hilfsfunktionen müssen in get() und put() doppelt stehen. Das ist unschön. Es ist auch die einzige Form, die bei mir zuverlässig lief.
Grenze 2: Anonyme Callbacks an Array-Methoden
// Alle drei werden abgewiesen
const doubled = arr.map(function (x) { return x * 2; });
const filtered = arr.filter(function (x) { return x > 0; });
const sorted = arr.slice().sort(function (a, b) { return a - b; });
forEach ebenfalls. Damit fällt das Standardwerkzeug weg.
Der Ausweg sind klassische Schleifen, und weil auch sort(callback) betroffen ist, heißt sortieren: von Hand sortieren.
function get() {
const items = [];
for (let i = 0; i < records.length; i++) {
if (!records[i].isActive) continue;
items.push({ id: records[i].id, order: records[i].sortOrder });
}
for (let i = 0; i < items.length; i++) {
for (let j = i + 1; j < items.length; j++) {
if (items[i].order > items[j].order) {
const tmp = items[i]; items[i] = items[j]; items[j] = tmp;
}
}
}
return JSON.stringify({ items: items });
}
Warum? Meine Arbeitshypothese, und mehr ist es nicht, lautet: Die Sandbox behandelt anonyme Function Expressions, die als Argument übergeben werden, als nicht vertrauenswürdigen Code. Das passt zum Muster aus Grenze 1 und zum Geist der Sperrliste, die voller Einträge zu Reflexion und dynamischer Auswertung ist. Microsoft hat dazu nichts veröffentlicht.
Grenze 3: Logger.Warn und Logger.Error — inzwischen behoben
Server.Logger.Log(...) lief im Mai 2026 anstandslos. Server.Logger.Warn(...) und Server.Logger.Error(...) lieferten damals beide 400.
Der Ausweg damals war, die Schwere in die Nachricht zu ziehen:
Server.Logger.Log("WARN: Dataverse liefert keinen Erfolgsstatus");
Server.Logger.Log("ERROR: Update fehlgeschlagen: " + err.message);
Update vom 3. September 2026: Ich habe alle drei Methoden erneut getestet, live gegen einen frisch angelegten Server-Logic-Endpunkt im Demo-Portal, mit genau diesem Code:
function get() {
Server.Logger.Log("log-ok");
Server.Logger.Warn("warn-ok");
Server.Logger.Error("error-ok");
return JSON.stringify({ status: "success" });
}
Ergebnis: HTTP 200, alle drei Aufrufe liefen ohne Crash durch. Die Referenz der Server-Objekte dokumentiert inzwischen auch alle drei mit Codebeispiel. Was im Mai eine reale Grenze war, ist heute ein gelöstes Problem — testen Sie es trotzdem einmal selbst in Ihrer Umgebung, bevor Sie sich darauf verlassen, falls Sie auf einer älteren Site-Version unterwegs sind. Das Umgehungsmuster oben schadet in keinem Fall.
Mir hat niemand gesagt, dass das zwischen Mai und September behoben wurde. Ich weiß es nur, weil ich für diesen Beitrag zufällig nochmal nachgetestet habe. Genau diese Lücke, dass sich die Plattform still ändert, während Sie mit Ausliefern beschäftigt sind, schließt Care: ein monatlicher Abgleich gegen das, was Microsoft tatsächlich ausgeliefert hat, statt der Hoffnung, dass Sie es bemerken.
Eine verwandte Lücke ist erwähnenswert: Die Logger-Dokumentation verweist auf die DevTools-Erweiterung als Ort, an dem Sie Meldungen sehen. Dieser Artikel erwähnt Server Logic mit keinem Wort. Wo Ihre Logausgabe tatsächlich landet, ist Stand heute nicht dokumentiert.
Grenze 4: Der Dataverse-Konnektor liefert einen String
Die Dokumentation zeigt eine Antwortstruktur:
{
"StatusCode": 204,
"Body": "",
"IsSuccessStatusCode": true,
"ReasonPhrase": "No Content",
"ServerError": false,
"ServerErrorMessage": null,
"Headers": { }
}
Was sie im Fließtext nirgends sagt, ist der Rückgabetyp. In der Praxis bekommen Sie diese Struktur als serialisierten String, und das Feld Body darin ist ebenfalls ein String. Ein einziger Lesevorgang braucht also zwei Parse-Schritte:
function get() {
function parseConnector(response) {
if (typeof response === "string") {
try { return JSON.parse(response); } catch (e) { return null; }
}
return response;
}
const raw = Server.Connector.Dataverse.RetrieveMultipleRecords("contacts", "$top=10");
const wrapper = parseConnector(raw);
if (!wrapper || !wrapper.IsSuccessStatusCode) {
Server.Logger.Log("ERROR: Konnektor-Status " + (wrapper && wrapper.StatusCode));
return JSON.stringify({ items: [] });
}
const data = JSON.parse(wrapper.Body);
return JSON.stringify({ items: data.value });
}
Microsofts eigenes clientseitiges Beispiel macht denselben doppelten Parse, erst JSON.parse(res.data), dann JSON.parse(p.Body). Näher an eine Bestätigung kommen Sie nicht.
Zwei kleinere Fallen in derselben Ecke. Der Konnektor erwartet den EntitySetName, nicht den Tabellennamen: accounts, nicht account. Und die Antworthülle auf Clientseite ist mit großem Data dokumentiert, während der Beispielcode res.data liest. Sichern Sie sich gegen beides ab: const payload = res.data || res.Data;
Wie Sie eine Engine debuggen, die nicht mit Ihnen spricht
Für Server Logic gibt es keine Fehlerdokumentation. Keine Statuscode-Tabelle, keine Fehlercodeliste, keinen Troubleshooting-Artikel. Die Zeichenfolge "Exception occurred while processing this request" taucht auf Microsoft Learn nirgends auf. Sie sind allein, also bauen Sie sich Ihre eigene Instrumentierung.
Bisektion. Fangen Sie mit einem Endpunkt an, der eine Konstante zurückgibt. Ergänzen Sie ein Konstrukt nach dem anderen. In dem Moment, in dem der 400 erscheint, ist die letzte Ergänzung der Verursacher. Mühsam, und gegen einen stillen Validator die einzige Methode, die funktioniert.
Setzen Sie einen Marker. Schreiben Sie in jede Antwort eine Versionsmarke. Sonst debuggen Sie wegen Caching und Deployment-Timing irgendwann Code, der gar nicht läuft.
return JSON.stringify({ marker: "v7", items: items });
Nutzen Sie das Stages-Muster. Kapseln Sie jeden Schritt in ein eigenes try/catch und sammeln Sie die Ergebnisse, statt einen Fehler den ganzen Endpunkt reißen zu lassen.
function get() {
const stages = {};
try { stages.user = Server.User && Server.User.fullname; }
catch (e) { stages.user_err = e.message; }
try {
const r = Server.Connector.Dataverse.RetrieveMultipleRecords("contacts", "$top=1");
stages.connectorType = typeof r;
stages.connectorFirst = String(r).substring(0, 200);
} catch (e) { stages.connector_err = e.message; }
return JSON.stringify({ marker: "diag-v1", stages: stages });
}
Das ist die wertvollste Angewohnheit im Umgang mit einer undurchsichtigen Laufzeitumgebung. Sie verwandelt einen binären 400 in einen lesbaren Bericht.
Prüfen Sie Typen. Geben Sie typeof, Object.keys() und die ersten 200 Zeichen von allem aus, was die Plattform Ihnen reicht. Genau so ist der String-Rückgabewert aus Grenze 4 aufgefallen.
Achtung: Eine Sache noch, bevor Sie den Fehler bei sich suchen: Es gibt eine Governance-Einstellung auf Tenant-Ebene, die ausgehende HTTP-Aufrufe aus Server Logic vollständig blockiert. Ist sie aktiv, liefert jedes Skript, das einen ausgehenden Aufruf versucht, laut Dokumentation eine HTTP-403-Antwort statt der Ausführung, sofort und ohne erneutes Deployment. Wenn Ihre externen Aufrufe über Nacht ausgefallen sind und sich an Ihrem Code nichts geändert hat: Prüfen Sie diese Einstellung, bevor Sie mit der Bisektion anfangen.
Was bleibt
Server Logic ist ein wirklich nützlicher Zugewinn. JavaScript serverseitig auszuführen, vor dem Browser verborgen, abgesichert über Web Roles und Tabellenberechtigungen, schließt eine echte Lücke in Power Pages. Ich nutze es.
Aber "ECMAScript 2023 konform" beschreibt die Sprache, es verspricht nichts über die Sandbox. Die Sperrliste ist dokumentiert, und Sie sollten sie vor der ersten Codezeile gelesen haben. Jenseits dieser Liste gibt es eine zweite Verhaltensschicht, die gar nicht dokumentiert ist, und die einzige Rückmeldung der Laufzeitumgebung dazu ist ein 400 mit leerem Body.
Schreiben Sie defensiv. Instrumentieren Sie ab dem ersten Commit. Bisezieren Sie, wenn es still wird. Und prüfen Sie die undokumentierten Grenzen regelmäßig nach, denn eine so junge Plattform bewegt sich.
Sie kämpfen mit einer Laufzeitumgebung, die sich nicht erklärt?
Weekly Coaching ist mein Festpreisformat für genau diese Arbeit: eine feste Sitzung pro Woche, in Ihrer Umgebung, an Ihrem Code. Wir debuggen gemeinsam, und Ihr Team behält die Methode, nicht nur die Lösung.
Weekly Coaching, Festpreis, ohne ÜberraschungenWeitere Beiträge
Das Render-Mutate-Refresh-Muster
Liquid und Server Logic kombiniert zu einem Muster für datengebundene, benutzerspezifische Komponenten in Power Pages.
Beitrag lesen → DevelopmentLiquid FetchXML vs. Web API in Power Pages
Ein Entscheidungsbaum, Performance-Vergleich und Sicherheitsmuster zwischen den beiden Datenzugriffswegen in Power Pages.
Beitrag lesen →Quellen
- Server logic overview, Formulierung zu ECMAScript 2023, unterstützte HTTP-Methoden, Site Settings und Timeout
- Create and manage server logic, Sperrliste, Einschränkungen, CSRF-Token, Antworthülle
- Referenz der Server-Objekte, Server.Logger, Server.Connector.Dataverse, Server.User, Server.Context
- Tutorial zu Dataverse-Operationen, Kommentar zu del() und Beispiel mit doppeltem Parse
- Server Logic debuggen, gemockte Objekte im lokalen Debugger
- Externe Serviceaufrufe deaktivieren, Governance-Einstellung und HTTP 403
- Release-Plan, Public Preview Oktober 2025 und allgemeine Verfügbarkeit April 2026
- Ankündigung der allgemeinen Verfügbarkeit
- DevTools-Erweiterung, auf die die Logger-Dokumentation verweist