Skip to content

Programmatic Navigation ​

Frontend navigation with data-navigate handles most use cases, but sometimes you need to trigger navigation from your backend controller. After creating a record, processing a form, or handling business logic, you might want to navigate the user to a different page. Hyper's programmatic navigation methods make this straightforward and consistent.

Backend Navigation with hyper() ​

The hyper() response builder provides navigation methods that trigger client-side navigation from the server. Instead of returning HTML fragments, you send a navigation event that tells the browser to fetch a new URL.

Basic Navigation ​

The navigate() method is your primary tool for backend-initiated navigation:

php
public function store()
{
    $contact = Contact::create(signals()->validate([
        'name' => 'required|string',
        'email' => 'required|email',
    ]));

    return hyper()->navigate(route('contacts.index'));
}

After creating the contact, this navigates the user to the contacts index. The browser updates the URL, fetches the new page content, and updates the DOM—all without a full page reload.

Just like frontend navigation, backend navigation supports keys for targeted updates:

php
public function update(Contact $contact)
{
    $contact->update(signals()->validate([
        'name' => 'required|string',
        'email' => 'required|email',
    ]));

    return hyper()->navigate(route('contacts.index'), 'main');
}

The second parameter is the navigation key. Your contacts.index controller can check this key and respond with the appropriate fragment:

php
public function index()
{
    $contacts = Contact::paginate(10);

    if (request()->isHyperNavigate('main')) {
        return hyper()->fragment('contacts.index', 'page', compact('contacts'));
    }

    return view('contacts.index', compact('contacts'));
}

This pattern lets one route handle different navigation contexts without code duplication.

Query Parameter Control ​

Backend navigation provides the same query parameter control as frontend navigation, with dedicated methods for each use case.

The base navigate() method accepts an options array as the third parameter:

php
// Navigate with specific options
return hyper()->navigate(
    route('products.index'),
    'filters',
    ['merge' => true, 'except' => ['page']]
);

Available options:

  • merge - Merge with existing query parameters (default: false)
  • only - Array of parameters to preserve
  • except - Array of parameters to exclude
  • replace - Use replaceState instead of pushState (default: false)

Navigate without preserving any query parameters. This gives you a fresh start:

php
public function store()
{
    $contact = Contact::create(signals()->all());

    return hyper()
        ->navigateClean(route('contacts.index'))
        ->js("showToast('Contact created successfully')");
}

The user lands on /contacts with no query parameters, regardless of what parameters were in the URL before.

Preserve all existing query parameters and merge with new ones:

php
public function update(Contact $contact)
{
    $contact->update(signals()->all());

    return hyper()->navigateMerge(
        route('contacts.index') . '?updated=' . $contact->id
    );
}

If the current URL was /contacts?search=john&page=2, this navigates to /contacts?search=john&page=2&updated=123, preserving the user's search and page context.

Preserve only specific query parameters:

php
public function applyFilter()
{
    $filter = signals('filter');

    return hyper()->navigateOnly(
        route('products.index') . '?filter=' . $filter,
        ['search', 'category']
    );
}

This keeps search and category from the current URL but discards everything else like page or sort. The new filter parameter is added.

Preserve all query parameters except those specified:

php
public function delete(Contact $contact)
{
    $contact->delete();

    // Reset pagination but keep filters
    return hyper()->navigateExcept(
        route('contacts.index'),
        ['page']
    );
}

After deleting a contact, this navigates to the index while preserving all filters and search terms, but resets pagination to page 1 by excluding the page parameter.

Checking Navigation Requests ​

Your controllers can detect when a request is a navigation request and respond appropriately.

isHyperNavigate() ​

Check if the current request is a Hyper navigation request:

php
public function index()
{
    $contacts = Contact::paginate(10);

    if (request()->isHyperNavigate()) {
        // Navigation request: return fragment
        return hyper()->fragment('contacts.index', 'list', compact('contacts'));
    }

    // Direct visit: return full view
    return view('contacts.index', compact('contacts'));
}

This pattern enables progressive enhancement. Navigation requests get lightweight fragments, while direct visits get the full page.

Check for specific navigation keys to provide different responses:

php
public function dashboard()
{
    $stats = $this->getStats();

    if (request()->isHyperNavigate('sidebar')) {
        return hyper()->fragment('dashboard', 'sidebar', compact('stats'));
    }

    if (request()->isHyperNavigate('main')) {
        return hyper()->fragment('dashboard', 'page', compact('stats'));
    }

    return view('dashboard', compact('stats'));
}

Different navigation keys trigger different fragment responses, all from a single route.

Multiple Keys with Conditional Logic ​

Use the whenHyperNavigate() method for cleaner conditional logic:

php
public function index()
{
    $contacts = Contact::paginate(6);

    return hyper()
        ->whenHyperNavigate('selectAll', function ($hyper) use ($contacts) {
            return $hyper->signals(['selectedIds' => $contacts->pluck('id')->toArray()]);
        })
        ->whenHyperNavigate(['pagination', 'filters'], function ($hyper) use ($contacts) {
            return $hyper->fragment('contacts.index', 'list', compact('contacts'));
        }, function ($hyper) use ($contacts) {
            return $hyper
                ->fragment('contacts.index', 'page', compact('contacts'))
                ->web(view('contacts.index', compact('contacts')));
        });
}

This handles multiple navigation scenarios in a readable, chainable way:

  • selectAll key: Update signals only
  • pagination or filters keys: Return list fragment
  • Any other request: Return page fragment with web fallback

Hyper provides two ways to change the user's URL from the backend: navigate() and redirect().

hyper()->navigate() ​

Client-side navigation using AJAX. The page doesn't reload:

php
return hyper()->navigate('/dashboard');

Use when:

  • You want a smooth, SPA-like experience
  • The target URL is within your application
  • You're already in a Hyper request context

hyper()->redirect() ​

Full page redirect using JavaScript window.location. The page fully reloads:

php
return hyper()->redirect('/dashboard');

Use when:

  • You need to redirect outside Hyper's navigation flow
  • You want to force a full page reload
  • You're redirecting after authentication or similar operations
  • You need session flash data to persist

Alternative: Standard Laravel Redirects

You can also use Laravel's standard redirect() helper for HTTP 302 redirects:

php
return redirect('/dashboard')->with('message', 'Success');

This triggers a full HTTP redirect with page reload, not client-side navigation.

Comparison:

  • hyper()->navigate() - AJAX navigation, no page reload (fastest, recommended)
  • hyper()->redirect() - JavaScript redirect with full page reload
  • redirect() - HTTP 302 redirect with full page reload

Combining Navigation with Other Actions ​

Navigation methods are chainable with other hyper() methods, letting you navigate while updating signals, showing notifications, or executing JavaScript:

php
public function store()
{
    $contact = Contact::create(signals()->validate([
        'name' => 'required|string',
        'email' => 'required|email',
    ]));

    return hyper()
        ->signals(['message' => 'Contact created successfully'])
        ->navigateClean(route('contacts.index'))
        ->js("showToast('Success!')");
}

This response:

  1. Updates the message signal
  2. Navigates to the contacts index
  3. Shows a toast notification

All three actions execute in sequence on the client.

Real-World Patterns ​

Post-Creation Navigation ​

After creating a resource, navigate to its list or detail page:

php
public function store()
{
    $product = Product::create(signals()->validate([...]));

    return hyper()
        ->forget(['name', 'price', 'description'])
        ->navigateClean(route('products.show', $product))
        ->js("showToast('Product created')");
}

Post-Update Navigation with Preserved Filters ​

After updating a resource, return to the list while preserving the user's filter context:

php
public function update(Contact $contact)
{
    $contact->update(signals()->all());

    return hyper()
        ->navigateMerge(route('contacts.index'))
        ->js("showToast('Contact updated')");
}

If the user came from a filtered list, they return to the same filtered view.

Post-Deletion Navigation with Pagination Reset ​

After deleting a resource, return to the list but reset pagination:

php
public function destroy(Contact $contact)
{
    $contact->delete();

    return hyper()
        ->navigateExcept(route('contacts.index'), ['page'])
        ->js("showToast('Contact deleted')");
}

This keeps filters but resets to page 1, preventing "page 5 of 3 pages" scenarios.

Conditional Navigation Based on Validation ​

Navigate only if validation passes, otherwise update errors:

php
public function process()
{
    try {
        $data = signals()->validate([...]);

        // Process data
        $this->doSomething($data);

        return hyper()->navigate(route('success'));

    } catch (ValidationException $e) {
        // Validation failed, errors automatically sent
        return hyper()->signals(['errors' => $e->errors()]);
    }
}
  • Basics - Understand frontend navigation with data-navigate and @navigate()
  • Query Parameters - Learn how parameter modifiers work
  • Advanced Patterns - Discover timing modifiers and complex navigation patterns
  • Responses - Explore the full hyper() response builder API