public function hasViews( ?string $viewsPath = null, string $directory = '../resources/views', ?string $namespace = null,): static
$packager ->name('Blog') ->hasViews();
acme/blog/└── resources/views/ ├── index.blade.php ├── post.blade.php └── partials/ └── author.blade.php
Views register under the package short name, and — unlike the file-based resources — the whole directory is registered, subdirectories included:
view('blog::index');view('blog::post', ['post' => $post]);view('blog::partials.author', ['author' => $author]);
@include('blog::partials.author', ['author' => $post->author]) @extends('blog::layouts.app')
The $namespace parameter
$packager->hasViews(namespace: 'acme-blog'); // has no effect
Not currently applied
The third parameter is accepted and stored, but bootViews() registers the view namespace from
shortName() regardless. As of 2.4.0 there is no way to register views under a namespace other
than the package short name.
If you need a different one, set it with hasShortName() — which changes
the publish tags and every other namespaced path too — or call loadViewsFrom() yourself from a
lifecycle hook.
A custom views directory
The first parameter takes a path, absolute or relative to your provider's directory:
$packager->hasViews('../resources/blade'); // relative$packager->hasViews(__DIR__.'/../resources/blade'); // absolute
Or use the second parameter, which is only consulted when the first is empty:
$packager->hasViews(directory: '../ui/views');
Either way, a directory that does not exist throws DirectoryNotFoundException at registration. The
two parameters differ in one detail worth knowing: the first is checked with is_dir() and throws
if missing, the second is used as-is. Prefer the first for anything unusual.
Publishing and overriding
php artisan vendor:publish --tag=blog::views
The directory is copied to resources/views/vendor/{short-name}/:
resources/views/vendor/blog/├── index.blade.php├── post.blade.php└── partials/author.blade.php
Laravel's view finder checks that directory before the package's own, per view. Which means a
consumer can publish everything and then delete all but the one file they wanted to change —
blog::post resolves to their copy, blog::index still resolves to yours, and your future updates
to index.blade.php keep arriving.
Worth putting in your readme, because the instinct is to keep the whole published directory and then wonder why an upgrade changed nothing.
Writing views a consumer will want to override
The more a view assumes, the harder it is to override well. A few habits that pay off:
Keep layout out of content views. A consumer with their own layout should not have to fork your content view to use it.
@extends(config('blog.layout', 'blog::layouts.app')) @section('content') <article> <h1>{{ $post->title }}</h1> {!! $post->body !!} </article>@endsection
Leave named slots for insertion, so small additions do not require a fork:
<article> @stack('blog-post-before') <h1>{{ $post->title }}</h1> {!! $post->body !!} @stack('blog-post-after') </article>
Don't hard-code CSS classes a consumer cannot change:
<article class="blog-post"> <article class="{{ config('blog.classes.post', 'blog-post') }}">
Sharing data with your views
Three mechanisms, in increasing order of scope:
// Passed per render — ordinary Blade.view('blog::post', ['post' => $post]); // Bound to specific views, resolved at render time.$packager->hasViewComposer('blog::post', PostComposer::class); // Available in every view in the application.$packager->hasSharedDataForAllViews(['blogVersion' => '2.4.0']);
See View composers for both of the latter.
Views and Blade components
Class-based components have their own view resolution, and register separately — see View components:
$packager ->name('Blog') ->hasViews() ->hasComponents('blog', PostCard::class);
A component's view is looked up in your namespace, so it belongs in the same directory:
public function render(): View{ return view('blog::components.post-card');}
Anonymous components
Laravel finds anonymous components under a registered view namespace at
components/, so they work with nothing extra declared:
resources/views/components/badge.blade.php
<x-blog::badge type="draft">Draft</x-blog::badge>
Introspection
$packager->isViewable(); // bool$packager->views(); // absolute path to the views directory
Testing
test('the package registers its views', function () { expect(view()->exists('blog::post'))->toBeTrue();}); test('a published view overrides the package view', function () { $this->artisan('vendor:publish --tag=blog::views')->assertExitCode(0); file_put_contents(resource_path('views/vendor/blog/post.blade.php'), 'overridden'); expect(view('blog::post')->render())->toBe('overridden');});