13.x documentation and the Framework v13.37.0 implementation, and builds on package development basics. For regular template syntax, see Blade. For view namespaces and publishing, see Overriding and updating package views.
Distinguish registration, compilation, and execution
The callback you pass toBlade::directive() returns a string of PHP code to generate, not the value to display. The $expression argument is also the PHP expression written in the template, not an evaluated value.
view:cache also performs compilation. Design directive generation callbacks so that they do not retrieve the current user, write to the database, call external APIs, or perform similar work.
Register with a package-specific name
As an example, the package provides@acmeCourierLabel($code), which displays a configured label before a shipment code. This assumes that Composer’s PSR-4 maps Acme\Courier\ to src/ and that the provider is registered through auto-discovery or manually.
src/CourierServiceProvider.php
courier.label is a string and courier.enabled is a boolean. For distributing and merging configuration files, see Package config merging and caching.
Register the extensions in the provider’s regular boot() method, and do not wrap them in a runningInConsole() check. The same extensions are needed both for HTTP rendering and for Artisan’s view:cache. Also avoid making a provider that only registers Blade extensions a deferred provider that does not boot until another service is resolved.
Receive arguments as expressions, not values
$code. The generated PHP looks like this:
config() and $code are evaluated when this PHP runs. This differs from an implementation in which the callback itself reads config() and returns a fixed string.
The callCustomDirective() implementation we examined strips the outer parentheses, trims the expression, and passes it to the callback. It does not convert the argument into a PHP value or split it into individual arguments. This example exposes the directive as syntax that takes exactly one required expression.
If you need to handle multiple arguments, do not simply explode() on commas. Arrays, method calls, and string literals can also contain commas. If you want to handle attributes or complex HTML, providing a Blade component makes it easier to separate responsibilities.
Name collisions are also a compatibility issue
directive() stores handlers in an array keyed by name. Registering the same name overwrites the previous registration, and compileStatement() chooses custom directives before the built-in compilation methods. The official tests even include a case in which a custom registration overrides foreach.
Avoid generic names such as label or enabled and built-in names, and add a package-specific prefix. Treat the names and arguments that consumers use in templates as public API, and provide migration steps when you change them.
Evaluate conditionals at runtime with Blade::if
The condition registered in the provider above can be used as follows:Blade::if() stores the condition callback and then registers directives for the opening, unless, else, and closing parts. In this example, the opening part is converted into the following PHP:
check(). It does not fix the true or false result at compile time. HTTP processes that use compiled views also need the condition callback to be registered.
This syntax toggles what is displayed; it is not authorization for shipment operations. Enforce access control in routes and in the operations themselves as well.
Package updates do not necessarily regenerate views automatically
TheCompiler::isExpired() implementation we examined checks whether a cache exists and, depending on configuration, compares the modification times of the source view and the compiled file. It does not track changes to directives written in a provider as dependencies.
The official documentation also advises clearing the cache with
view:clear after changing directive logic. Run it in the consuming application’s deployment process after the updated package and the consumer’s views are in sync.
view:cache when you precompile views in production. Do not clear the cache from the package’s boot() on every request, and do not modify the consuming application’s deployment process without permission during installation. Refreshing the configuration cache and reloading long-running processes should also follow the environment’s regular deployment procedure.
Verify compiled output and rendered output separately
First, verify the generated PHP in a test Laravel application where the provider above is registered. The following method is added to a Feature test that extendsTests\TestCase.
Related pages
Overriding and updating package views
Review namespace lookup order and how to maintain published templates.
Integrating package caches with optimize
Review how to integrate building and clearing custom caches into the deployment process.
Primary sources consulted
- Laravel documentation: Extending Blade and custom if statements
- Laravel documentation: Package views and components
- BladeCompiler: directive(), callCustomDirective(), compileStatement(), if(), and check()
- Compiler: cache checks with isExpired()
- BladeCustomTest: custom directives, conditionals, and overriding built-in syntax