# Difference Between SiteAdminController and TenantAdminController in Property Web Builder

> Understand the difference between SiteAdminController and TenantAdminController in Property Web Builder. Learn how controllers scope actions to single tenants or operate across all tenants.

- Repository: [Ed Tee/property_web_builder](https://github.com/etewiah/property_web_builder)
- Tags: deep-dive
- Published: 2026-03-01

---

**The `SiteAdminController` scopes all actions to a single tenant website using subdomain-based isolation and subscription checks, while the `TenantAdminController` operates across all tenants without scoping, restricted only to a whitelist of super-admin emails.**

The Property Web Builder repository (`etewiah/property_web_builder`) implements a multi-tenant architecture that separates day-to-day website management from platform-wide administration. Understanding the difference between site_admin and tenant_admin controllers is essential for developers extending the admin interface or debugging authorization issues in this Rails application.

## Tenant Scoping and Data Isolation

The fundamental architectural distinction between these base controllers lies in how they handle multi-tenant data access and isolation boundaries.

### SiteAdminController: Automatic Tenant Scoping

In [`app/controllers/site_admin_controller.rb`](https://github.com/etewiah/property_web_builder/blob/main/app/controllers/site_admin_controller.rb), the controller includes the `SubdomainTenant` concern, which invokes `set_tenant_from_subdomain` to automatically set `ActsAsTenant.current_tenant` based on the request's subdomain. This mechanism ensures that all `PwbTenant::` model queries automatically include a `WHERE website_id = current_website.id` clause, preventing cross-tenant data leakage.

- **Single-tenant queries**: When inheriting from `SiteAdminController`, calling `Pwb::Page.all` returns only records belonging to the current website.
- **Security boundary**: A compromised admin account can only affect the specific website identified by the subdomain, not the entire platform.

### TenantAdminController: Unscoped Cross-Tenant Access

Conversely, [`app/controllers/tenant_admin_controller.rb`](https://github.com/etewiah/property_web_builder/blob/main/app/controllers/tenant_admin_controller.rb) deliberately excludes any tenant-scoping concerns to enable platform-wide administration. The controller provides an `unscoped_model` helper (defined in lines 37-41) that removes the default tenant filter, allowing queries across all websites.

```ruby

# Querying all tenants from a TenantAdminController subclass

class TenantAdmin::WebsitesController < TenantAdminController
  def index
    @all_websites = unscoped_model(Pwb::Website).order(:created_at)
  end
end

```

## Authentication and Authorization Models

Each controller implements distinct security models appropriate to their administrative scope.

### Site-Level Admin Verification

`SiteAdminController` uses Devise helpers and requires the logged-in user to be an admin or owner of the current website via the `user_is_admin_for_subdomain?` check. This ensures that only users with explicit permissions for that specific tenant can access its admin panel.

### Super-Admin Email Whitelist

`TenantAdminController` bypasses tenant-specific ownership checks and instead validates users against a whitelist defined in the `TENANT_ADMIN_EMAILS` environment variable. The `tenant_admin_allowed?` method (lines 47-52) checks if the current user's email appears in this privileged list before granting access to cross-tenant functionality.

```ruby

# Authorization check in TenantAdminController

before_action :require_tenant_admin!

private

def require_tenant_admin!
  render_tenant_admin_forbidden unless tenant_admin_allowed?
end

```

## Security Boundaries and Feature Gating

The controllers implement different bypass mechanisms and subscription logic reflecting their operational contexts.

### Environment-Based Auth Bypass

Both controllers support the `AdminAuthBypass` concern, which skips authentication when the `BYPASS_ADMIN_AUTH` environment variable is set. However, `SiteAdminController` additionally includes `DevSubscriptionBypass` for development scenarios, while `TenantAdminController` relies solely on the standard bypass mechanism.

### Subscription Access Control

`SiteAdminController` enforces commercial constraints by calling `check_subscription_access` before actions, ensuring the current website maintains an active subscription before allowing admin functionality. `TenantAdminController` intentionally omits subscription checks, as platform administrators must manage websites regardless of their billing status or plan tier.

## Error Handling and UI Patterns

The controllers handle distinct failure modes appropriate to their user interfaces.

### Record Not Found Handling

`SiteAdminController` rescues `ActiveRecord::RecordNotFound` exceptions to display user-friendly "record not found" pages within the single-tenant context, preserving the website's branded admin experience.

### Parameter Missing and Logging

`TenantAdminController` specifically handles `ActionController::ParameterMissing` via the `handle_parameter_missing` method (lines 70-109). This provides detailed logging and returns structured JSON or HTML error responses, which is critical when managing forms across hundreds of potential tenant configurations.

## Layout and Interface Differences

Each controller renders a distinct administrative interface:

- **`SiteAdminController`**: Uses `layout 'site_admin'` for the single-tenant management dashboard tailored to individual website owners.
- **`TenantAdminController`**: Uses `layout 'tenant_admin'` for the cross-tenant super-admin interface designed for platform operators.

## Practical Implementation Examples

### Accessing Scoped Data in Site Admin

When building features for individual website management, inherit from `SiteAdminController` to automatically scope all queries:

```ruby
class SiteAdmin::PagesController < SiteAdminController
  def index
    @pages = Pwb::Page.all   # Automatically scoped to current_website

  end
end

```

### Handling Missing Parameters in Tenant Admin

When processing cross-tenant forms that may have inconsistent parameter structures:

```ruby
class TenantAdmin::ResourcesController < TenantAdminController
  def create
    @resource = Pwb::Resource.new(resource_params)
    if @resource.save
      redirect_to tenant_admin_resources_path, notice: 'Created'
    else
      render :new
    end
  end
  
  private
  
  def resource_params
    params.require(:resource).permit(:name, :content)
  end
end

```

If `params.require(:resource)` raises `ActionController::ParameterMissing`, `TenantAdminController` automatically rescues and logs the error via `handle_parameter_missing`.

## Summary

- **Tenant Scope**: `SiteAdminController` includes `SubdomainTenant` for automatic scoping; `TenantAdminController` uses `unscoped_model` for cross-tenant queries.
- **Authorization**: Site admins verify ownership via `user_is_admin_for_subdomain?`; tenant admins validate against `TENANT_ADMIN_EMAILS`.
- **Subscription Logic**: Only `SiteAdminController` enforces `check_subscription_access` for billing compliance.
- **Data Isolation**: `SiteAdminController` queries automatically filter by `website_id`; `TenantAdminController` requires explicit unscoping.
- **Error Focus**: Site admin handles missing records gracefully; tenant admin handles missing parameters with detailed logging.

## Frequently Asked Questions

### What is the main difference between SiteAdminController and TenantAdminController?

The primary difference is tenant scoping. `SiteAdminController` automatically scopes all database queries to a single website resolved from the request subdomain, while `TenantAdminController` operates without tenant scoping to manage data across all websites in the platform.

### How does tenant scoping work in Property Web Builder?

In [`app/controllers/site_admin_controller.rb`](https://github.com/etewiah/property_web_builder/blob/main/app/controllers/site_admin_controller.rb), the `SubdomainTenant` concern sets `ActsAsTenant.current_tenant` via `set_tenant_from_subdomain`. This activates the `acts_as_tenant` gem's automatic query filtering, ensuring all `Pwb::` model queries include the current website's ID.

### Who can access the TenantAdminController interface?

Only users whose email addresses appear in the `TENANT_ADMIN_EMAILS` environment variable can access `TenantAdminController` and its subclasses. This whitelist approach restricts cross-tenant administrative powers to a small set of trusted platform operators.

### Why does TenantAdminController bypass subscription checks?

Tenant administrators manage the platform infrastructure itself, including websites with expired or inactive subscriptions. Requiring an active subscription for these super-admin actions would prevent necessary support and billing management, so `TenantAdminController` intentionally omits the `check_subscription_access` logic found in `SiteAdminController`.