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:
public function store()
{
$contact = Contact::create(signals()->validate([
'name' => 'required|string',
'email' => 'required|email',
]));
return hyper()->navigate(route('contacts.index'));
}2
3
4
5
6
7
8
9
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.
Navigation with Keys
Just like frontend navigation, backend navigation supports keys for targeted updates:
public function update(Contact $contact)
{
$contact->update(signals()->validate([
'name' => 'required|string',
'email' => 'required|email',
]));
return hyper()->navigate(route('contacts.index'), 'main');
}2
3
4
5
6
7
8
9
The second parameter is the navigation key. Your contacts.index controller can check this key and respond with the appropriate fragment:
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'));
}2
3
4
5
6
7
8
9
10
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.
navigate() with Options
The base navigate() method accepts an options array as the third parameter:
// Navigate with specific options
return hyper()->navigate(
route('products.index'),
'filters',
['merge' => true, 'except' => ['page']]
);2
3
4
5
6
Available options:
merge- Merge with existing query parameters (default: false)only- Array of parameters to preserveexcept- Array of parameters to excludereplace- Use replaceState instead of pushState (default: false)
navigateClean()
Navigate without preserving any query parameters. This gives you a fresh start:
public function store()
{
$contact = Contact::create(signals()->all());
return hyper()
->navigateClean(route('contacts.index'))
->js("showToast('Contact created successfully')");
}2
3
4
5
6
7
8
The user lands on /contacts with no query parameters, regardless of what parameters were in the URL before.
navigateMerge()
Preserve all existing query parameters and merge with new ones:
public function update(Contact $contact)
{
$contact->update(signals()->all());
return hyper()->navigateMerge(
route('contacts.index') . '?updated=' . $contact->id
);
}2
3
4
5
6
7
8
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.
navigateOnly()
Preserve only specific query parameters:
public function applyFilter()
{
$filter = signals('filter');
return hyper()->navigateOnly(
route('products.index') . '?filter=' . $filter,
['search', 'category']
);
}2
3
4
5
6
7
8
9
This keeps search and category from the current URL but discards everything else like page or sort. The new filter parameter is added.
navigateExcept()
Preserve all query parameters except those specified:
public function delete(Contact $contact)
{
$contact->delete();
// Reset pagination but keep filters
return hyper()->navigateExcept(
route('contacts.index'),
['page']
);
}2
3
4
5
6
7
8
9
10
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:
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'));
}2
3
4
5
6
7
8
9
10
11
12
This pattern enables progressive enhancement. Navigation requests get lightweight fragments, while direct visits get the full page.
Navigation Key Detection
Check for specific navigation keys to provide different responses:
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'));
}2
3
4
5
6
7
8
9
10
11
12
13
14
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:
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')));
});
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
This handles multiple navigation scenarios in a readable, chainable way:
selectAllkey: Update signals onlypaginationorfilterskeys: Return list fragment- Any other request: Return page fragment with web fallback
Navigation vs Redirect
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:
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:
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:
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 reloadredirect()- 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:
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!')");
}2
3
4
5
6
7
8
9
10
11
12
This response:
- Updates the
messagesignal - Navigates to the contacts index
- 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:
public function store()
{
$product = Product::create(signals()->validate([...]));
return hyper()
->forget(['name', 'price', 'description'])
->navigateClean(route('products.show', $product))
->js("showToast('Product created')");
}2
3
4
5
6
7
8
9
Post-Update Navigation with Preserved Filters
After updating a resource, return to the list while preserving the user's filter context:
public function update(Contact $contact)
{
$contact->update(signals()->all());
return hyper()
->navigateMerge(route('contacts.index'))
->js("showToast('Contact updated')");
}2
3
4
5
6
7
8
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:
public function destroy(Contact $contact)
{
$contact->delete();
return hyper()
->navigateExcept(route('contacts.index'), ['page'])
->js("showToast('Contact deleted')");
}2
3
4
5
6
7
8
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:
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()]);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
Related Topics
- Basics - Understand frontend navigation with
data-navigateand@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

