diff --git a/Cargo.lock b/Cargo.lock
index 896f7bc1..f360117b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -140,6 +140,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
 [[package]]
 name = "etcetera"
 version = "0.3.2"
@@ -360,6 +366,7 @@ dependencies = [
  "tokio",
  "toml",
  "url",
+ "which",
 ]
 
 [[package]]
@@ -1063,6 +1070,16 @@ version = "0.10.2+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
 
+[[package]]
+name = "which"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe"
+dependencies = [
+ "either",
+ "libc",
+]
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 3223b14f..5cfee75d 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -1280,6 +1280,96 @@ mod cmd {
         editor.set_theme_from_name(theme);
     }
 
+    fn yank_main_selection_to_clipboard(editor: &mut Editor, args: &[&str], event: PromptEvent) {
+        let (view, doc) = current!(editor);
+
+        // TODO: currently the main selection is the first one. This needs to be revisited later.
+        let range = doc
+            .selection(view.id)
+            .ranges()
+            .first()
+            .expect("at least one selection");
+
+        let value = range.fragment(doc.text().slice(..));
+
+        if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) {
+            log::error!("Couldn't set system clipboard content: {:?}", e);
+        }
+
+        editor.set_status("yanked main selection to system clipboard".to_owned());
+    }
+
+    fn yank_joined_to_clipboard(editor: &mut Editor, args: &[&str], event: PromptEvent) {
+        let (view, doc) = current!(editor);
+
+        let values: Vec<String> = doc
+            .selection(view.id)
+            .fragments(doc.text().slice(..))
+            .map(Cow::into_owned)
+            .collect();
+
+        let msg = format!(
+            "joined and yanked {} selection(s) to system clipboard",
+            values.len(),
+        );
+
+        let joined = values.join("\n");
+
+        if let Err(e) = editor.clipboard_provider.set_contents(joined) {
+            log::error!("Couldn't set system clipboard content: {:?}", e);
+        }
+
+        editor.set_status(msg);
+    }
+
+    fn paste_clipboard_impl(editor: &mut Editor, action: Paste) {
+        let (view, doc) = current!(editor);
+
+        match editor
+            .clipboard_provider
+            .get_contents()
+            .map(|contents| paste_impl(&[contents], doc, view, action))
+        {
+            Ok(Some(transaction)) => {
+                doc.apply(&transaction, view.id);
+                doc.append_changes_to_history(view.id);
+            }
+            Ok(None) => {}
+            Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
+        }
+    }
+
+    fn paste_clipboard_after(editor: &mut Editor, _: &[&str], _: PromptEvent) {
+        paste_clipboard_impl(editor, Paste::After);
+    }
+
+    fn paste_clipboard_before(editor: &mut Editor, args: &[&str], event: PromptEvent) {
+        paste_clipboard_impl(editor, Paste::After);
+    }
+
+    fn replace_selections_with_clipboard(editor: &mut Editor, args: &[&str], event: PromptEvent) {
+        let (view, doc) = current!(editor);
+
+        match editor.clipboard_provider.get_contents() {
+            Ok(contents) => {
+                let transaction =
+                    Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
+                        let max_to = doc.text().len_chars().saturating_sub(1);
+                        let to = std::cmp::min(max_to, range.to() + 1);
+                        (range.from(), to, Some(contents.as_str().into()))
+                    });
+
+                doc.apply(&transaction, view.id);
+                doc.append_changes_to_history(view.id);
+            }
+            Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
+        }
+    }
+
+    fn show_clipboard_provider(editor: &mut Editor, _: &[&str], _: PromptEvent) {
+        editor.set_status(editor.clipboard_provider.name().into());
+    }
+
     pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
         TypableCommand {
             name: "quit",
@@ -1400,7 +1490,48 @@ mod cmd {
             fun: theme,
             completer: Some(completers::theme),
         },
-
+        TypableCommand {
+            name: "clipboard-yank",
+            alias: None,
+            doc: "Yank main selection into system clipboard.",
+            fun: yank_main_selection_to_clipboard,
+            completer: None,
+        },
+        TypableCommand {
+            name: "clipboard-yank-join",
+            alias: None,
+            doc: "Yank joined selections into system clipboard.",
+            fun: yank_joined_to_clipboard,
+            completer: None,
+        },
+        TypableCommand {
+            name: "clipboard-paste-after",
+            alias: None,
+            doc: "Paste system clipboard after selections.",
+            fun: paste_clipboard_after,
+            completer: None,
+        },
+        TypableCommand {
+            name: "clipboard-paste-before",
+            alias: None,
+            doc: "Paste system clipboard before selections.",
+            fun: paste_clipboard_before,
+            completer: None,
+        },
+        TypableCommand {
+            name: "clipboard-paste-replace",
+            alias: None,
+            doc: "Replace selections with content of system clipboard.",
+            fun: replace_selections_with_clipboard,
+            completer: None,
+        },
+        TypableCommand {
+            name: "show-clipboard-provider",
+            alias: None,
+            doc: "Show clipboard provider name in status bar.",
+            fun: show_clipboard_provider,
+            completer: None,
+        },
     ];
 
     pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml
index 7f18e9a2..8d93d2d9 100644
--- a/helix-view/Cargo.toml
+++ b/helix-view/Cargo.toml
@@ -34,3 +34,6 @@ slotmap = "1"
 serde = { version = "1.0", features = ["derive"] }
 toml = "0.5"
 log = "~0.4"
+
+which = "4.1"
+
diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs
new file mode 100644
index 00000000..dcc44340
--- /dev/null
+++ b/helix-view/src/clipboard.rs
@@ -0,0 +1,193 @@
+// Implementation reference: https://github.com/neovim/neovim/blob/f2906a4669a2eef6d7bf86a29648793d63c98949/runtime/autoload/provider/clipboard.vim#L68-L152
+
+use anyhow::Result;
+use std::borrow::Cow;
+
+pub trait ClipboardProvider: std::fmt::Debug {
+    fn name(&self) -> Cow<str>;
+    fn get_contents(&self) -> Result<String>;
+    fn set_contents(&self, contents: String) -> Result<()>;
+}
+
+macro_rules! command_provider {
+    (paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; ) => {{
+        Box::new(provider::CommandProvider {
+            get_cmd: provider::CommandConfig {
+                prg: $get_prg,
+                args: &[ $( $get_arg ),* ],
+            },
+            set_cmd: provider::CommandConfig {
+                prg: $set_prg,
+                args: &[ $( $set_arg ),* ],
+            },
+        })
+    }};
+}
+
+pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
+    // TODO: support for user-defined provider, probably when we have plugin support by setting a
+    // variable?
+
+    if exists("pbcopy") && exists("pbpaste") {
+        command_provider! {
+            paste => "pbpaste";
+            copy => "pbcopy";
+        }
+    } else if env_var_is_set("WAYLAND_DISPLAY") && exists("wl-copy") && exists("wl-paste") {
+        command_provider! {
+            paste => "wl-paste", "--no-newline";
+            copy => "wl-copy", "--foreground", "--type", "text/plain";
+        }
+    } else if env_var_is_set("DISPLAY") && exists("xclip") {
+        command_provider! {
+            paste => "xclip", "-o", "-selection", "clipboard";
+            copy => "xclip", "-i", "-selection", "clipboard";
+        }
+    } else if env_var_is_set("DISPLAY") && exists("xsel") && is_exit_success("xsel", &["-o", "-b"])
+    {
+        // FIXME: check performance of is_exit_success
+        command_provider! {
+            paste => "xsel", "-o", "-b";
+            copy => "xsel", "--nodetach", "-i", "-b";
+        }
+    } else if exists("lemonade") {
+        command_provider! {
+            paste => "lemonade", "paste";
+            copy => "lemonade", "copy";
+        }
+    } else if exists("doitclient") {
+        command_provider! {
+            paste => "doitclient", "wclip", "-r";
+            copy => "doitclient", "wclip";
+        }
+    } else if exists("win32yank.exe") {
+        // FIXME: does it work within WSL?
+        command_provider! {
+            paste => "win32yank.exe", "-o", "--lf";
+            copy => "win32yank.exe", "-i", "--crlf";
+        }
+    } else if exists("termux-clipboard-set") && exists("termux-clipboard-get") {
+        command_provider! {
+            paste => "termux-clipboard-get";
+            copy => "termux-clipboard-set";
+        }
+    } else if env_var_is_set("TMUX") && exists("tmux") {
+        command_provider! {
+            paste => "tmux", "save-buffer", "-";
+            copy => "tmux", "load-buffer", "-";
+        }
+    } else {
+        Box::new(provider::NopProvider)
+    }
+}
+
+fn exists(executable_name: &str) -> bool {
+    which::which(executable_name).is_ok()
+}
+
+fn env_var_is_set(env_var_name: &str) -> bool {
+    std::env::var_os(env_var_name).is_some()
+}
+
+fn is_exit_success(program: &str, args: &[&str]) -> bool {
+    std::process::Command::new(program)
+        .args(args)
+        .output()
+        .ok()
+        .and_then(|out| out.status.success().then(|| ())) // TODO: use then_some when stabilized
+        .is_some()
+}
+
+mod provider {
+    use super::ClipboardProvider;
+    use anyhow::{bail, Context as _, Result};
+    use std::borrow::Cow;
+
+    #[derive(Debug)]
+    pub struct NopProvider;
+
+    impl ClipboardProvider for NopProvider {
+        fn name(&self) -> Cow<str> {
+            Cow::Borrowed("none")
+        }
+
+        fn get_contents(&self) -> Result<String> {
+            Ok(String::new())
+        }
+
+        fn set_contents(&self, _: String) -> Result<()> {
+            Ok(())
+        }
+    }
+
+    #[derive(Debug)]
+    pub struct CommandConfig {
+        pub prg: &'static str,
+        pub args: &'static [&'static str],
+    }
+
+    impl CommandConfig {
+        fn execute(&self, input: Option<&str>, pipe_output: bool) -> Result<Option<String>> {
+            use std::io::Write;
+            use std::process::{Command, Stdio};
+
+            let stdin = input.map(|_| Stdio::piped()).unwrap_or_else(Stdio::null);
+            let stdout = pipe_output.then(Stdio::piped).unwrap_or_else(Stdio::null);
+
+            let mut child = Command::new(self.prg)
+                .args(self.args)
+                .stdin(stdin)
+                .stdout(stdout)
+                .stderr(Stdio::null())
+                .spawn()?;
+
+            if let Some(input) = input {
+                let mut stdin = child.stdin.take().context("stdin is missing")?;
+                stdin
+                    .write_all(input.as_bytes())
+                    .context("couldn't write in stdin")?;
+            }
+
+            // TODO: add timer?
+            let output = child.wait_with_output()?;
+
+            if !output.status.success() {
+                bail!("clipboard provider {} failed", self.prg);
+            }
+
+            if pipe_output {
+                Ok(Some(String::from_utf8(output.stdout)?))
+            } else {
+                Ok(None)
+            }
+        }
+    }
+
+    #[derive(Debug)]
+    pub struct CommandProvider {
+        pub get_cmd: CommandConfig,
+        pub set_cmd: CommandConfig,
+    }
+
+    impl ClipboardProvider for CommandProvider {
+        fn name(&self) -> Cow<str> {
+            if self.get_cmd.prg != self.set_cmd.prg {
+                Cow::Owned(format!("{}+{}", self.get_cmd.prg, self.set_cmd.prg))
+            } else {
+                Cow::Borrowed(self.get_cmd.prg)
+            }
+        }
+
+        fn get_contents(&self) -> Result<String> {
+            let output = self
+                .get_cmd
+                .execute(None, true)?
+                .context("output is missing")?;
+            Ok(output)
+        }
+
+        fn set_contents(&self, value: String) -> Result<()> {
+            self.set_cmd.execute(Some(&value), false).map(|_| ())
+        }
+    }
+}
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 35a547ad..5d18030a 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -1,3 +1,4 @@
+use crate::clipboard::{get_clipboard_provider, ClipboardProvider};
 use crate::{
     theme::{self, Theme},
     tree::Tree,
@@ -14,9 +15,10 @@ use slotmap::SlotMap;
 
 use anyhow::Error;
 
+use helix_core::Position;
+
 pub use helix_core::diagnostic::Severity;
 pub use helix_core::register::Registers;
-use helix_core::Position;
 
 #[derive(Debug)]
 pub struct Editor {
@@ -27,6 +29,7 @@ pub struct Editor {
     pub registers: Registers,
     pub theme: Theme,
     pub language_servers: helix_lsp::Registry,
+    pub clipboard_provider: Box<dyn ClipboardProvider>,
 
     pub syn_loader: Arc<syntax::Loader>,
     pub theme_loader: Arc<theme::Loader>,
@@ -62,6 +65,7 @@ impl Editor {
             syn_loader: config_loader,
             theme_loader: themes,
             registers: Registers::default(),
+            clipboard_provider: get_clipboard_provider(),
             status_msg: None,
         }
     }
diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs
index 20613451..17f415fc 100644
--- a/helix-view/src/lib.rs
+++ b/helix-view/src/lib.rs
@@ -1,6 +1,7 @@
 #[macro_use]
 pub mod macros;
 
+pub mod clipboard;
 pub mod document;
 pub mod editor;
 pub mod register_selection;