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 ());
});
});
}
}
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
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
Problem: Users receive no synchronized records.Debugging:
Check if syncFilter() method exists and returns valid query
Verify filter conditions match actual data
Test filter query directly: Model::query()->syncFilter()->toSql()
Check authentication state in filter: auth()->check()
Problem: Users receive unauthorized data.Solutions:
Review filter logic for security holes
Add explicit null checks for auth()->user()
Test with different user roles and permissions
Use whereRaw('1 = 0') as a safe default for unauthorized access
Add a policy to your model that checks whether the user can view this resource.
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.