Skip to content

Query String Binding

Sync component properties with URL query parameters for shareable, bookmarkable state.

Overview

Query string binding automatically syncs component properties with URL query parameters. This enables:

  • Shareable URLs: Users can share links with specific filters/search terms
  • Bookmarkable state: Browser back/forward buttons work correctly
  • Deep linking: Direct links to specific views/filters
  • State persistence: Page refresh preserves state from URL

Basic Usage

Simple Binding

php
use Diffyne\Attributes\QueryString;

class PostList extends Component
{
    #[QueryString]
    public string $search = '';
    
    #[QueryString]
    public int $page = 1;
    
    #[QueryString]
    public string $status = 'all';
}

When $search changes to "laptop", the URL becomes:

/posts?search=laptop&page=1&status=all

URL Behavior

  • Property changes → URL updates automatically
  • URL changes → Property updates automatically
  • Page refresh → State restored from URL
  • Browser back/forward → State synced with URL

Parameters

as - Custom Parameter Name

Use a different query parameter name:

php
#[QueryString(as: 'q')]
public string $search = '';

// URL: /posts?q=laptop (instead of ?search=laptop)

keep - Keep Empty Values

By default, empty values are removed from the URL. Use keep: true to preserve them:

php
#[QueryString(keep: true)]
public ?string $filter = null;

// URL: /posts?filter= (even when empty)

history - Browser History

Control whether URL changes push to browser history:

php
#[QueryString(history: false)]
public string $view = 'grid';

// URL updates but doesn't add to history (replaces current entry)

Examples

Search with Pagination

php
class ProductSearch extends Component
{
    #[QueryString]
    public string $q = '';
    
    #[QueryString]
    public int $page = 1;
    
    #[QueryString]
    public ?int $categoryId = null;
    
    public function updated(string $property): void
    {
        if (in_array($property, ['q', 'categoryId'])) {
            $this->page = 1; // Reset to first page
        }
        $this->loadProducts();
    }
}

URL Examples:

  • /products?q=laptop&page=1
  • /products?q=laptop&page=2&categoryId=5
  • /products?categoryId=5&page=1

Filters with Custom Names

php
class PostList extends Component
{
    #[QueryString(as: 'q')]
    public string $search = '';
    
    #[QueryString(as: 'cat')]
    public ?int $categoryId = null;
    
    #[QueryString(as: 'sort')]
    public string $sortBy = 'created_at';
    
    #[QueryString]
    public int $page = 1;
}

URL: /posts?q=laptop&cat=5&sort=title&page=2

Keep Important State

php
class Dashboard extends Component
{
    #[QueryString(keep: true)]
    public string $viewMode = 'grid'; // Always in URL
    
    #[QueryString]
    public ?string $filter = null; // Removed when empty
}

Best Practices

1. Use for Shareable State

php
// ✅ Good - users can share filtered views
#[QueryString]
public string $search = '';

#[QueryString]
public string $status = 'all';

// ❌ Avoid - internal state doesn't need URL
public bool $isExpanded = false;
php
#[QueryString]
public string $search = '';

#[QueryString]
public int $page = 1;

public function updatedSearch(): void
{
    // Reset page when search changes
    $this->page = 1;
    $this->loadResults();
}

3. Use Custom Names for Clean URLs

php
// ✅ Good - shorter, cleaner URL
#[QueryString(as: 'q')]
public string $search = '';

// ⚠️ OK - but longer URL
#[QueryString]
public string $search = '';

4. Combine with Locked Properties

php
#[QueryString]
public string $search = ''; // User can change

#[Locked]
public array $results = []; // Server-controlled

public function updatedSearch(): void
{
    // Server loads results based on search
    $this->loadResults();
}

Advanced Patterns

Conditional Query Parameters

php
class ProductList extends Component
{
    #[QueryString]
    public string $search = '';
    
    #[QueryString]
    public ?int $categoryId = null;
    
    #[QueryString(keep: true)]
    public string $sort = 'name';
    
    public function getQueryString(): array
    {
        $query = [];
        
        if ($this->search) {
            $query['q'] = $this->search;
        }
        
        if ($this->categoryId) {
            $query['cat'] = $this->categoryId;
        }
        
        $query['sort'] = $this->sort;
        
        return $query;
    }
}

Multiple Query String Properties

php
class AdvancedSearch extends Component
{
    #[QueryString]
    public string $q = '';
    
    #[QueryString]
    public ?string $minPrice = null;
    
    #[QueryString]
    public ?string $maxPrice = null;
    
    #[QueryString]
    public array $tags = [];
    
    #[QueryString]
    public int $page = 1;
    
    public function updated(string $property): void
    {
        if ($property !== 'page') {
            $this->page = 1; // Reset page on filter change
        }
        $this->search();
    }
}

URL Structure

Default Behavior

php
#[QueryString]
public string $search = 'laptop';
public int $page = 2;

// URL: ?search=laptop&page=2

Custom Names

php
#[QueryString(as: 'q')]
public string $search = 'laptop';

// URL: ?q=laptop

Empty Values

php
#[QueryString]
public string $search = ''; // Empty

// URL: /posts (search removed)

#[QueryString(keep: true)]
public string $view = ''; // Empty but kept

// URL: /posts?view=

Integration with Forms

Query string properties work seamlessly with forms:

blade
<form>
    <input diff:model="search" placeholder="Search...">
    <select diff:model="status">
        <option value="all">All</option>
        <option value="active">Active</option>
    </select>
</form>

When user types or selects, URL updates automatically.

Browser Navigation

Back/Forward Buttons

Query string binding works with browser navigation:

  1. User filters: /posts?search=laptop&status=active
  2. User navigates away
  3. User clicks back button
  4. Component state restored from URL: $search = 'laptop', $status = 'active'

Users can bookmark or share URLs:

https://example.com/posts?search=laptop&status=active&page=2

When opened, component initializes with:

  • $search = 'laptop'
  • $status = 'active'
  • $page = 2

Common Use Cases

1. Search with Filters

php
class ProductSearch extends Component
{
    #[QueryString]
    public string $q = '';
    
    #[QueryString]
    public ?int $categoryId = null;
    
    #[QueryString]
    public ?float $minPrice = null;
    
    #[QueryString]
    public ?float $maxPrice = null;
    
    #[QueryString]
    public int $page = 1;
}

2. Data Tables

php
class UserTable extends Component
{
    #[QueryString]
    public string $search = '';
    
    #[QueryString]
    public string $sortBy = 'name';
    
    #[QueryString]
    public string $sortDir = 'asc';
    
    #[QueryString]
    public int $page = 1;
    
    #[QueryString]
    public int $perPage = 10;
}

3. Dashboard Views

php
class Dashboard extends Component
{
    #[QueryString(keep: true)]
    public string $view = 'overview';
    
    #[QueryString]
    public ?string $dateRange = null;
    
    #[QueryString]
    public ?int $userId = null;
}

Troubleshooting

URL Not Updating

Problem: Property changes but URL doesn't update.

Solution: Ensure property is marked with #[QueryString]:

php
// ✅ Correct
#[QueryString]
public string $search = '';

// ❌ Wrong - missing attribute
public string $search = '';

State Not Restoring from URL

Problem: Page refresh doesn't restore state.

Solution: Check that mount() doesn't override URL values:

php
public function mount(): void
{
    // ❌ Wrong - overrides URL value
    $this->search = '';
    
    // ✅ Correct - only set default if not in URL
    if (!$this->search) {
        $this->search = '';
    }
}

Empty Values in URL

Problem: Empty values cluttering URL.

Solution: Remove keep: true or use conditional logic:

php
// Remove empty values
#[QueryString] // keep defaults to false
public ?string $filter = null;

Next Steps

Released under the MIT License.