Sprout uses a structured exception hierarchy to communicate different failure modes. Understanding when and why each exception is thrown helps with debugging and building robust error handling.
Sprout’s exceptions follow several principles:
Specific over generic. Each exception type represents a distinct failure mode. Rather than throwing generic exceptions with different messages, Sprout uses dedicated exception classes. This allows applications to catch specific failures and handle them appropriately.
Factory methods over constructors. Most exceptions use static factory methods (MisconfigurationException::missingConfig())
rather than public constructors. This keeps exception messages consistent and makes the codebase easier to search — you
can find everywhere a specific error is thrown by searching for the factory method name.
Context in messages. Exception messages include relevant context (which tenancy, which resolver, which config key) to aid debugging. The factory methods ensure this context is always present and formatted consistently.
All Sprout exceptions extend SproutException, which extends PHP’s base Exception. This means you can catch all
Sprout exceptions with a single catch block when needed:
try {
// Tenanted operation
} catch (SproutException $e) {
// Handle any Sprout failure
}
More commonly, you’ll catch specific exception types to handle particular failure modes.
Sprout’s exceptions fall into four categories based on when they occur and what they represent.
These indicate problems with how Sprout is configured. They typically surface early — during boot or the first request that exercises the misconfigured code path.
MisconfigurationException covers configuration problems:
When you see this exception, check your multitenancy.php config file. The message tells you exactly which
configuration key is problematic.
CompatibilityException indicates that two components can’t work together. Currently, this covers resolver/override conflicts:
These aren’t bugs — they’re fundamental design constraints. The exception message explains which components conflict and why. See Identity Resolvers for details on these specific conflicts.
These occur during normal operation when something expected isn’t available.
NoTenantFoundException is thrown when a route requires a tenant but none was resolved. This happens when:
Route::tenanted() route without a valid tenant identifierThis is the most common exception in production. It means someone accessed a tenanted URL without proper identification. Applications typically catch this to show a “tenant not found” page or redirect to tenant selection.
TenancyMissingException is thrown when code expects a current tenancy but none is active. This typically indicates code running outside a tenanted context that shouldn’t be — perhaps a queued job or command that forgot to establish tenant context first.
TenantMissingException is thrown when a tenancy exists but has no current tenant. Similar to above, but the tenancy is active — it just doesn’t have a tenant set. This can happen in “possibly tenanted” routes where the tenant is optional.
These relate to tenant-aware models and their relationships.
TenantMismatchException is thrown when attempting to associate a child model with a tenant different from its current owner. This protects data integrity — a child model can’t silently switch tenants.
TenantRelationException covers problems with the tenant relationship on child models:
These exceptions indicate model configuration problems rather than runtime data issues.
ServiceOverrideException covers problems with service overrides:
These typically indicate bugs in custom override implementations rather than configuration problems.
These should surface during development, not production. If they reach production, something is misconfigured in your deployment. Let them bubble up and get logged — they need developer attention.
This is the exception you’ll handle most often. Common strategies:
// In exception handler
public function render($request, Throwable $e)
{
if ($e instanceof NoTenantFoundException) {
return response()->view('errors.tenant-not-found', [], 404);
}
return parent::render($request, $e);
}
Or redirect to tenant selection if your app supports it.
These indicate code running in the wrong context. In commands or jobs, establish tenant context before running tenanted logic:
$tenancy = $sprout->tenancies()->get('tenants');
$tenancy->setTenant($tenant);
// Now tenanted code can run
TenantMismatchException indicates a logic error — your code is trying to move data between tenants. Review the code path that triggered it.
TenantRelationException indicates a model configuration issue. Check that tenant child models define exactly one tenant relationship.