Bypassing Global Query Scopes for Admin/Backend Features

๐Ÿ“– 2 minutes read

Global query scopes are great for filtering production data โ€” hiding soft-deleted records, filtering by status, etc. But when you’re building admin tools, those same scopes become obstacles.

Here’s the problem: you’re building a translation editor for your e-commerce dashboard. Your Product model has a global scope that hides discontinued products. But translators need to update ALL products, including discontinued ones, because those translations might be needed for historical orders or future reactivation.

The Pattern: Explicit Scope-Bypassing Methods

Instead of fighting with scopes everywhere, create dedicated methods that explicitly bypass them:

// In your Order model
public function all_items()
{
    return $this->hasMany(OrderItem::class)
        ->withoutGlobalScopes()
        ->get();
}

// Usage in admin controllers
$order = Order::find($id);
$allItems = $order->all_items(); // Gets even soft-deleted items

Compare this to the default relationship:

// Normal relationship - respects global scopes
$order->items; // Hides discontinued/soft-deleted items

// Admin relationship - explicit about bypassing
$order->all_items(); // Shows everything

Why This Matters

This pattern:

  • Makes intent explicit: all_items() signals “I know what I’m doing, show me everything”
  • Isolates scope-bypassing: Only admin code uses these methods; production code uses normal relationships
  • Prevents bugs: No accidental scope bypassing in customer-facing features
  • Self-documenting: Method name explains why scopes are bypassed

When to Use This

  • Admin dashboards that need full data visibility
  • Translation/content management interfaces
  • Data export tools
  • Debugging utilities
  • Bulk operations that shouldn’t skip “hidden” records

Alternative Approaches

You could use withoutGlobalScopes() inline everywhere:

$order->items()->withoutGlobalScopes()->get();

But this is:

  • Verbose and repetitive
  • Easy to forget in some places
  • Harder to grep for “where are we bypassing scopes?”

A named method is cleaner, easier to test, and more maintainable.

Bonus: Selective Scope Bypassing

You can also bypass specific scopes while keeping others:

public function published_items_including_deleted()
{
    return $this->hasMany(OrderItem::class)
        ->withoutGlobalScope(SoftDeletingScope::class)
        ->get();
}

This keeps your “published” scope active but removes soft delete filtering โ€” useful when you want partial bypassing.

Remember: Global scopes exist to protect production users from seeing the wrong data. When you need admin superpowers, make it explicit with dedicated methods. Your future self (and your code reviewers) will thank you.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *