Development 4 November 2026 9 min read

32 MB Passes, 4 MB Fails: What the Power Pages Server Logic Memory Limit Actually Does

Power Pages Server Logic has a memory limit that only applies when a periodic check fires. How I measured it, what logging does, and a budget for your data.

In Power Pages Server Logic, I allocated 32 MB of memory and the call succeeded. Three times out of three.

Then I allocated 3.5 MB and let the script run for another 300 milliseconds. It failed with this message:

The V8 runtime has exceeded its memory limit

That isn't a contradiction. It's how the limit behaves, and it explains why these failures look random: one record fails, the next one works, and one call in five breaks for no visible reason.

Here is what I measured, how you can measure it yourself, and what it means for your design.

The limit isn't documented

Microsoft Learn documents a timeout for Server Logic through ServerLogic/TimeoutInSeconds. It doesn't list a memory limit today.

It used to. An earlier version of the Server Logic overview listed ServerLogic/MaxMemoryUsageInBytes with "Maximum memory per function: 10 MB". That row was removed in November 2025 without an explanation.

So I measured it.

How to measure something you can't read

Server Logic has no heap API. You can only find the limit from the outside: allocate memory until the call fails, then narrow it down with a binary search.

The cost of any step is then a simple subtraction: how much extra memory fits without that step, minus how much fits with it.

I got three things wrong before the numbers made sense. You'd probably hit the same three:

  1. "x".repeat(n) allocates almost nothing. The engine keeps a compact reference until you read the string. In a local test, 20 MB of text cost 0.01 MB. Read one character with charCodeAt and it costs the full 20 MB.
  2. Without a pause, you measure the wrong limit. More on that in the next section.
  3. The last log line isn't a reliable signal. Use the HTTP status and the trace of the request.

The probe and the test runner are available for download below.

The check is periodic

This is the finding that matters most. I allocated 8 MB, well above the limit, and let the script keep running for a set time. Each row is ten calls:

Time after allocatingPassedFailed
0 ms100
10 ms100
50 ms100
100 ms37
200 ms010
400 ms010
Why a large allocation passes and a small one fails Two schematic timelines. A very large allocation ends before the first memory check and passes. A small allocation just over the limit is still running when the check lands, and fails. 32 MB, script ends right away: passes usable limit startcheckcheck finishedno check ran 3.5 MB, script keeps running: fails usable limit checkcheckstart memory limit errora check caught it
Schematic, not to scale. The check timing is what I measured: roughly every 100 to 200 ms. Exact amounts are in the tables.

The limit only applies when a check runs, and that seems to happen roughly every 100 to 200 ms. A script that goes over the limit and finishes quickly is never caught.

Without any pause, 32 MB passed three out of three times. At 64 MB, one of three passed. Everything above failed.

I don't know the implementation. The error text matches the wording in the ClearScript project, and an issue there describes exactly this: the heap size is sampled periodically, so a sudden spike between samples can go undetected. That fits what I measured, but that Power Pages uses ClearScript is my inference, not a statement from Microsoft.

What the limit is

So every measurement needs a pause. I used 300 ms. These amounts fit:

What you keep in memoryFitsFails at
Text in blocks of 256 KB3,552 KB3,584 KB
Text in blocks of 64 KB1,664 KB1,696 KB
Text as a single block4,832 KB4,864 KB
Text with non-Latin-1 characters, 256 KB blocks1,504 KB characters1,536 KB characters
Parsed JSON objects832 KB of JSON864 KB of JSON

There are two things to take from this:

  • There's no single number. The same limit allows 1.7 MB or 4.8 MB, depending on how the memory is used.
  • One special character can double a string. The engine stores a string with even one character outside Latin-1 at two bytes per character. A single bullet point in a description is enough.

Parsed objects are the most expensive. Under 1 MB of JSON fills the limit if you keep it as objects.

Where the memory goes in one call

Real code rarely holds ballast. It holds the same data several times.

A Custom API response reaches Server Logic as a string with the actual result nested inside, as strings within strings. I replayed that shape with 266 KB of content. This is the memory in use at each step:

StepMemory in use
Raw response receivedabout 1,310 KB
Outer wrapper parsedabout 1,500 KB
Body parsedabout 2,750 KB
Content parsedover the limit
After returning to the callerabout 3,040 KB
Memory in use at each step of one call with 266 KB of content Bars rise from about 1,310 KB for the raw response to 2,750 KB after parsing the body. Parsing the content goes over the limit of about 3,550 KB. After returning, about 3,040 KB stay in use. Raw response: 1,310 KB 1,310 Rawresponse Wrapper parsed: 1,500 KB 1,500 Wrapperparsed Body parsed: 2,750 KB 2,750 Bodyparsed Content parsed: over limit over limit Contentparsed Returned to caller: 3,040 KB 3,040 Returnedto caller limit
Memory in KB, measured with ballast placed at each step, 266 KB of content. The hatched bar couldn't be measured, because the call already fails there.

266 KB of JSON already reaches the limit. This doesn't mean a 266 KB call always fails. It means a check at the wrong moment would catch it.

That shows when I raise the payload size. The "with pause" column uses five calls with a 300 ms pause, the other two use ten calls each:

ContentWith pauseNo pause, no logNo pause, full log
328 KB5 of 510 of 1010 of 10
435 KB5 of 510 of 109 of 10
528 KB0 of 510 of 107 of 10
639 KB0 of 510 of 104 of 10
856 KB0 of 510 of 100 of 10

The second column is the trap. 856 KB looks completely fine in a quick test, then fails as soon as anything makes the script run longer.

Logging a payload is a timing problem

The right column already shows it. Here is a direct comparison at 483 KB of content, with 80 calls each, in alternation:

VariantFailedMedian response
No logging0546 ms
JSON.stringify(content) only0573 ms
Server.Logger.Log(JSON.stringify(content))22926 ms

Building the string isn't what breaks it. The log call is. It adds about 350 ms while the entire payload stays in memory, so a check is far more likely to land in that window.

There's one more catch. In these failures, the trace is often missing, probably because the log is too large for it. You lose the error message exactly when you need it.

Never log a full payload outside of a short debugging session. And if a failure has no trace, check whether your own log is the reason.

Logging isn't the only cause, though. Without any logging, the larger payloads above still fail as soon as a check lands. Removing logs buys you stability, not capacity.

MaxMemoryUsageInBytes still works

I set the undocumented setting to three values and measured each one:

SettingText fitsParsed JSON fitsRecords in my test catalog
Not set3,520 KB832 KB30
10 MB3,520 KB832 KB30
20 MB9,152 KB2,048 KB62
40 MB20,928 KB4,608 KB102

This table tells me two things:

  • The default still seems to be 10 MB. Not setting it and setting 10 MB give identical results. The old documentation was right about the number.
  • 10 MB doesn't mean 10 MB is available. About a third of it can actually hold your data.

The setting works immediately. I still wouldn't build on it. It's gone from the documentation, so it may change without notice, and I haven't tested concurrent load. Use it as an emergency measure, and ask Microsoft through a support ticket before you rely on it in production.

The real fix is less data

On a real project, the results page used about 13 % of the data it loaded. Most of the payload was attributes that only one detail page displayed. Filtering the related lists in the API shrank one response from over 500 KB to about 20 KB, and the failures on the detail page were gone.

Before you tune anything, check three things:

  • What does the page actually use? List the fields your front end reads, then compare that with the response.
  • How many calls does one page make? That project's filtered search made a second request that loaded the complete catalog.
  • Is your DEV data realistic? A failing page that works on DEV usually has less data, not better code.

The browser isn't your bottleneck. In Chrome, parsed JSON took about 1.3 times its own size in my test, and a browser tab has far more room than 3 MB. The tight limit is on the server.

A budget you can use

With default settings, on my measurements:

  • Up to about 250 KB of JSON per call: You have room, even if a check lands at the worst moment. This one is my estimate, since 266 KB already reached the limit at the peak.
  • Up to about 450 KB: It works, but it depends on timing, and a slow response can push it over.
  • Above about 500 KB: It fails as soon as a check lands.

Measure your own response sizes, and work out how much one record costs you. That number tells you how far your data can grow. Your limits may differ from mine, so run the probe on your own environment.

Try it yourself

Download the probe and the runner:

  • memory-probe.js: Server Logic with ballast, a replay path and checkpoints. Create it as blogtest-memory-probe.
  • memory-probe-runner.console.js: the browser console script that runs the series. Paste it on your portal and read window.probeResults when it's done.

Use a development environment only. The probe deliberately pushes the limit.

I measured this on a trial environment in the US region in November 2026. Numbers on other environments may differ: on a second tenant, the text limit was lower while the object limit was identical. If you run it, I'd like to know what you get.

Sources

Tino Rabe

Tino Rabe

Power Pages Spezialist · Former Microsoft MVP

Power Pages specialist, former Microsoft MVP. I help companies build secure customer portals: architecture workshop, weekly coaching, security audits.

Your team builds. Who checks the foundations?

Weekly Coaching: your developers keep building, I secure architecture, permissions and sequencing in a weekly cadence. About Weekly Coaching

Book a call