Helpers
Backstage ships a small set of runtime helpers 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.
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.
backstage()
The backstage() helper is the main entry point. Without arguments it returns the helpers instance:
backstage(); // Backstage\Helpers\Support\Helpers
backstage()->site(); // current Site
backstage()->content(); // current ContentYou can also resolve a binding directly by name, with optional dot-notation for properties:
backstage('site'); // current Site
backstage('content.title'); // shorthand for backstage()->content()->title
backstage('language'); // current LanguageIf the binding is not registered (for example, on a request that did not resolve content), backstage('site') returns null. Calling backstage()->site() without arguments outside a content response throws an exception.
Looking up a specific record
Both site() and content() accept lookup arguments:
backstage()->site('main'); // Site where slug = 'main'
backstage()->site($id, 'ulid'); // Site where ulid = $id
backstage()->content('about'); // Content where slug = 'about' (current locale)
backstage()->content('about', locale: 'nl'); // Content where slug = 'about' and language_code = 'nl'
// Scope a content lookup to a specific site
backstage()->content('about', site: 'main'); // by site slug
backstage()->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')).
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="backstage()->content()->title" />
@endcontent
@endsite
{{-- Pull a sibling page by slug --}}
@php($about = backstage()->content('about'))
<a href="{{ $about->url }}">{{ $about->title }}</a>// In a controller
public function show()
{
$site = backstage()->site();
$content = backstage()->content();
return view('page', compact('site', 'content'));
}props() (separate package)
The backstage/props package exposes a parallel props() helper backed by the backstage.props container binding. It is meant for project-specific UX additions that should not live in core.
props(); // Backstage\Props\Helpers\Support\Helpers
props()->ping(); // example helper exposed by props ('pong')Like backstage(), the props() helper also accepts a binding name (with optional dot-notation), e.g. props('something.value'), returning null when the binding is not registered.
Use backstage() for everything provided by core; reach for props() only when you have registered your own helpers in the props package.
Adding your own helpers
backstage/props is the package front-enders own. Add reusable Blade/template helpers here — never in backstage/cms. Every new helper is two changes inside the props package:
- A utility trait under
Helpers/Support/Utilities/containing the method. - The matching method signature on the
PropsHelperscontract.
Both are required: the trait makes the helper callable, the contract keeps props() typed and signals to other developers what is available.
1. Create a utility trait
// src/Helpers/Support/Utilities/MenuUtility.php
namespace Backstage\Props\Helpers\Support\Utilities;
use Backstage\Models\Menu;
trait MenuUtility
{
public function menu(string $slug): ?Menu
{
return Menu::where('slug', $slug)->first();
}
}The trait inherits the $app container property from DefaultUtility, so $this->app->getLocale() and $this->retrieveBinding('site') are available.
2. Update the contract
The contract is the source of truth for what props() can do. Add the method signature:
// src/Helpers/Contracts/PropsHelpers.php
namespace Backstage\Props\Helpers\Contracts;
use Backstage\Models\Menu;
interface PropsHelpers
{
public function ping(): string;
public function menu(string $slug): ?Menu;
}3. Compose the trait into Helpers
// src/Helpers/Support/Helpers.php
class Helpers implements PropsHelpers
{
use Utilities\DefaultUtility;
use Utilities\PingUtility;
use Utilities\MenuUtility; // ← new
}The helper is now available everywhere props() resolves:
{{ props()->menu('main')?->title }}$menu = props()->menu('footer');Conventions
- One trait per topic —
MenuUtility,NavigationUtility,BreadcrumbUtilityetc. KeepHelpersa one-line composition. - Always update the contract. Skipping it works at runtime but breaks IDE completion and static analysis, and makes it harder for the next front-ender to see what is available.
- Use
$this->retrieveBinding('site')(notapp('site')) when a helper needs a request-bound model, so the "outside of a content response" exception is thrown consistently. - Never add helpers to
backstage/cms. Core'sCoreHelperscontract is reserved for framework-level lookups (site(),content()); everything UX-shaped belongs in props.