Salesforce CLI (sf) integration for Neovim. Run Apex tests with failures in
quickfix, deploy and retrieve metadata, manage orgs and scratch orgs, and execute
anonymous Apex — without leaving the editor.
Long-running commands (tests, deploys, scratch org creation) run in a terminal
split at the bottom of the screen, so you see exactly what the CLI is doing.
The split opens in Normal mode following the output, so you can scroll back
with the usual keys at any time; when the command finishes, Enter or q
closes it.
Quick lookups (listing orgs for a picker, setting the target org, opening an org)
run in the background without blocking the editor. No custom UI: feedback and
pickers go through vim.notify / vim.ui.select, so it works with whatever UI
plugins you already have, or none.
- Neovim >= 0.10
- Salesforce CLI (
sf) installed and authenticated - ripgrep (
rg) — used to locate Apex class files for quickfix - An SFDX project: open Neovim from the directory containing
sfdx-project.json(the plugin uses the current working directory as the project root)
Run :checkhealth sf-nvim to verify all of the above.
{
"wkuehler/sf-nvim",
version = "*", -- latest tagged release; drop to track main
opts = {
enable_default_keybinds = true,
},
}use({
"wkuehler/sf-nvim",
tag = "v*",
config = function()
require("sf-nvim").setup({ enable_default_keybinds = true })
end,
})vim-plug:
Plug 'wkuehler/sf-nvim', { 'tag': 'v*' }
" after plug#end():
lua require("sf-nvim").setup({ enable_default_keybinds = true })Any plugin manager works — :Sf is registered at startup; call
require("sf-nvim").setup({ ... }) once to configure it (keymaps are off
until you do).
sf-nvim does not provide completion or diagnostics; pair it with the Apex language server. With mason.nvim and nvim-lspconfig:
-- :MasonInstall apex-language-server (needs a Java 11+ runtime)
vim.lsp.config("apex_ls", {
apex_enable_semantic_errors = false,
apex_enable_completion_statistics = false,
})
vim.lsp.enable("apex_ls")On Neovim 0.10 use require("lspconfig").apex_ls.setup({ ... }) instead of
vim.lsp.config/vim.lsp.enable. sf-nvim's ftdetect sets filetype=apex
for .cls/.trigger inside an SFDX project, which is what apex_ls attaches
to.
Defaults:
require("sf-nvim").setup({
test_results_dir = "test-results", -- where test JSON is saved, relative to cwd
auto_open_quickfix = true, -- open quickfix when a test run has failures
test_wait_time = 15, -- minutes passed to `sf apex run test -w`
enable_default_keybinds = false, -- install the keymaps listed below
leader_prefix = "<leader>s", -- prefix for those keymaps
deploy_on_save = false, -- deploy a source file to the target org on :w
tail_notify = true, -- notify on each debug log captured by :Sf log tail
})Add test-results/ (or whatever you set) to your .gitignore.
Everything is available as :Sf <group> <action> with tab completion:
| Command | What it does |
|---|---|
:Sf test current |
Run tests for the current .cls; failures go to quickfix |
:Sf test method |
Run only the @IsTest method under the cursor |
:Sf test all |
Run the whole Apex test suite |
:Sf test failed |
Rerun only the methods that failed in the latest saved results |
:Sf test load |
Load the most recent saved results into quickfix |
:Sf test clear |
Delete saved test results (asks first) |
:Sf apex execute |
Run the current file as anonymous Apex |
:'<,'>Sf apex selection |
Run the selected lines as anonymous Apex |
:Sf apex debug |
Run the current file as anonymous Apex in the background and open its debug log |
:'<,'>Sf apex debugselection |
Same, for the selected lines |
:Sf log list |
Pick a stored debug log and open it |
:Sf log latest |
Open the most recent debug log |
:Sf log tail |
Toggle a background tail of debug logs; creates a trace flag for your user if needed |
:Sf log show |
Open the captured logs (sf://log/tail) in a split |
:Sf soql buffer |
Run the buffer as a SOQL query; results as a table |
:'<,'>Sf soql selection |
Run the selected lines as a SOQL query |
:Sf soql prompt |
Prompt for a query and run it |
:Sf org open |
Open the target org in a browser |
:Sf org list |
sf org list |
:Sf org info |
sf org display |
:Sf org create |
Create a scratch org: pick a *-scratch-def.json, enter days and alias |
:Sf org login |
sf org login web, with an alias prompt and an offer to set it as default |
:Sf org delete |
Pick a scratch org and delete it (asks first) |
:Sf org pick |
Pick any authenticated org and open it in a browser |
:Sf org limits |
sf org list limits as a used/max table in sf://limits |
:Sf config org |
Set target-org from a picker of authenticated orgs |
:Sf config hub |
Set target-dev-hub from a picker of Dev Hubs |
:Sf project file |
Deploy the current file (or its LWC/Aura bundle); errors go to quickfix |
:Sf project fetch |
Retrieve the current file (or bundle) from the org |
:Sf project deploy |
sf project deploy start; on failure, errors go to quickfix |
:Sf project retrieve |
sf project retrieve start |
:Sf project validate |
sf project deploy start --dry-run |
:Sf project preview |
sf project deploy preview — what would deploy, and conflicts |
:Sf project previewretrieve |
sf project retrieve preview |
:Sf generate class / trigger / lwc / aura |
sf template generate ... with name and output-dir prompts; opens the new file |
Full reference: :help sf-nvim.
With enable_default_keybinds = true and the default leader_prefix = "<leader>s".
Keys are grouped by a prefix letter — t tests, a anonymous Apex, l debug
logs, q SOQL, o org, c config, p project, g generate. If
which-key.nvim is installed, those
groups are labelled automatically (require("sf-nvim").groups).
| Key | Command |
|---|---|
<leader>stc |
:Sf test current |
<leader>stm |
:Sf test method |
<leader>sta |
:Sf test all |
<leader>stf |
:Sf test failed |
<leader>stl |
:Sf test load |
<leader>stx |
:Sf test clear |
<leader>sae |
:Sf apex execute (normal) / :Sf apex selection (visual) |
<leader>sad |
:Sf apex debug (normal) / :Sf apex debugselection (visual) |
<leader>sll |
:Sf log list |
<leader>slL |
:Sf log latest |
<leader>slt |
:Sf log tail |
<leader>sls |
:Sf log show |
<leader>sqb |
:Sf soql buffer |
<leader>sqq |
:Sf soql prompt |
<leader>sq |
:Sf soql selection (visual) |
<leader>soo |
:Sf org open |
<leader>sol |
:Sf org list |
<leader>soi |
:Sf org info |
<leader>soc |
:Sf org create |
<leader>soa |
:Sf org login |
<leader>sox |
:Sf org delete |
<leader>soO |
:Sf org pick |
<leader>soL |
:Sf org limits |
<leader>sco |
:Sf config org |
<leader>sch |
:Sf config hub |
<leader>spf |
:Sf project file |
<leader>spF |
:Sf project fetch |
<leader>spd |
:Sf project deploy |
<leader>spr |
:Sf project retrieve |
<leader>spv |
:Sf project validate |
<leader>spp |
:Sf project preview |
<leader>spP |
:Sf project previewretrieve |
<leader>sgc |
:Sf generate class |
<leader>sgt |
:Sf generate trigger |
<leader>sgl |
:Sf generate lwc |
<leader>sga |
:Sf generate aura |
To define your own instead, leave enable_default_keybinds off and map to the
:Sf commands (or to the functions in require("sf-nvim").actions).
Edit → deploy. :Sf project file pushes just the file you're in (for LWC
and Aura, the whole bundle) in the background and reports the result. Compile
errors land in quickfix with line and column. Set deploy_on_save = true to do
this automatically on every :w of a file under a package directory.
Tests. Open a test class and run :Sf test current, or put the cursor in
one @IsTest method and run :Sf test method. The run streams in a terminal
split; press Enter or q when it finishes. Failures (including compile
failures) are loaded into quickfix with file, line, column and message — :cn /
:cp to jump between them. Results are also saved as JSON under
test_results_dir, so :Sf test load can reload the latest run later.
Deploy. :Sf project validate for a dry run, check the output, then
:Sf project deploy. If either fails, the failed components are loaded into
quickfix from sf project deploy report.
Anonymous Apex. :Sf apex execute runs the whole file in a terminal split;
select some lines and :'<,'>Sf apex selection (or <leader>sae in visual mode)
runs just those. :Sf apex debug runs in the background instead and opens the
result in a sf://apex-run buffer: a one-line verdict (success, compile error
with line/column, or the runtime exception and stack) followed by the full
debug log with apexlog highlighting.
Debug logs. :Sf log list fetches the org's stored logs into a picker
(time, operation, status, size, duration, user) and opens the chosen one;
:Sf log latest skips the picker. Logs open in read-only sf://log/<id>
buffers — q closes them. Stored logs only exist while a trace flag is active
for your user; :Sf log tail creates one, after which list/latest start
returning logs.
Tailing. :Sf log tail toggles sf apex tail log running in the
background — no split is opened. Each captured log bumps a counter (and, with
tail_notify, a short notification); :Sf log show opens the accumulated
output in a split whenever you want to look, and q closes it while the tail
keeps running. The buffer keeps the last 5000 lines. If the CLI exits on its
own (expired token, org switch) you get an error notification rather than a
silent stop; the process is killed when Neovim exits.
Statusline. require("sf-nvim").status() returns the target org and, while
tailing, ⏺ N (logs captured) — e.g. dev ⏺ 3. It's refreshed on setup()
and after :Sf config org. lualine example:
sections = { lualine_x = { function() return require("sf-nvim").status() end } },The User SfTargetChanged and User SfTailChanged autocommand events fire
when either part changes, if your statusline needs a redraw trigger.
SOQL. Write a query in a .soql buffer (or anywhere) and run
:Sf soql buffer, or select the lines and :'<,'>Sf soql selection, or
:Sf soql prompt for a one-off. // and -- comment lines are dropped.
Results render as an aligned table in sf://query, columns in the order you
selected them; relationship fields flatten to Owner.Name, subqueries show as
[N rows]. Query errors show the CLI's message with its caret in the same
buffer.
Filetypes. The plugin registers soql, apex (.apex, plus .cls and
.trigger inside an SFDX project), and apexlog, and ships syntax
highlighting for apexlog. Bring your own apex/soql syntax or Tree-sitter
grammars.
Scratch orgs. :Sf org create finds every config/**/*-scratch-def.json
in the project and prompts for duration and alias. :Sf config org switches
the target org from a picker afterwards.
- Long-running commands (tests, deploys, scratch org creation) run in a terminal split by design so you can watch them; only quick lookups run in the background.
- Quickfix parsing depends on the
sf apex run test --jsonoutput shape. - Class file lookup assumes the standard
<Name>.clsfile naming.
make test # plenary.nvim test suite
make test-file FILE=tests/quickfix_spec.lua # one spec
make lint / make fmt # luacheck / styluaSee CONTRIBUTING.md.
MIT