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