Stage helpers
Backstage ships a small set of runtime helpers, exposed through stage(), that give you the current site, content and language inside any request, plus a few Blade directives for conditional rendering. They live in packages/core and are always available — no installation step required.
Looking for the front-end
props()helper instead? See Props helpers.
Request bindings
For every request that resolves to a piece of content, the BindSite middleware (registered automatically on the web group) populates three container bindings:
content— the currentBackstage\Models\Contentinstancesite— the relatedBackstage\Models\Sitelanguage— the related language
The bindings are registered with app()->scoped(...), so they are reset between requests under Octane and other long-lived workers.
stage()
The stage() helper is the main entry point. Without arguments it returns the helpers instance:
stage(); // Backstage\Helpers\Support\Helpers
stage()->site(); // current Site
stage()->content(); // current ContentYou can also resolve a binding directly by name, with optional dot-notation for properties:
stage('site'); // current Site
stage('content.title'); // shorthand for stage()->content()->title
stage('language'); // current LanguageIf the binding is not registered (for example, on a request that did not resolve content), stage('site') returns null. Calling stage()->site() without arguments outside a content response throws an exception.
Looking up a specific record
Both site() and content() accept lookup arguments:
stage()->site('main'); // Site where slug = 'main'
stage()->site($id, 'ulid'); // Site where ulid = $id
stage()->content('about'); // Content where slug = 'about' (current locale)
stage()->content('about', locale: 'nl'); // Content where slug = 'about' and language_code = 'nl'
// Scope a content lookup to a specific site
stage()->content('about', site: 'main'); // by site slug
stage()->content('about', site: $site, siteColumn: 'id'); // by Site instance + columnWhen $slug is null, the call returns the request-bound record (equivalent to app('site') / app('content')).
Menus
stage()->menu() resolves a menu by its slug ($name), scoped to the current site and locale, and returns it as a nested array tree — plain data, not Eloquent models — ready to render in a Blade view:
stage()->menu(); // 'main' menu for the current site + locale
stage()->menu('footer'); // a named menu
stage()->menu('main', $site, 'nl'); // explicit site + localeThe menu is matched on menus.slug = $name, then narrowed to the resolved site_ulid and language_code (both columns live on the menus table). So a main menu can exist per site/locale without the slug having to encode them — and if no menu matches that slug for the current site and locale, the helper returns the empty shape described below.
Each node is an array:
[
'name' => 'Product',
'url' => '#', // resolved link: linked content URL, else the manual URL, else '#'
'target' => null,
'is_action' => false, // target starts with 'action'
'is_primary' => false, // target === 'action-primary'
'children' => [ /* same shape, nested to any depth */ ],
]Items are loaded through the adjacency-list tree, ordered by position. Any linked content is eager-loaded with only the columns needed to build its URL, so resolving a menu is just the slug lookup plus one tree query.
Diverging navigation and actions
By default ($diverge = true) the top-level items are split into navigation links and action buttons — actions being items whose target starts with action. A component can destructure them in one call:
// app/View/Components/Section/Navigation.php
[$this->navigation, $this->actions] = stage()->menu('main');Pass diverge: false to get a single flat collection of root nodes instead — useful for a footer that has no actions:
$this->footerMenu = stage()->menu('footer', diverge: false);When no site can be resolved, the helper returns the matching empty shape ([collect(), collect()] when diverging, otherwise an empty collection), so the destructure never fails.
Rendering then just walks the arrays:
@foreach($navigation as $item)
@if(count($item['children']))
<button type="button">{{ $item['name'] }}</button>
<ul>
@foreach($item['children'] as $child)
<li><a href="{{ $child['url'] }}">{{ $child['name'] }}</a></li>
@endforeach
</ul>
@else
<a href="{{ $item['url'] }}">{{ $item['name'] }}</a>
@endif
@endforeachThe
activestate is deliberately not part of the array — it depends on the current request, so resolve it in the view layer (e.g. anisActive()method on the component).
Field options
stage()->fieldOptions() returns the options array configured on a field, looked up by its model_key and slug. It replaces the brittle chain of fetching the field and reaching into config['options'] by hand:
// Before — explodes if the field is missing or has no options
$statusOptions = Field::where('model_key', 'property')->where('slug', 'status')->first()->config['options'];
// After
$statusOptions = stage()->fieldOptions('property', 'status');The helper never throws. If no field matches the given model_key + slug, or the matched field has no options configured, it reports the problem through Laravel's report() handler (a RuntimeException, so you get a full stack trace in your logs) and returns an empty array:
stage()->fieldOptions('property', 'status'); // ['draft' => 'Draft', 'published' => 'Published', ...]
stage()->fieldOptions('property', 'missing'); // [] — exception reported, request continuesBecause the fallback is always [], it is safe to feed straight into a Blade loop or a select without guarding the call.
Blade directives
@site
Render a block only when the current site has a given slug:
@site('main')
<p>Welcome to the main site.</p>
@endsite@content
Render a block only when the current content has a given slug:
@content('home')
<h1>{{ backstage()->site()->name }}</h1>
@endcontentBoth directives compile to a check against hasSlug(...) on the bound model, so they only work inside requests where BindSite has populated the container.
Examples
{{-- Show a hero only on the homepage of the main site --}}
@site('main')
@content('home')
<x-hero :title="stage()->content()->title" />
@endcontent
@endsite
{{-- Pull a sibling page by slug --}}
@php($about = stage()->content('about'))
<a href="{{ $about->url }}">{{ $about->title }}</a>// In a controller
public function show()
{
$site = stage()->site();
$content = stage()->content();
return view('page', compact('site', 'content'));
}