customLaunchers (covered earlier) handles the vast majority of real-world needs. This lesson covers the rarer case: writing a genuine custom launcher plugin, for browsers or launch mechanisms with no existing package.
When You Actually Need a Full Custom Launcher
A full custom launcher plugin is warranted when you need control beyond flags and preferences — for example, launching a browser through a completely custom mechanism (a remote device farm, a non-standard emulator, or a proprietary browser with no existing karma-*-launcher package at all).
baseBrowserDecorator provides shared launcher behavior (process management, disconnect handling) that most custom launchers build on rather than reimplement.
A custom launcher plugin can be a local file, referenced directly via require(), without ever publishing it to npm.
The plugin object's key follows the launcher:<Name> convention, exactly like published karma-*-launcher packages.
$inject declares which Karma-provided services the constructor function needs — dependency injection, Angular-style.
Once registered, the custom launcher is used in browsers exactly like any built-in one.
Custom Launcher Plugin Cheat Sheet
The key pieces every custom launcher plugin needs.
Piece
Purpose
baseBrowserDecorator
Provides shared process lifecycle behavior
this._start(url)
Called by Karma to actually launch the browser at the given URL
this._execCommand
Helper for spawning a browser process, from the decorator
launcher:<Name> key
The naming convention Karma's plugin system expects
$inject array
Declares constructor dependencies for Karma's plugin injector
Extending an Existing Launcher Instead
Before writing a launcher entirely from scratch, check whether extending an existing one (subclassing its prototype, similar to how customLaunchers extends via base, but in actual code) gets you most of the way there with far less new code to maintain.
const ChromeHeadlessLauncher = require('karma-chrome-launcher')['launcher:ChromeHeadless'][1];
function MyChromeVariant(baseBrowserDecorator, args, logger) {
ChromeHeadlessLauncher.call(this, baseBrowserDecorator, args, logger);
// add custom behavior on top
}
MyChromeVariant.prototype = ChromeHeadlessLauncher.prototype;
This pattern is more advanced and less commonly needed than customLaunchers, but useful when a base launcher's default behavior itself needs to change.
Publishing a Reusable Launcher Package
If a custom launcher solves a genuinely reusable problem (not specific to one project), publishing it as a real karma-*-launcher npm package lets it participate in Karma's automatic plugin discovery for any consumer, exactly like the official launcher packages.
Common Mistakes
Writing a full custom launcher when customLaunchers with a base and extra flags would have been sufficient.
Forgetting the launcher:<Name> naming convention, causing Karma to fail resolving the plugin.
Not using baseBrowserDecorator, and reimplementing process lifecycle management that Karma already provides.
Publishing a launcher without the karma- prefix, breaking automatic plugin discovery for consumers.
Key Takeaways
A full custom launcher plugin is only needed for genuinely novel launch mechanisms, not simple flag tweaks.
baseBrowserDecorator provides shared process lifecycle behavior that custom launchers build on top of.
The launcher:<Name> key convention and $inject dependency declarations follow Karma's standard plugin shape.
Publishing a reusable launcher as a karma-*-prefixed npm package enables automatic discovery for other consumers.
Pro Tip
Before writing any custom launcher from scratch, read the source of an existing simple one (karma-chrome-launcher is short and well-documented) end to end. It's the fastest way to understand the exact contract Karma expects, since the official documentation on this specific plugin type is relatively sparse.
You now understand custom launcher plugins. Next, learn how to write a custom reporter plugin for specialized output needs.