-
Notifications
You must be signed in to change notification settings - Fork 21
Get Started And Create Your First Plug In
Let’s get started right away!
First set up your computer for Salesforce CLI plugin generation, and then generate an initial sample plugin that’s ready for your custom code. Then create a few example commands, just to see how that works, and you’ll be well on your way!
Before you generate a new Salesforce plugin, set up these prerequisites.
-
Install or update Node.js.
To build a Salesforce CLI plugin, you need the latest long-term support (LTS) version of Node.js. If you’re new to Node.js development, we suggest that you use nvm (Node Version Manager) to install Node.js. See this installation script to install or update nvm.
To check your Node.js version, run:
node --version
If your node version is earlier than 8 (or if you don’t have Node.js installed), run this command to install LTS:
nvm install –-lts
Then run this command to ensure nvm always loads the installed LTS version in new terminals:
nvm alias "default" "lts/*"
-
Install the Yarn package manager.
npm install -g yarn
-
Install TypeScript (target es2017.)
npm install -g typescript
Salesforce CLI plugins can use JavaScript instead of TypeScript, but the classes in the Salesforce DX core library are written in TypeScript.
-
Install or update Salesforce CLI.
If you don’t have Salesforce CLI installed on your computer, see Install the Salesforce CLI in the Salesforce CLI Setup Guide. After installing, update Salesforce CLI to ensure you’re on the latest version.
sf update
-
We recommend you use Visual Studio Code with Salesforce Extensions as your IDE, because it includes tools for developing on the Salesforce platform.
Use the interactive plugin generator, which is itself a plugin, to create your own Salesforce CLI plugin.
-
Install the
plugin-dev
Salesforce CLI plugin, which contains commands for generating plugins, commands, flags, and more:sf plugins install @salesforce/plugin-dev
-
Change to the top-level directory where you want to generate your plugin. For example:
cd /Users/astro/salesforce-plugins
-
Run the interactive command:
sf dev generate plugin
You're first prompted whether you're an internal Salesforce developer; we often use this tool to create our plugins too! But you should answer
n
so you're not subject to our internal requirements. You're next prompted for information to populate your new plugin, such as its name, description, author, and percentage of code coverage you want. The command will clone either the plugin-template-sf or plugin-template-sf-external Github repo and install the plugin's npm package dependencies usingyarn install
.When the generate command completes, the new plugin contains an example
sf hello world
command. See this section for a description of some of the generated files. -
Change to the new plugin directory, which is the same as the name you provided.
cd my-plugin
-
To run the commands in your in-development plugin from the top-level directory that your code lives in, precede the commands with
bin/dev.js
. For example, to run the samplehello world
command:bin/dev.js hello world bin/dev.js hello world --name Astro
To view the
--help
output for the command:bin/dev.js hello world --help
As you create new commands, test them the same way. For example:
bin/dev.js create webcart
-
When you’re ready to test-drive your plugin, link your in-development plugin to Salesforce CLI. From the top-level plugin directory, such as
my-plugin
, run this command (be sure to include the period at the end!):sf plugins link .
The command installs your plugin in Salesforce CLI by creating a symlink to your
my-plugin
directory. After you link your plugin, you can run your commands without usingbin/dev.js
. For example:sf hello world sf hello world -h
You might see this warning; don't worry, it's expected and everything is working just fine:
Warning: my-plugin is a linked ESM module and cannot be auto-transpiled. Existing compiled source will be used instead.
This warning means that your plugin is written with ESM (officially called ECMAScript modules), which is what our templates use. We can't auto-compile ESM when running a command, which means you must ensure that your plugin has been compiled after every change. We recommend running
yarn compile --watch
in a separate terminal so that all your changes are compiled every time you save a file.The
-h
flag, which displays a shorter version of help messages, is available only when you link or install the plugin. It's not available when usingbin/dev.js
.To see which plugins you've installed or linked, including your new plugin, run:
sf plugins --core
Your linked plugin is listed like this in the output:
my-plugin 1.0.0 (link) /Users/astro/salesforce-plugins/my-plugin
To unlink the plugin, run:
sf plugins unlink .
The sf
plug-In Generator creates many files, some that support the entire plugin, some for the hello world
command. This table describes a handful of the important ones which you can use as templates when you start coding your own commands.
File | Description |
---|---|
package.json |
Npm file that describes package dependencies and versions. |
tsconfig.json |
Typescript file that specifies the root files and the compiler options required to compile the project. |
src/commands/hello/world.ts |
Main TypeScript file that contains the code for the hello world command. The command imports and extends classes from @salesforce/sf-plugins-core. When you add your own commands, you use the SfCommand abstract class. |
messages/hello.world.md |
Markdown file that contains the messages that make up the command help and errors. |
test/commands/hello/world.test.ts |
Unit tests. |
test/commands/hello/world.nut.ts |
Complex integration, smoke, and end-to-end tests. Also known as NUTS (non-unit-tests.) |
.github/workflows/*.yml |
Sample GitHub Actions workflow files for testing, releasing, and publishing your plugin. See this repo for how the Salesforce CLI developer team uses GitHub Actions. |
Saying hello to the world is always fun, but I’m sure you're ready to do more. Let's add two more commands to your plugin.
Let's create a new command call external service
that makes an HTTP request to an external service that returns an interesting fact about a number. The command has no flags other than the ones you get for "free" (--help
, -h
and --json
). Note that when you enter the service's URL in your browser, it displays JSON with the interesting fact and some metadata about it. Okay, let's get going!
-
Install Got, our recommended npm HTTP library, into your plugin by running this command:
yarn add got
Because the new command makes HTTP requests, you need an HTTP library. While you can use any library you want, we recommend Got because it’s simple yet full featured.
-
In a terminal, change to the top-level directory of your plugin and run this command to generate the initial files (AKA scaffolding):
sf dev generate command --name call:external:service
The command prompts if you want to overwrite the existing
package.json
file, entery
so the file is updated with information about the new command.The
--name
flag specifies the full colon-separated name of the command you want to create. For example, if you want to create the commanddo awesome stuff
, set--name
todo:awesome:stuff
.The command generates these files, similar to the files associated with the
hello world
command:src/commands/call/external/service.ts
messages/call.external.service.md
test/command/call/external/service.nut.ts
test/command/call/external/service.test.ts
The Typescript files contain
import
statements for the minimum required Salesforce libraries, and scaffold some basic code. The new type names come from the value you passed to the--name
flag. -
Open up
src/commands/call/external/service.ts
in your IDE, such as Visual Studio Code. -
Add this
import
statement to the top of the file with the otherimport
statements:import got from 'got';
-
Let’s now handle the JSON returned by our new command when a user runs it with the
--json
flag, which all CLI commands have by default. We want our command to return the JSON provided by the external service’s API. The underlying CLI framework uses the return type of the command class’run
method, which in this case isCallExternalServiceResult
. So replace the code forCallExternalServiceResult
to reflect the service’s JSON:export type CallExternalServiceResult = { text: string; number: number; found: boolean; type: string; };
-
Let’s next work with the command flags. Remember that our command doesn't have any flags beyond the default ones, so remove this code inside the
CallExternalService
class which declares a flag that we no longer need:public static readonly flags = { name: Flags.string({ summary: messages.getMessage('flags.name.summary'), description: messages.getMessage('flags.name.description'), char: 'n', required: false, }), }
Then remove the
Flags
import from@salesforce/sf-plugins-core
at the top of the file; theimport
statement should look like this:import { SfCommand } from '@salesforce/sf-plugins-core';
-
The final coding step is to update the
run
method inside theCallExternalService
class. This method is where you put all of your logic for your command. In our case, we want our command to make an HTTP GET request to the service API and return the result. Replace therun
method with this code:public async run(): Promise<CallExternalServiceResult> { const result = await got<CallExternalServiceResult>( 'http://numbersapi.com/random/trivia?json' ).json<CallExternalServiceResult>(); this.log(result.text); return result; }
In the preceding code, note that
this.log
logs the interesting fact to the terminal only if the–json
flag is not provided. In other words, the underlying CLI framework is smart enough to know whether to log non-JSON output, like strings, to the terminal. -
We're ready to test! Open a Terminal, either in VS Code or outside, and run the command using
bin/dev.js
.bin/dev.js call external service
Hopefully you’ll see an interesting fact like this:
54 is the number of countries in Africa.
Let’s try the freebie flags, the ones that are magically available even though you didn't explicitly add them. First get help for the command:
bin/dev.js call external service --help
The help message isn't very useful yet because you haven't updated the boilerplate content in
messages/call.external.service.md
. We'll show an example of updating the help in the next example. Now let's run the command again but get JSON output instead of the default human-readable:bin/dev.js call external service --json
Good job adding a new command! Now let’s create something more Salesforce-y.
Let's next create a more complex command that connects to a Salesforce org and displays a list of all Account names and IDs.
The command has one new flag, --target-org
, with short name -o
, for specifying the username or alias of an org you’ve previously logged into (AKA authorized) with the login org
command. Do you recognize the flag? It's a standard flag that many of the core Salesforce CLI commands use, such as project deploy start
. You can use the flag too, including its existing code to connect to an org. Let's give it a try.
-
In a terminal, change to the top-level directory of your plugin and run this command to generate the initial command files; answer
y
to overwrite thepackage.json
file:sf dev generate command --name connect:org
Here's the list of generated files:
src/commands/connect/org.ts
messages/connect.org.md
test/command/connect/org.nut.ts
test/command/connect/org.test.ts
-
Run this interactive command to generate a flag. In this context, "generate" means update the existing command files with the required information for the new
--target-org
flag:sf dev generate flag
Be sure you specify these properties of the new flag when prompted:
- Command to add a new flag:
connect org
- Type:
requiredOrg
- Use standard definition: Y
- Flag name:
--target-org
(default)
The command updates two files:
src/commands/connect/org.ts
with the new flag code andmessages/connect.org.md
with a new entry for the flag's summary, which is displayed when you run the command with the--help
flag. - Command to add a new flag:
-
Open
src/commands/connect/org.ts
in your IDE and review the new generated code for the--target-org
flag:'target-org': Flags.requiredOrg(),
Because we're using existing code for the flag, the single line of code is all you need -- magic!
-
Because
org.ts
still contains code for the--name
flag, which was added automatically when you originally generated the command, remove it now, just to keep things tidy. This is the code you can remove frompublic static readonly flags
:name: Flags.string({ summary: messages.getMessage('flags.name.summary'), description: messages.getMessage('flags.name.description'), char: 'n', required: false, }),
The new flag is ready! Let's now code the command functionality.
-
Update the command’s return type (
ConnectOrgResult
) with this code:export type ConnectOrgResult = Array<{ Name: string; Id: string }>;
-
Update the
run
method with this code:public async run(): Promise<ConnectOrgResult> { // parse the provided flags const { flags } = await this.parse(ConnectOrg); // Get the orgId from the Org instance stored in the `target-org` flag const orgId = flags['target-org'].getOrgId(); // Get the connection from the Org instance stored in the `target-org` flag const connection = flags['target-org'].getConnection(); this.log(`Connected to ${flags['target-org'].getUsername()} (${orgId}) with API version ${connection.version}`); // Use the connection to execute a SOQL query against the org const result = await connection.query<{ Name: string; Id: string }>('SELECT Id, Name FROM Account'); // Log the results if (result.records.length > 0) { this.log('Found the following Accounts:'); for (const record of result.records) { this.log(` • ${record.Name}: ${record.Id}`); } } else { this.log('No Accounts found.'); } return result.records; }
Your new command should now be ready to test!
-
If you haven't already, log into your test org with the
login org web
command. Runorg list
to see the orgs you've already logged into and their associated usernames and aliases. We'll use the username or alias to test your new command.Logging into an org before you work with it is standard Salesforce CLI practice. Think about
project deploy start
: it too takes a username (also via the--target-org
flag) of the org you want to deploy to, and it's assumed that you've previously logged into it. -
Test your new command with
bin/dev.js
; replace<org-username-or-alias>
with the username or alias of the org you just logged into:bin/dev.js connect org --target-org <org-username-or-alias>
Hopefully you'll see output similar to this (the record IDs have been truncated for security):
Connected to <your-org-username> (<your-org-id>) with API version 61.0 Found the following Accounts:` • Acme: 001B...` • salesforce.com: 001B...` • Global Media: 001B... • TestAccount: 001B... • xyz: 001B... • Test1: 001B...
Make sure that the short flag name works too!
bin/dev.js connect org -o <your-org-username>
Pretty cool, huh.
Congratulations! You've successfully generated a Salesforce CLI plugin and added a few commands. Now it's time to customize it for your specific purpose. Here are the next steps on this exciting journey:
- Read more about what Salesforce CLI is all about and its architecture.
- When you're ready, read our design guidelines around naming, implementing interactive commands, and more.
-
Code your business logic. This is the "real" development work to implement your new commands and flags. The section assumes that you've run through the Get Started examples and now need reference information to customize your plugin.
- You also want to write useful messages, such as
--help
output and errors.
- You also want to write useful messages, such as
- Write robust tests.
- Maintain your beautiful plugin.
© Copyright 2024 Salesforce.com, inc. All rights reserved. Various trademarks held by their respective owners.
- Quick Intro to Developing sf Plugins
- Get Started: Create Your First Plugin
- Design Guidelines
- Code Your Plugin
- Debug Your Plugin
- Write Useful Messages
- Test Your Plugin
- Maintain Your Plugin
- Integrate Your Plugin With the Doctor Command
- Migrate Plugins Built for sfdx
- Conceptual Overview of Salesforce CLI