Appearance
Livewire "Unable to find component" in a package component
Livewire throws Unable to find component: [...] when a request carries a component name that its registry cannot turn back into a class. For a component that lives outside App\Livewire, such as one shipped in a package, that happens on the first wire:click: the initial render works because you passed the class, and the follow-up request fails because Livewire only has the name. The fix is to register the class under an explicit alias with Livewire::component() on every request. This post shows why the first render hides the bug, how it froze the interactive sections in FilamentCraft until v1.5.0, and the test that now guards it.
Why the first render works and the first click does not
Livewire resolves a component two different ways during its life, and only one of them needs a name that maps back to a class.
On the first render you usually hand Livewire the class itself. Livewire::mount(SomeComponent::class, [...]) or a full-page route both start from a class, so there is nothing to look up. Livewire instantiates it, renders it, and stamps a name into the snapshot it sends to the browser.
On every later request the browser posts that snapshot to /livewire/update. Now Livewire has a string, not a class, and it has to find the class from the string. In Livewire 3 (we read 3.7.15) that lookup lives in Livewire\Mechanisms\ComponentRegistry. It checks explicit aliases first, then classes registered without an alias, and finally generates a class name from the component name by prefixing config('livewire.class_namespace'), which defaults to App\Livewire. If none of those produces a Livewire\Component subclass, it throws ComponentNotFoundException with the message Unable to find component: [name].
The catch is how the name gets generated in the first place. For a class that was never registered, Livewire kebab-cases every segment of the fully qualified class name and strips the App\Livewire prefix only if it is there. A class inside your app round-trips cleanly. A class in a package does not: the generated name has no app.livewire prefix to strip, and turning it back into a class adds App\Livewire\ in front of the vendor namespace, which points at a file that does not exist.
Reproducing it in ten lines
You can watch this happen without a browser by asking a fresh ComponentRegistry for the name of a package class and then asking for the class behind that name. This is the probe we ran against the shipped NewsletterSection:
php
$registry = new \Livewire\Mechanisms\ComponentRegistry;
$class = \FilamentCraft\Sections\Builtin\NewsletterSection::class;
$name = $registry->getName($class);
// "filament-craft.sections.builtin.newsletter-section"
$registry->getClass($name);
// ComponentNotFoundException: Unable to find component:
// [filament-craft.sections.builtin.newsletter-section]
$registry->component('filamentcraft-section.newsletter', $class);
$registry->getName($class); // "filamentcraft-section.newsletter"
$registry->getClass('filamentcraft-section.newsletter'); // NewsletterSection::classThe first getClass() call is what /livewire/update does on a click. Once the class has an alias, the name Livewire stamps at render time is the alias, and the alias resolves straight back to the class.
How it froze interactive sections in FilamentCraft
FilamentCraft sections come in two kinds. A BladeSection is a static view. A LivewireSection extends Livewire\Component, so a newsletter form or contact form can validate input and show a success state without a page reload. The guide to interactive sections covers how to write one.
The page renderer does not know which Livewire components a page will contain until an editor places them, so it cannot use <livewire:...> tags. Each section renders itself, and LivewireSection::renderToHtml() does it with a class:
php
$html = Livewire::mount(static::class, ['section' => $data]);Before v1.5.0 that was the whole story. The section painted its HTML on the published page, the visitor typed an email, pressed Subscribe, and the update request could not resolve filament-craft.sections.builtin.newsletter-section back to a class. Nothing on the page changed. The CHANGELOG entry for v1.5.0 describes it from the visitor's side: the first wire:click or wire:submit failed and the section appeared frozen on the live site. It affected every interactive section, built-in or written by a host app, because they all go through the same mount() call.

The fix: register an alias where the section is registered
Every section already passes through SectionRegistry::register(), and that runs from the package service provider's boot on every request, which is exactly where Livewire needs to learn the alias. So the registry now registers each LivewireSection with Livewire as it goes:
php
if (is_subclass_of($class, LivewireSection::class)) {
Livewire::component($this->livewireAlias($slug), $class);
}
public function livewireAlias(string $slug): string
{
return 'filamentcraft-section.'.str_replace('::', '.', $slug);
}The alias is derived from the section's slug, not from its class name, so it stays stable if a host moves or renames the class. A prefixed slug such as acme::pricing becomes filamentcraft-section.acme.pricing, because :: has no meaning in a Livewire name.
Two details matter if you copy this pattern into your own package.
First, the registration has to run on the update request too, not only on the request that rendered the page. Registering inside a controller or a view composer is not enough. A service provider's boot() method runs on every request, so that is the place.
Second, register with an explicit name. Livewire::component(SomeClass::class) with no name also works in Livewire 3, which keeps a list of unaliased classes and matches them by a crc32 hash of the class name, but the name that ends up in the HTML is then an opaque number. An alias reads better in the browser's network tab, which is where you will be the next time something breaks.
Why our tests did not catch it
Livewire::test() passed the whole time, and the reason is in Livewire\Features\SupportTesting\Testable::create(). Before mounting, it checks whether the class is discoverable from the configured namespace, and if it is not, it calls component() on the class for you. The test registered the component that the real request never had, so every call() in the test resolved fine. The testing reference now carries a warning about exactly this: a Livewire::test() for an unregistered section succeeds, and the same section fails on a real page.
The regression test that guards the fix asserts the lookup directly instead of trusting a round-trip through the test harness:
php
it('registers a LivewireSection under a Livewire alias that resolves back to the class', function (): void {
$registry = app(SectionRegistry::class);
$livewire = app(ComponentRegistry::class);
$alias = $registry->livewireAlias(InteractiveFixtureSection::slug());
expect($livewire->getName(InteractiveFixtureSection::class))->toBe($alias)
->and($livewire->getClass($alias))->toBe(InteractiveFixtureSection::class);
});It checks both directions: the class produces the alias at render time, and the alias produces the class at update time. If either half drifts, the test fails in CI instead of on a customer's contact form.
The broader lesson we took from it is to click an interactive feature on a real page at least once before calling it done. Livewire has two request shapes, and unit tests usually exercise only one.
Livewire "Unable to find component" checklist
If you hit this error, the cause is almost always one of these:
- The class lives outside
livewire.class_namespaceand was never registered. Register it withLivewire::component('your-alias', YourComponent::class)in a service provider'sboot(). - The component is registered, but only inside code that runs on some requests. Move the registration to a provider so
/livewire/updatesees it too. - The classes sit in one namespace and
livewire.class_namespacepoints at another, which is common after an upgrade from Livewire 2'sApp\Http\Livewireto Livewire 3'sApp\Livewire. The generated name keeps the old prefix and resolves under the new one. Move the classes or change the config so they agree.
For FilamentCraft sections, none of this is your job anymore. Register the section through the plugin's ->registerSection() or the filamentcraft.sections.register config key, as the custom sections guide shows, and the Livewire alias comes with it. To watch a section's update requests in your own network tab, open the live demo, or compare pricing if you want it in your own panel.
