mrkeyoor.com_
Thu 06 Aug 07:41 UTC
PyPIUtilsupdated 06 Aug 2026

pyperclip

pyperclip is a single-module package with two functions that matter: copy(text) puts a string on the system clipboard and paste() reads it back. Underneath it picks a mechanism for your platform the first time you call either one. On Windows it calls the Win32 clipboard API through ctypes with no extra install. On macOS it uses pyobjc if that happens to be importable and otherwise shells out to pbcopy and pbpaste. On Linux it looks for wl-copy and wl-paste under Wayland, then xclip, then xsel, then klipper over qdbus, and finally tries qtpy or PyQt5. WSL gets its own path. If nothing is found it installs stub functions that raise PyperclipException the moment you use them. It handles plain text only, and that is the whole library.

Verdict

For putting a string on a desktop clipboard from a Python script, it is two functions and no dependencies, and that is exactly right. Treat every call as something that can raise on Linux, and do not reach for it on a server.

API stability5/5copy() and paste() have had the same signatures for over a decade and the public surface is still just four names; recent releases added backends such as wl-clipboard and WSL support without changing anything callers already wrote
Docs3/5There is a Read the Docs site and the module docstring is unusually candid, listing every platform requirement and a security note about the binaries it runs; the README is nine lines of example, and behaviors like lazy backend detection and is_available() are only visible if you read the source
Maintenance2/51.11.0 shipped 2025-09-26 and the repository has not been pushed since that same day, with around 116 issues open and a single author; the code is small and stable enough that this is survivable, but new platform quirks are unlikely to get fixed quickly
Ecosystem4/5Around 25M downloads a week, largely as a transitive dependency of CLI and automation tooling, and it is the answer every Stack Overflow clipboard question in Python gets; the space itself is thin, with pyclip and klembord orders of magnitude smaller

Use it if

  • You are writing a desktop-side CLI or script and want a one-liner to hand a result to the user's clipboard instead of making them select terminal output
  • You need it to work on Windows, macOS, and a desktop Linux without shipping platform branches, and you accept a runtime error where no clipboard exists
  • You want zero dependencies: the package is a single pure-Python file with no install-time requirements at all
  • You are teaching or prototyping, where import pyperclip and two function calls is the entire mental model
  • You want a clipboard from the shell too, since python -m pyperclip -c reads stdin onto the clipboard and -p writes it back out
Skip it if

Setup reality

pip install pyperclip finishes instantly and installs one file with no dependencies. On Windows that is genuinely all of it. On macOS it works out of the box because pbcopy and pbpaste are part of the OS. Linux is where the install lies to you: pip succeeds, then the first copy() raises PyperclipException telling you to apt-get install xclip, because pyperclip needs an external binary it cannot declare as a Python dependency. Which one depends on the session, since Wayland wants wl-clipboard while X11 wants xclip or xsel, and pyperclip decides at runtime by checking WAYLAND_DISPLAY and DISPLAY. Headless environments have neither, so a Docker image or a CI job needs xvfb plus xclip if you truly must exercise this path, and skipping the test is usually the better answer. Two more details worth planning for. Detection is lazy, so copy and paste are stub functions until the first call, which means an import-time availability check has to call is_available() or determine_clipboard() explicitly. And on X11 the clipboard is owned by a process rather than the system, so text copied by a short-lived script can vanish when the owner exits, which is a property of X11 and not something pyperclip can fix.

Patterns

Put text on the clipboard and read it backcopy-and-paste

import pyperclip

pyperclip.copy('the text to be copied')
print(pyperclip.paste())
# 'the text to be copied'

The backend is chosen lazily on this first call, not at import, so the import itself never fails even on a machine with no clipboard at all.

Fail with a message instead of a tracebackhandle-no-clipboard

import sys
import pyperclip

def to_clipboard(text: str) -> bool:
    try:
        pyperclip.copy(text)
        return True
    except pyperclip.PyperclipException as exc:
        print(f'clipboard unavailable: {exc}', file=sys.stderr)
        return False

if not to_clipboard(result):
    print(result)   # fall back to stdout

PyperclipException subclasses RuntimeError. On Linux with no mechanism installed the message already tells the user to install xclip or wl-clipboard, so printing it is more useful than wrapping it. Falling back to stdout is what makes a CLI usable over SSH.

Detect clipboard support up frontcheck-availability

import pyperclip

pyperclip.determine_clipboard()      # force detection now
if not pyperclip.is_available():
    print('no clipboard on this machine; use --output instead')

is_available() just checks whether copy and paste are still the lazy-loading stubs, so it always reports True until something triggers detection. Calling determine_clipboard() first is what makes the answer meaningful.

Pin a specific clipboard mechanismforce-backend

import pyperclip

# valid: pbcopy, pyobjc, qt, xclip, xsel, wl-clipboard, klipper, windows, no
pyperclip.set_clipboard('xsel')
pyperclip.copy('hello')

# 'no' turns every call into a raising stub, handy in tests
pyperclip.set_clipboard('no')

Call this before the first copy or paste, since detection is what set_clipboard replaces. An unknown name raises ValueError listing the valid options, and picking a backend whose binary is missing fails later at call time rather than here.

Find out which mechanism was pickedinspect-backend

import pyperclip

copy_fn, paste_fn = pyperclip.determine_clipboard()
print(copy_fn.__name__)   # e.g. 'copy_xclip', 'copy_wl', 'copy_windows', 'ClipboardUnavailable'

The function name is the only handle you get on the chosen backend; there is no attribute reporting it. Worth logging in a bug report template, because 'works on my machine' clipboard problems are almost always a different backend.

Install the binary Linux actually needslinux-prerequisites

# Wayland session (echo $WAYLAND_DISPLAY is non-empty)
$ sudo apt-get install wl-clipboard

# X11 session (echo $DISPLAY is non-empty)
$ sudo apt-get install xclip     # preferred
$ sudo apt-get install xsel      # fallback

# nothing else installed? pyperclip tries qtpy, then PyQt5
$ pip install qtpy PyQt5

Detection order is wl-clipboard, then xclip, then xsel, then klipper over qdbus, then Qt. pyperclip cannot declare these as Python dependencies, which is why pip install succeeds on a box where the library cannot work.

Keep clipboard code out of CIheadless-environment

import os
import pytest
import pyperclip

pyperclip.determine_clipboard()

@pytest.mark.skipif(not pyperclip.is_available(), reason='no clipboard')
def test_copies_result():
    pyperclip.copy('x')
    assert pyperclip.paste() == 'x'

# if you really must exercise it in a container:
#   apt-get install -y xvfb xclip
#   xvfb-run -a pytest

Containers and CI runners have no DISPLAY and no WAYLAND_DISPLAY, so pyperclip lands on the stub backend and every call raises. Skipping is nearly always cheaper than standing up xvfb just to test two lines of glue.

Test the code around the clipboardmock-in-tests

from unittest import mock

def test_command_copies_output():
    buffer = {}
    with mock.patch('pyperclip.copy', side_effect=lambda t: buffer.update(text=t)), \
         mock.patch('pyperclip.paste', side_effect=lambda: buffer.get('text', '')):
        run_command(['export', '--clip'])
    assert buffer['text'].startswith('id,name')

Patch pyperclip.copy and pyperclip.paste rather than the backend, because the module rebinds those module-level names during lazy loading and a patch applied too early can be overwritten by detection.

Copy something that is not already a stringcopy-non-string

import json
import pyperclip

pyperclip.copy(json.dumps(payload, indent=2))
pyperclip.copy(str(row_count))

# pyperclip.copy(42) happens to work on most backends, but do not rely on it
# pyperclip.copy(None) puts the literal text 'None' on the clipboard

Backends call str() on whatever you pass, so nothing raises and a bug becomes the string 'None' sitting on the user's clipboard. Serialize explicitly and you keep control of the formatting.

Poll the clipboard for new contentwatch-for-changes

import time
import pyperclip

def watch(interval: float = 0.5):
    previous = pyperclip.paste()
    while True:
        current = pyperclip.paste()
        if current != previous:
            previous = current
            yield current
        time.sleep(interval)

for text in watch():
    print('clipboard changed:', text[:60])

There is no wait or notify API in 1.11.0, so polling is the whole story. On Linux and macOS each paste() spawns a subprocess, so a 0.5 second interval is roughly two process launches a second; do not tighten it much below that.

Wipe a secret off the clipboardclear-clipboard

import threading
import pyperclip

def copy_secret(secret: str, seconds: int = 20) -> None:
    pyperclip.copy(secret)

    def clear():
        if pyperclip.paste() == secret:
            pyperclip.copy('')

    threading.Timer(seconds, clear).start()

Copying an empty string is the only clear there is. The equality check avoids wiping something the user copied in the meantime. On X11 the selection lives in the process that set it, so a script that exits before the timer fires may have already lost the value anyway.

Use it as a shell commandcli-usage

$ echo 'hello' | python -m pyperclip --copy
$ python -m pyperclip --copy 'literal text'
$ python -m pyperclip --paste > out.txt

# short forms
$ cat report.csv | python -m pyperclip -c
$ python -m pyperclip -p | wc -l

The module has a small __main__, so no console script is installed and it is python -m pyperclip rather than a pyperclip command. With --copy and no argument it reads all of stdin, so pressing Ctrl-D is what ends an interactive run.

Alternatives

PackageRegistryPick it when
pyclipPyPIYou need binary clipboard data as well as text, with the same tiny cross-platform surface
klembordPyPIYou need rich content on the clipboard, such as HTML alongside a plain text fallback
pyperclip3PyPIYou want a pyperclip-shaped API that also handles bytes, without switching to a different function naming style