Sync Filters

Sync filters determine which records from your models are synchronized to each user. Proper filtering is essential for security, performance, and ensuring users only receive relevant data.

Basic Filtering

User-Owned Records

The most common filter restricts records to those owned by the current user. This is a good place to start when determining what is relevant to sync:
class Message extends Model
{
    use Harmonics;
    
    protected $syncFields = ['id', 'body', 'user_id', 'created_at'];
    
    protected function syncFilter(): Builder
    {
        return $this->where('user_id', auth()->id());
    }
}

Public Records Only

For models with public/private visibility:
class Post extends Model
{
    use Harmonics;
    
    protected $syncFields = ['id', 'title', 'content', 'status', 'created_at'];
    
    protected function syncFilter(): Builder
    {
        return $this->where('status', 'published');
    }
}

No Filter (All Records)

If all authenticated users should see all records:
class Category extends Model
{
    use Harmonics;
    
    protected $syncFields = ['id', 'name', 'slug', 'sort_order'];
    
    // No syncFilter method = all records synced
}
Security Risk: Only omit sync filters for truly public data that all authenticated users should access. Always default to restrictive filtering.

Advanced Filtering Patterns

Team-Based Access

Filter records based on team membership:
class Project extends Model
{
    use Harmonics;
    
    protected $syncFields = ['id', 'name', 'description', 'status', 'team_id'];
    
    protected function syncFilter(): Builder
    {
        $user = auth()->user();
        
        // Get user's team IDs
        $teamIds = $user->teams()->pluck('id');
        
        return $this->whereIn('team_id', $teamIds);
    }
    
    public function team()
    {
        return $this->belongsTo(Team::class);
    }
}

Role-Based Filtering

Different records based on user roles:
class Document extends Model
{
    use Harmonics;
    
    protected $syncFields = [
        'id', 'title', 'visibility', 'department_id', 'created_at'
    ];
    
    protected function syncFilter(): Builder
    {
        $user = auth()->user();
        
        $query = $this->query();
        
        if ($user->isAdmin()) {
            // Admins see everything
            return $query;
        }
        
        if ($user->isManager()) {
            // Managers see their department + public documents
            return $query->where(function($q) use ($user) {
                $q->where('department_id', $user->department_id)
                  ->orWhere('visibility', 'public');
            });
        }
        
        // Regular users see only public documents
        return $query->where('visibility', 'public');
    }
}

Multi-Tenant Filtering

Tenant Isolation

Ensure complete data isolation between tenants:
class Order extends Model
{
    use Harmonics;
    
    protected $syncFields = [
        'id', 'order_number', 'status', 'total', 'customer_id', 'created_at'
    ];
    
    protected function syncFilter(): Builder
    {
        $user = auth()->user();
        
        // Strict tenant isolation
        return $this->where('tenant_id', $user->tenant_id);
    }
}

Hierarchical Tenants

For complex tenant hierarchies:
class Invoice extends Model
{
    use Harmonics;
    
    protected $syncFields = ['id', 'invoice_number', 'amount', 'status'];
    
    protected function syncFilter(): Builder
    {
        $user = auth()->user();
        $tenant = $user->tenant;
        
        // Include invoices from current tenant and all child tenants
        $tenantIds = collect([$tenant->id])
            ->concat($tenant->descendants()->pluck('id'));
        
        return $this->whereIn('tenant_id', $tenantIds);
    }
}

Dynamic Filtering

Time-Based Filtering

Filter records based on temporal constraints:
class Event extends Model
{
    use Harmonics;
    
    protected $syncFields = [
        'id', 'name', 'start_time', 'end_time', 'is_public', 'creator_id'
    ];
    
    protected function syncFilter(): Builder
    {
        $user = auth()->user();
        
        return $this->where(function($query) use ($user) {
            // Include user's own events (regardless of time)
            $query->where('creator_id', $user->id)
                  // Include public events (current and future only)
                  ->orWhere(function($q) {
                      $q->where('is_public', true)
                        ->where('end_time', '>=', now());
                  });
        });
    }
}

Performance Optimization

Database Indexes

Ensure your filters use proper database indexes:
// Migration
Schema::table('messages', function (Blueprint $table) {
    // Index for user-based filtering
    $table->index('user_id');
    
    // Composite index for team + status filtering
    $table->index(['team_id', 'status']);
    
    // Index for time-based filtering
    $table->index('created_at');
});

Efficient Query Patterns

Write filters that utilize indexes effectively:
class Task extends Model
{
    use Harmonics;
    
    protected function syncFilter(): Builder
    {
        $user = auth()->user();
        
        // ✅ Good: Uses indexes efficiently
        return $this->where('assignee_id', $user->id)
                    ->where('status', '!=', 'archived')
                    ->where('created_at', '>=', now()->subMonths(3));
        
        // ❌ Avoid: Function calls in WHERE clauses prevent index usage
        // return $this->whereRaw('LOWER(title) LIKE ?', ['%' . strtolower($search) . '%']);
    }
}

Eager Loading Relationships

Prevent N+1 queries in relationship-based filters:
class Comment extends Model
{
    use Harmonics;
    
    protected function syncFilter(): Builder
    {
        return $this->with(['post', 'user']) // Eager load relationships
                    ->whereHas('post', function($query) {
                        $query->where('status', 'published');
                    });
    }
}

Security Considerations

Input Validation

Always validate user inputs used in filters:
class Project extends Model
{
    use Harmonics;
    
    protected function syncFilter(): Builder
    {
        $user = auth()->user();
        
        // ✅ Good: Validate user relationship exists
        if (!$user || !$user->team_id) {
            return $this->whereRaw('1 = 0'); // Return no results
        }
        
        return $this->where('team_id', $user->team_id);
    }
}

Authorization Checks

Combine filters with explicit authorization:
class FinancialReport extends Model
{
    use Harmonics;
    
    protected function syncFilter(): Builder
    {
        $user = auth()->user();
        
        // Explicit permission check
        if (!$user->can('view-financial-reports')) {
            return $this->whereRaw('1 = 0');
        }
        
        // Department-based filtering for authorized users
        return $this->where('department_id', $user->department_id);
    }
}

Troubleshooting

Next Steps


You now understand how to implement secure and efficient sync filters. Continue with React Integration to learn how to use your synchronized data in the frontend.