Performance auditing
Why Is This Site Slow?
Microscope audits your PHP runtime, database, server, Craft configuration, content schema and Twig templates — then tells you, for every single finding, exactly what to change and how to check it worked.
See It in Action
A score you can track, an area breakdown that says where to look, and findings that carry the fix with them.
Scan #142
4 August 2026, 03:00 · scheduled · SAPI fpm-fcgi · 31 checks, 2 skipped
- Found
- off
- Recommended
- on
- Found
- 128 MB
- Recommended
- ≥ 1.75 GB
- Found
- 4 templates
- Recommended
- eager-loaded
- Found
- devMode: true
- Recommended
- devMode: false
Why it matters
With devMode on, Craft checks every template for changes and recompiles the ones that moved, skips several caches entirely, and writes a verbose log line for every query. On a production site that is the single most expensive setting you can leave switched on — and it exposes stack traces to visitors when something throws.
How to fix it
Set CRAFT_DEV_MODE=false in this environment’s .env, or pin it per-environment in config/general.php:
return GeneralConfig::create()
->devMode(App::env('CRAFT_DEV_MODE') ?? false)
->allowAdminChanges(App::env('CRAFT_ALLOW_ADMIN_CHANGES') ?? false);
Then clear the compiled templates so the next request builds them fresh: php craft clear-caches/compiled-templates.
How to check it worked
Run php craft microscope/scan/run --category=craft from the server, or re-scan from the control panel. This finding should come back as a pass.
shell_exec, so CPU count and load average couldn’t be read. Not counted against the score.A representation of the scan report screen. Figures are illustrative.
Features
A performance audit that ends with instructions, not a list of complaints.
Every Finding Comes With the Fix
Most performance advice for Craft is generic — “enable OPcache”, “use eager loading”. Microscope tells you whether this server has OPcache on, how big it is, whether it’s full, and the exact php.ini lines to change. Every finding carries what was measured, what it should be, why it costs something, the steps to fix it with config snippets, and how to confirm the fix took.
Six Areas, One Report
The PHP runtime, the database, the server, Craft’s own configuration, your content schema and your Twig templates. Twenty-seven checks, each of which can be switched off from Microscope → Checks when it doesn’t apply to how your site is hosted.
It Reads Your Templates
Static analysis of every .twig file for element queries inside loops, relational fields executed per row, unbounded .all(), .all()|length where .count() belongs, transforms generated in loops, query-heavy templates with no {% cache %}, includes without only, and images missing dimensions or loading hints. Eight rules, each reporting the file and the line.
A Score That Can’t Be Padded
Each area starts at 100 and loses the cost of what’s wrong — a critical costs 25, a warning 10, a notice 3 — so adding passing checks can never inflate a score. Checks that couldn’t run are recorded as skipped and excluded entirely: a Postgres site, or a host that blocks shell_exec, isn’t marked down for it.
Never in the Request Path
Microscope is not a profiler and adds nothing to your front end. It runs on demand or on a schedule, writes a report, and gets out of the way. Nothing is injected into a page, nothing is measured per request, and there is no agent to keep running.
A Report You Can Hand Over
Two outputs from the same document: a print-optimised page the browser saves as a PDF with better typography than any library produces, and a real PDF file rendered server-side for emailing, attaching or archiving. Server-side PDF is an optional dependency, so a strict Composer audit policy can never block the install.
Scheduled, and Emailed Only When It Matters
Daily, weekly or monthly, started from cron or piggybacked on control panel requests for hosting without usable cron. The default notification is to send only when something new appears — a report that arrives unchanged every week stops being read.
A Gate for Your Deploy Pipeline
php craft microscope/scan/run --fail-on=critical exits non-zero when something critical is found. A build can refuse to ship the change that turns OPcache off or leaves devMode on.
Your Checks, In the Same Report
Register your own checks through one event. A plugin knows better than Microscope does what “misconfigured” looks like for it, and its findings belong in the same report, scored the same way and printed on the same page.
What It Looks At
Twenty-seven checks in six areas, plus eight template rules. Any of them can be switched off when it doesn’t apply to your hosting.
PHP
6The runtime the site is actually served by.
-
OPcacheEnabled, sized, hit rate, restarts, timestamp validation.
-
PHP versionAgainst the version Craft performs best on, and PHP’s own support schedule.
-
Memory limitAgainst what image transforms, project config and large queries need.
-
Request limitsExecution time,
max_input_varsand upload limits. -
Performance extensionsAPCu, Redis, Imagick, intl and Zstandard.
-
Realpath cacheSize and lifetime — Composer applications lean on it heavily.
Database
7MySQL and MariaDB, measured against your real data.
-
InnoDB buffer poolAgainst the actual size of Craft’s data, plus disk read rate.
-
Database versionAgainst the version Craft is tuned for.
-
Table sizesCraft tables that have grown out of proportion to the content.
-
Temporary tablesHow often sorts and groups spill to disk.
-
Slow query logWhether it’s on, and how many the server has seen.
-
ConnectionsConnection limits, refusals, and the thread and table caches.
-
Query cacheMariaDB’s query cache, which serialises writes behind a global lock.
Server
4The machine and the connection in front of it.
-
System resourcesCPU count, available memory and load per core.
-
Response compressionWhether HTML is gzip- or Brotli-compressed over the wire.
-
HTTP protocolHTTP version and TLS.
-
Disk spaceFree space on the volume holding the site.
Craft
6Craft’s own configuration, read from the running install.
-
Dev modeDev mode, admin changes and template debugging left on in production.
-
CachingCache driver, template caching and session storage.
-
QueueHow background jobs are run, and whether a backlog has built up.
-
Image handlingImage driver, when transforms are generated, and optimisation.
-
Garbage collectionRevision limits, soft-deleted content and whether anything cleans up.
-
Project configIts size, and whether Craft is watching it for changes in production.
Content schema
3The shape of the content, not the content itself.
-
Field layoutsLayouts wide enough to slow the entry editor — or overflow
max_input_varsand silently lose content on save. -
Field usageCustom field count, and fields not used in a single layout.
-
Content volumeHow much content the site holds, and where unbounded queries stop being survivable.
Templates
8Static analysis of every Twig file — eight rules.
-
Queries in loopsAn element query executed once per row.
-
Relations in loopsA relational field resolved per row instead of eager-loaded.
-
Unbounded queries
.all()with nolimit. -
Count via all
.all()|lengthwhere.count()belongs. -
Transforms in loopsImage transforms generated per iteration.
-
Missing cache tagQuery-heavy templates with no
{% cache %}. -
Includes in loops
{% include %}withoutonly, inside a loop. -
Image dimensionsImages missing width, height or a loading hint.
Plugins can register their own checks, which appear in the same report and are scored the same way.
Frequently Asked Questions
Common questions about Microscope.
Nothing. Microscope is free, under the Craft license. There is no paid edition, no trial and no upsell — every check, both report formats, scheduled scans and the console commands are all in the one free plugin.
No, and no. Microscope never sits in the request path: nothing is injected into your pages, nothing is measured per request, and there is no agent left running. A scan runs on demand or on a schedule, writes a report, and stops.
Craft CMS 5.3 or later and PHP 8.2 or later. The database checks need MySQL or MariaDB — on PostgreSQL that whole area records itself as skipped rather than guessing.
Each area starts at 100 and loses the cost of each problem found: a critical costs 25, a warning 10 and a notice 3. The overall score is the mean of the areas that actually ran. Subtracting rather than averaging means adding passing checks can never inflate a score.
They are recorded as skipped, with the reason, and excluded from the score entirely. A shared host that will not report its innodb_buffer_pool_size has not got a small buffer pool — it has an unknown one, and scoring it down would be making something up.
No — you can start a scan from the control panel, and that is actually the more accurate way to run it for the PHP area. See the next question.
Because they are reading different PHP configurations. The CLI SAPI routinely differs from the one serving the site — different memory_limit, different max_execution_time, and very often no OPcache at all. Microscope records which SAPI each scan came from and the PHP findings say so, but for the PHP area specifically a scan started from the control panel describes the site more accurately.
Only for the server-rendered PDF, and it is deliberately optional. Every published dompdf release carries a security advisory, and Composer’s audit policy blocks flagged packages by default — requiring it would make Microscope uninstallable on any site with that policy on. Without dompdf everything still works and the PDF route redirects to the printable page. Both dompdf 2.x and 3.x are supported, so a site that already has it via Commerce, Formie or Freeform needs no extra install.
It reads your .twig files as text and applies eight structural rules — it never executes a template or renders a page. Containment is tracked by byte offset rather than by line, so a one-line {% for %} with a query inside it is caught like any other. Findings name the template and the line.
Yes. php craft microscope/scan/run --fail-on=critical exits non-zero when a critical finding is present, so a build can refuse to ship the change that turns OPcache off or leaves devMode on. --fail-on=warning is stricter still.
Two ways, chosen in settings. Cron is recommended: run php craft microscope/scan/scheduled hourly and it starts a scan only when one is due. For hosting without usable cron, control panel requests can trigger it — Microscope checks after the response has been sent, so nobody waits for it, and pushes the scan onto the queue behind a lock so concurrent requests cannot start several.
Not by default. The default is to notify only when something is new or has got worse, because a report that arrives unchanged every week stops being read. You can switch it to every scan, or to any scan with problems, and optionally attach the PDF.
No. The schema checks count fields, layouts and totals; they never read field values. The template rules read template source, not rendered output. Nothing leaves your server — there is no external service involved at any point.
Yes, through Checks::EVENT_REGISTER_CHECKS. A check describes itself, says whether it can run in this environment, and returns findings; BaseCheck gives you critical(), warning(), notice() and pass(). Your findings are scored, reported and printed exactly like the built-in ones. See Extending.
Put your site on the slide
One command, and you know what to fix first.
Free, under the Craft license · Craft 5.3+ · PHP 8.2+