Custom commands
June 28, 2026 ยท View on GitHub
osls plugins can define custom CLI commands.
These commands can then be called by users, for example: osls my-command.
class MyPlugin {
constructor() {
this.commands = {
'my-command': {
lifecycleEvents: ['resources', 'functions'],
},
};
}
}
module.exports = MyPlugin;
A CLI Command that can be called by a user, e.g. osls foo. A Command has no logic, but simply defines the CLI configuration (e.g. command, parameters) and the Lifecycle Events for the command. Every command defines its own lifecycle events.
Lifecycle events
By default, a command has no logic. Use lifecycle event hooks to add logic when the command runs:
class MyPlugin {
constructor() {
this.commands = {
'my-command': {
lifecycleEvents: ['run'],
},
};
this.hooks = {
'my-command:run': () => {
// Do something
},
};
}
}
For each event, an additional before and after event is created:
this.hooks = {
'before:my-command:run': () => {
// Before my command runs
},
'my-command:run': () => {
// My command runs
},
'after:my-command:run': () => {
// After
},
};
Note that a command can define multiple events: these will be called sequentially.
Command options
Commands can have CLI options:
- either passed with a double dash (
--):osls my-command --function functionName. - or as a shortcut with a single dash (
-):osls my-command -f functionName.
Options can be specified in the command definition. The value of the CLI option can be retrieved via the options parameter of the plugin:
class MyPlugin {
constructor(serverless, options) {
this.options = options;
this.commands = {
'my-command': {
// The 'usage' property is used to display the 'osls --help' output
usage: 'This is my new custom command!',
lifecycleEvents: ['run'],
options: {
// Define the '--function' option with the '-f' shortcut
function: {
usage: 'Specify the function you want to handle (e.g. "--function myFunction")',
shortcut: 'f',
required: true,
type: 'string', // Possible values: 'string', 'boolean', 'multiple'
},
},
},
};
this.hooks = {
'my-command:run': () => this.run(),
};
}
run() {
console.log('The option was: ', this.options.function);
}
}
If an option is not required, a default property can be set in the option definition.
Nested commands
Commands can define sub-commands via a nested commands object. This mirrors how osls core
nests deploy function under deploy:
class MyPlugin {
constructor() {
this.commands = {
'my-command': {
usage: 'My top-level command',
lifecycleEvents: ['run'],
commands: {
resource: {
usage: 'My nested sub-command',
lifecycleEvents: ['run'],
},
},
},
};
this.hooks = {
'my-command:run': () => this.run(),
'my-command:resource:run': () => this.runResource(),
};
}
}
The sub-command above is invoked as osls my-command resource, and its lifecycle event is
namespaced under the parent: my-command:resource:run.
Spawning other commands
A plugin can run another command's full lifecycle from within a hook via
serverless.pluginManager.spawn():
class MyPlugin {
constructor(serverless) {
this.serverless = serverless;
this.hooks = {
'my-command:run': async () => {
// Run the `package` command lifecycle
await this.serverless.pluginManager.spawn('package');
},
};
}
}
The command can be passed as a colon-separated string ('deploy:function') or as an array of
segments (['deploy', 'function']).
Pass { terminateLifecycleAfterExecution: true } to stop the current command's remaining
lifecycle events once the spawned command finishes:
await this.serverless.pluginManager.spawn('package', {
terminateLifecycleAfterExecution: true,
});
Command naming
Command names must be unique across all plugins. For example instead of defining a custom deploy command, name it my-company-deploy instead.
If a plugin defines a command name that conflicts with osls core or another plugin, the CLI will exit with an error.