Transfer.nvim is a lightweight Neovim plugin that lets you manually upload, download, and diff files or directories between your local workspace and remote servers via rsync and OpenSSH.
It supports mapping multiple local and remote paths, excluded path, and more.
Ideal for developers who prefer precise, command-driven deployment workflows.
Suitable for small projects where there is no need for CI/CD.
{
"coffebar/transfer.nvim",
lazy = true,
cmd = { "TransferInit", "DiffRemote", "TransferUpload", "TransferDownload", "TransferDirDiff", "TransferRepeat" },
opts = {},
},
TransferInit
- create a config file and open it. Just edit if it already exists.DiffRemote
- open a diff view with the remote file.TransferRepeat
- repeat the last transfer command (except TransferInit, DiffRemote).TransferUpload [path]
- upload the given file or directory.TransferDownload [path]
- download the given file or directory.TransferDirDiff [path]
- diff the directory with the remote one and display the changed files in the quickfix.
-- .nvim/deployment.lua
return {
["example_name"] = {
host = "myhost",
username = "web", -- optional
mappings = {
{
["local"] = "live", -- path relative to project root
["remote"] = "/var/www/example.com", -- absolute path or relative to user home
},
{
["local"] = "test",
["remote"] = "/var/www/test.example.com",
},
},
excludedPaths = { -- optional
"live/src/", -- local path relative to project root
"test/src/",
},
},
}
Example ~/.ssh/config
for passwordless auth:
Host myhost
HostName 127.1.177.12
User web
IdentityFile ~/.ssh/myhost_key
Host server2
...
{
window = {
mappings = {
-- upload (sync files)
uu = {
function(state)
vim.cmd("TransferUpload " .. state.tree:get_node().path)
end,
desc = "upload file or directory",
nowait = true,
},
-- download (sync files)
ud = {
function(state)
vim.cmd("TransferDownload" .. state.tree:get_node().path)
end,
desc = "download file or directory",
nowait = true,
},
-- diff directory with remote
uf = {
function(state)
local node = state.tree:get_node()
local context_dir = node.path
if node.type ~= "directory" then
-- if not a directory
-- one level up
context_dir = context_dir:gsub("/[^/]*$", "")
end
vim.cmd("TransferDirDiff " .. context_dir)
vim.cmd("Neotree close")
end,
desc = "diff with remote",
},
},
},
}
require("which-key").add({
{ "<leader>u", group = "Upload / Download", icon = "" },
{
"<leader>ud",
"<cmd>TransferDownload<cr>",
desc = "Download from remote server (scp)",
icon = { color = "green", icon = "" },
},
{
"<leader>uf",
"<cmd>DiffRemote<cr>",
desc = "Diff file with remote server (scp)",
icon = { color = "green", icon = "" },
},
{
"<leader>ui",
"<cmd>TransferInit<cr>",
desc = "Init/Edit Deployment config",
icon = { color = "green", icon = "" },
},
{
"<leader>ur",
"<cmd>TransferRepeat<cr>",
desc = "Repeat transfer command",
icon = { color = "green", icon = "" },
},
{
"<leader>uu",
"<cmd>TransferUpload<cr>",
desc = "Upload to remote server (scp)",
icon = { color = "green", icon = "" },
},
})
- rcarriga/nvim-notify - animated popup notifications.
- nvim-neo-tree/neo-tree.nvim - file explorer.
- coffebar/neovim-project - project management.
Check this repo for converting you config from JetBrains projects.
Look at defaults and overwrite anything in your opts.
transfer.nvim.mp4
- Windows paths;
- SSH Auth that is not passwordless.
Feel free to contribute, open issues, and submit pull requests to help us improve transfer.nvim.
Run tests with make test
.
Follow the Conventional Commits specification for commit naming.