Technical Deep Dive: How We Enforce SaaS Limits in a WordPress Multisite Platform (Without Slowing Anything Down)
If you’re building a real SaaS on top of WordPress Multisite, you eventually hit the same wall everyone hits: How do you enforce plan limits reliably without breaking performance, risking data drift, or making the UX feel like a hack? Most people duct-tape together a handful of actions, throw a few if checks into functions.php,

If you’re building a real SaaS on top of WordPress Multisite, you eventually hit the same wall everyone hits:
How do you enforce plan limits reliably without breaking performance, risking data drift, or making the UX feel like a hack?
Most people duct-tape together a handful of actions, throw a few if checks into functions.php, and hope for the best.
We don’t do “hope for the best.”
At SampleHQ — a multi-tenant SaaS built fully on WordPress Multisite — we built a proper, predictable, enforceable limit system:
storage quotas, user limits, sample limits, image optimization, and bulk import throttling.
Here’s the full breakdown of how it works under the hood.
1. The Source of Truth: plan-restrictions.php
Every rule, every limit, every cap lives in one place:
/functions/plan-restrictions.php
It’s the brain of the entire system.
Key Functions
sf_get_site_plan()
Returns'light'or'pro'. Defaults to'light'if someone bypasses setup.sf_get_user_limit()/sf_get_sample_limit()
Numbers for Light,'unlimited'for Pro.sf_get_storage_limit()
500MB for Light → 5GB for Pro (configurable).
This gives us something every SaaS needs:
One file to modify, test, and reason about
No scattered conditionals
No “magic rules” duplicated across plugins
2. Real-Time Storage Usage (Not a Counter That Drifts)
Most platforms track usage with a database counter.
It works until it doesn’t.
One SFTP delete… one batch operation… one misfired plugin… and the counters drift.
We don’t rely on counters — we scan the tenant’s upload directory in real time:
function sf_get_current_storage_usage() {
$upload_dir = wp_upload_dir();
$upload_path = $upload_dir['basedir'];
$total_size = 0;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($upload_path, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$total_size += $file->getSize();
}
}
return $total_size;
}
Why this matters
- Accuracy: If the user deletes something, the quota reflects it immediately.
- No drift: No counters to reset or repair.
- Fast: PHP’s
RecursiveIteratorIteratoris memory-efficient and surprisingly fast.
This is the part where developers usually comment:
“Wait… WordPress can do that?”
Yes. It can. It just usually doesn’t.
3. Automatic Image Optimization (The Secret Weapon)
A single unoptimized 10MB JPEG can eat 2% of a Light plan’s storage.
So every image upload goes through:
sf_optimize_uploaded_image()
We resize and compress immediately after WordPress creates the attachment:
imagejpeg($resized, $file_path, $quality);
Why this matters
- A 5MB upload → 150-200KB after optimization
- Reduces quota usage by up to 96%
- Prevents storage abuse without the user even noticing
- Works across avatars, sample uploads, and imports
It’s invisible, automatic, and arguably one of the most important UX improvements in the platform.
4. Role-Aware Limit Messaging (To Drive Upgrades)
If the Owner hits the limit → they see:
“You’ve reached your plan limit. Upgrade to Pro.”
If a Manager hits the limit → they see:
“You’ve reached your plan limit. Please contact the Owner.”
The function behind it:
sf_get_user_limit_message()
Why this matters
- Owners see upgrade CTAs
- Managers/employees don’t get frustrated
- The right person receives the right message
- Zero confusion, less support, more upgrades
This isn’t UI fluff — it’s revenue design.
5. CSV Import Throttling (The Hardest Problem in Any SaaS)
Bulk imports are the #1 way users accidentally (or intentionally) bypass limits.
So we validate every row during CSV processing:
foreach ($rows as $row_index => $row) {
if ($import_type === 'users' && !sf_can_create_user()) {
$limit_reached = true;
}
if ($limit_reached) {
$results['failed'][] = [
'error' => 'Plan limit reached...',
'reason' => 'limit_reached'
];
break;
}
// process normally
}
Why this matters
- No 10,000-row CSV loopholes
- No race conditions
- Limits apply exactly the same way as manual creation
- Predictable, testable behavior
This is the difference between “a plugin” and a platform.
Final Thoughts
WordPress Multisite gives you an incredible foundation for building a SaaS — but only if you treat it like an engineering platform, not a blog CMS.
Limit enforcement is one of those areas where most SaaS products built on WordPress fall apart:
drift-prone counters
inconsistent rules
no messaging hierarchy
unoptimized uploads
loopholes through imports
SampleHQ solves this with:
- centralized plan logic
- real-time storage scanning
- automatic image optimization
- role-aware upgrade nudges
- bulletproof import throttling
If you want WordPress to feel like a real SaaS, this is the kind of engineering discipline that makes it possible.
Bojan