Read and Write Data from the Clipboard in R with clipr

Copy a range from Excel or Google Sheets straight into an R data frame — and copy R output back — without ever saving a file

Programming

Move small tables between Excel or Google Sheets and R without a file, using the clipr package: read_clip() and read_clip_tbl() to paste a copied range into a data frame, write_clip() to copy an R result back, the clipr_available() guard for scripts, and the base-R fallbacks (pbpaste/pbcopy, readClipboard/writeClipboard, xclip/xsel) on macOS, Windows, and Linux.

Author
Published

July 13, 2026

Modified

July 13, 2026

A data-flow diagram: a spreadsheet range on the left flows through the system clipboard in the centre to an R data frame on the right, showing how clipr bridges Excel and R.

TipWhat you’ll learn
  • Paste a copied Excel or Google Sheets range straight into an R data frame with clipr — no .csv, no file dialog.
  • Read raw clipboard text with read_clip() and a table with read_clip_tbl(); copy an R result back to the clipboard with write_clip().
  • Guard scripts with clipr_available() so they never hang on a machine with no clipboard.
  • The cross-platform base-R fallbackspbpaste/pbcopy (macOS), readClipboard/writeClipboard (Windows), xclip/xsel (Linux) — when you can’t add a dependency.

You have a small table sitting in Excel or Google Sheets and you just want it in R — right now, without saving a file, opening it, and deleting it afterward. The clipr package (short for clipboard reader) turns the system clipboard into a one-line bridge: copy the range in your spreadsheet, call read_clip_tbl(), and you have a data frame. Going the other way, write_clip() puts an R result on the clipboard so you can paste it back into a cell.

This is the fastest way to move a small table around during interactive work. (For anything you’ll reuse or share, save a real file — see the Import & Export Data lessons.)

Note

This is a static reference — and it has to be. The clipboard is a feature of your desktop session, so clipr only works in an interactive R session on your own machine. A headless server, a knit/render, or a CI runner (CI = continuous integration, the automated build servers that check code) has no clipboard — there clipr_available() returns FALSE and the read/write calls do nothing. That is why the clipr blocks below are shown as copy-paste-ready code with example output as text, not run live on this page. Copy any block and run it in your own interactive R session to reproduce it. The one block that does execute here (the read.table() parse demo) needs no clipboard.

Install and load clipr

clipr is on CRAN (the Comprehensive R Archive Network, R’s official package repository). Install it once, then load it in each session:

install.packages("clipr")
library(clipr)
Warning

Linux needs one system tool. On macOS and Windows the clipboard works out of the box. On Linux, clipr relies on either xclip or xsel, which you install with your system package manager — for example sudo apt-get install xclip on Debian/Ubuntu. Without one of them, clipr can’t reach the clipboard.

Read whatever is on the clipboard

read_clip() returns the raw clipboard contents as a character vector, one element per line. It is the low-level reader — use it when the clipboard holds plain text (a list of names, a column of IDs) rather than a full table.

# First copy some text (e.g. a column from a spreadsheet), then:
my_text <- read_clip()
my_text
#> [1] "Alice"   "Bob"     "Carol"   "Dan"

If the clipboard is empty, read_clip() returns NULL with a warning — so a script can test the result before using it.

Copy a table from Excel or Sheets into R

The function you’ll reach for most is read_clip_tbl(). It hands the clipboard text to base R’s read.table() and returns a data frame, so a copied spreadsheet range lands in R with its columns intact.

The workflow is two steps:

  1. In Excel or Google Sheets, select the range including the header row and press Ctrl + C (Cmd + C on macOS).
  2. In R, call read_clip_tbl():
my_data <- read_clip_tbl()
my_data
#>              name  mpg cyl disp  hp drat
#> 1       Mazda RX4 21.0   6  160 110 3.90
#> 2   Mazda RX4 Wag 21.0   6  160 110 3.90
#> 3      Datsun 710 22.8   4  108  93 3.85
#> 4  Hornet 4 Drive 21.4   6  258 110 3.08
#> 5         Valiant 18.1   6  225 105 2.76

By default read_clip_tbl() assumes the copied block is tab-separated with a header row (sep = "\t", header = TRUE, stringsAsFactors = FALSE) — exactly what a spreadsheet puts on the clipboard. Any extra argument is passed straight through to read.table(), so you can override the defaults when the copied text is unusual:

# A block with no header row, and commas instead of tabs:
my_data <- read_clip_tbl(header = FALSE, sep = ",")

See the exact parse — without a clipboard

Because read_clip_tbl() is just read.table() fed from the clipboard, you can reproduce the exact parsing here, with no clipboard involved, by feeding read.table() the same tab-separated text that copying a spreadsheet range would place on it. This block runs live:

# The tab-separated ("\t") text that copying this Excel range would put on the clipboard.
# read_clip_tbl() would hand this same string to read.table() for you.
excel_block <- "name\tmpg\tcyl\tdisp\thp
Mazda RX4\t21.0\t6\t160\t110
Datsun 710\t22.8\t4\t108\t93
Hornet 4 Drive\t21.4\t6\t258\t110
Valiant\t18.1\t6\t225\t105"

my_data <- read.table(text = excel_block, header = TRUE, sep = "\t",
                      stringsAsFactors = FALSE)
my_data
            name  mpg cyl disp  hp
1      Mazda RX4 21.0   6  160 110
2     Datsun 710 22.8   4  108  93
3 Hornet 4 Drive 21.4   6  258 110
4        Valiant 18.1   6  225 105

The result is a genuine data frame — and str() confirms the numeric columns came through as numbers, not text, so you can compute on them straight away:

str(my_data)
'data.frame':   4 obs. of  5 variables:
 $ name: chr  "Mazda RX4" "Datsun 710" "Hornet 4 Drive" "Valiant"
 $ mpg : num  21 22.8 21.4 18.1
 $ cyl : int  6 4 6 6
 $ disp: int  160 108 258 225
 $ hp  : int  110 93 110 105

That is precisely what read_clip_tbl() returns after you copy a range in Excel; it simply reads the clipboard for you instead of you typing the text into text =.

Write data from R back to the clipboard

write_clip() copies an R object onto the clipboard so you can paste it into a spreadsheet, an email, or a chat. Pass a data frame and it writes tab-separated text that drops neatly into cells:

# Put a data frame on the clipboard, then paste into Excel with Ctrl + V:
write_clip(mtcars)

By default write_clip() guesses the object type (object_type = "auto"): a data frame or matrix is written as a table, and a plain vector is written as lines of text. For a data frame it uses write.table() under the hood, so row names are included — pass row.names = FALSE (forwarded to write.table()) if you don’t want them:

write_clip(mtcars, row.names = FALSE)

A character vector is written one element per line:

write_clip(c("Alpha", "Bravo", "Charlie"))
# Clipboard now holds:
# Alpha
# Bravo
# Charlie

Capture and customise what gets written

write_clip() invisibly returns its input, so it slots into a pipe without breaking the chain. To capture the exact string it placed on the clipboard, set return_new = TRUE; to change how vector elements are joined, set breaks:

# Return the string that was written (instead of the original object):
cb <- write_clip(c("Text", "for", "clipboard"), return_new = TRUE)
cb
#> [1] "Text\nfor\nclipboard"

# Join the elements with ", " instead of line breaks:
cb <- write_clip(c("Text", "for", "clipboard"), breaks = ", ", return_new = TRUE)
cb
#> [1] "Text, for, clipboard"

Since version 0.5.0, write_clip() will not touch the clipboard in a non-interactive session (a script run via Rscript, a knit, or CI) unless you explicitly opt in by setting the environment variable CLIPR_ALLOW=TRUE. This is a safety guard so an automated job never silently overwrites whatever a user had copied. In normal interactive use you never see it; in a script, set the variable (Sys.setenv(CLIPR_ALLOW = TRUE)) only if you truly mean to write. See the clipr documentation for details.

Guard your scripts with clipr_available()

Any script that reads or writes the clipboard should first check that a clipboard actually exists — otherwise it errors on a headless server or a colleague’s Linux box without xclip. clipr_available() returns TRUE or FALSE, so you can branch cleanly:

if (clipr_available()) {
  my_data <- read_clip_tbl()
} else {
  message("No clipboard here — falling back to a file.")
  my_data <- read.csv("data.csv")
}

For a one-off diagnosis of why the clipboard is unavailable, dr_clipr() prints a short report (missing xclip/xsel, a non-interactive session, and so on).

No dependency? The base-R fallbacks

If you can’t add a package — a locked-down environment, someone else’s machine — R can still reach the clipboard on macOS and Windows with built-in connections. clipr exists precisely because these differ per operating system and Linux has none built in; the table shows what clipr papers over:

Operating system Read the clipboard Write the clipboard
macOS pipe("pbpaste") pipe("pbcopy", "w")
Windows readClipboard(), or "clipboard" as a file writeClipboard(), or "clipboard" as a file
Linux none built in — needs xclip/xsel (use clipr) none built in — needs xclip/xsel (use clipr)

macOS — read a copied table and write one back through the pbpaste/pbcopy command-line tools:

# Read a copied table:
my_data <- read.table(pipe("pbpaste"), sep = "\t", header = TRUE)

# Write a data frame to the clipboard:
clip <- pipe("pbcopy", "w")
write.table(mtcars, file = clip, sep = "\t", row.names = FALSE)
close(clip)

Windows — base R treats "clipboard" as a file connection, and utils adds dedicated helpers:

# Read a copied table:
my_data <- read.table("clipboard", sep = "\t", header = TRUE)

# Write a data frame to the clipboard:
write.table(mtcars, "clipboard", sep = "\t", row.names = FALSE)

# Or the character-vector helpers:
lines <- readClipboard()
writeClipboard(c("Alpha", "Bravo", "Charlie"))

These work, but they are OS-specific — which is the whole reason clipr wraps them behind one set of functions that behave the same everywhere.

The round-trip workflow

Put the pieces together and the clipboard becomes a two-way scratchpad between your spreadsheet and R:

  1. In Excel/Sheets, copy the range (Ctrl/Cmd + C).
  2. In R, my_data <- read_clip_tbl() — now it’s a data frame.
  3. Do the work — filter, summarise, model, plot.
  4. write_clip(result) — the result is back on the clipboard.
  5. Switch to the spreadsheet and paste (Ctrl/Cmd + V).

No file is ever created. It is perfect for quick, throwaway moves during interactive analysis — and the moment the data matters enough to keep, save it to a real file instead (see Read Excel Files in R).

Common issues

  • read_clip_tbl() returns one big column, or misaligned columns. The copied block isn’t tab-separated the way read.table() expects (common when the source used commas or multiple spaces). Set the right separator: read_clip_tbl(sep = ","), or read the raw lines with read_clip() and parse them yourself.
  • Error: Clipboard on X11 requires that the DISPLAY envvar be configured — or nothing happens on a server. There is no clipboard in that session. This is expected on headless machines and CI. Guard with clipr_available() and fall back to a file.
  • write_clip() seems to do nothing in a script. In a non-interactive session it refuses to write unless CLIPR_ALLOW=TRUE is set — a deliberate safety guard, not a bug (see the note above).

Frequently asked questions

read_clip() returns the raw clipboard contents as a character vector, one element per line — use it for plain text such as a single column of values. read_clip_tbl() passes that text to read.table() and returns a data frame, so it’s the right choice when you’ve copied a rectangular range (with a header) out of Excel or Google Sheets.

Knitting, rendering, and CI all run in a non-interactive session with no clipboard, so clipr_available() is FALSE and the read/write calls do nothing (or error). The clipboard is a feature of your live desktop session — clipr is an interactive tool. For reproducible documents, read from and write to real files instead.

Select the range in Excel (including the header row), press Ctrl + C (Cmd + C on macOS), then in R run my_data <- clipr::read_clip_tbl(). Because a spreadsheet copies its cells as tab-separated text with a header, read_clip_tbl()’s defaults parse it straight into a data frame — no .csv needed.

On macOS and Windows, base R can reach the clipboard directly (pipe("pbpaste")/pipe("pbcopy") and readClipboard()/writeClipboard() respectively). On Linux there is no built-in clipboard connection — you need xclip or xsel. clipr exists to hide these per-OS differences behind one consistent set of functions, so the same code runs everywhere; use it when you want portable code, and the base-R connections when you can’t add a dependency.

Yes — clipr::write_clip(my_df) writes the data frame to the clipboard as tab-separated text; switch to Excel and paste with Ctrl + V and it fills the cells. Add row.names = FALSE if you don’t want R’s row names to become a leading column.

Going deeper in /learn

The clipboard is great for quick, throwaway moves. For data you’ll keep, reuse, or share, save a real file — the step-by-step lessons cover every format:

Citation

BibTeX citation:
@online{kassambara2026,
  author = {Kassambara, Alboukadel},
  title = {Read and {Write} {Data} from the {Clipboard} in {R} with
    Clipr},
  date = {2026-07-13},
  url = {https://www.datanovia.com/blog/read-write-clipboard-in-r-clipr},
  langid = {en}
}
For attribution, please cite this work as:
Kassambara, Alboukadel. 2026. “Read and Write Data from the Clipboard in R with Clipr.” July 13. https://www.datanovia.com/blog/read-write-clipboard-in-r-clipr.