Clipboard API

Learn to read and write system clipboard content in Tauri v2 apps using the official clipboard plugin

Tauri v2 provides a plugin to interact with the system clipboard from your React frontend — and from Rust, if you need backend logic. The clipboard is the temporary storage your operating system uses when you cut, copy, or paste. The plugin lets you write text, HTML, or images, read them back, clear the clipboard, and check what kind of content is currently stored.

The sections ahead cover installing the plugin, configuring permissions, then writing data and reading it, with full React code that you can run in a fresh Tauri v2 + Vite project. The Introduction is the focused setup walkthrough.

No browser clipboard restrictions:

Unlike the web Clipboard API, Tauri’s native clipboard plugin does not require a secure context (HTTPS), a user gesture, or explicit browser permission prompts. The plugin talks directly to the operating system’s clipboard through Tauri’s Rust backend, so the security model is controlled entirely by your Tauri capability file.

Setup — Installing the Plugin and Enabling Permissions

The clipboard plugin is not bundled by default. You need to add it as a dependency, both in Rust and in the frontend, and then grant permissions. The steps are ordered — each one depends on the previous one completing.

1

Step 1: Add the plugin to your Rust project

Run this command in the src-tauri directory to add the Rust crate:

cargo add tauri-plugin-clipboard-manager

This adds tauri-plugin-clipboard-manager to your Cargo.toml. The plugin must be initialized in Rust before any clipboard function can be called from JavaScript.

2

Step 2: Initialize the plugin in lib.rs

Open src-tauri/src/lib.rs and register the plugin inside the run function:

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_clipboard_manager::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Without this line, any call from the frontend will fail because the Tauri runtime won’t know how to handle clipboard commands.

3

Step 3: Install the frontend package

In your project root (where package.json lives), install the npm package:

npm install @tauri-apps/plugin-clipboard-manager

This package gives you the writeText, readText, and other functions you’ll import in your React components.

4

Step 4: Grant clipboard permissions

Tauri v2 uses capability files to define what each window is allowed to do. By default no clipboard operations are enabled — you must explicitly add the permissions you need.

Locate your capability file (usually src-tauri/capabilities/default.json) and add the permissions inside the permissions array. For basic read/write of text, your file should look like this:

{
  "identifier": "default",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "clipboard-manager:allow-write-text",
    "clipboard-manager:allow-read-text"
  ]
}

Missing permissions cause runtime errors:

If you call writeText without adding the corresponding permission, Tauri will reject the operation and you’ll see an error in the console. The plugin will not prompt the user — it simply fails. Always add exactly the permissions your app uses.

You can grant permissions per operation. The full set of available identifiers:

  • clipboard-manager:allow-write-text
  • clipboard-manager:allow-read-text
  • clipboard-manager:allow-write-html
  • clipboard-manager:allow-read-html
  • clipboard-manager:allow-write-image
  • clipboard-manager:allow-read-image
  • clipboard-manager:allow-clear

Once these four steps are complete, restart your development server (npm run tauri dev). The plugin is now ready to use from React.

Writing to the Clipboard

Writing content is the most common operation — copying a code snippet, a generated link, or a piece of text the user selected. The plugin exposes separate functions for text, HTML, and images so you can put exactly the right format on the clipboard.

Writing plain text

Import writeText and call it with a string. It returns a promise, so you must await it.

import { writeText } from "@tauri-apps/plugin-clipboard-manager";
function CopyButton() {
  const handleCopy = async () => {
    await writeText("Tauri v2 clipboard works!");
  };
  return <button onClick={handleCopy}>Copy text</button>;
}
export default CopyButton;

This component renders a single button. When clicked, the async function runs writeText. If the operation succeeds, the system clipboard now holds "Tauri v2 clipboard works!" and you can paste it anywhere outside the app. If it fails — for example because you forgot to add the permission — the promise rejects and an error appears in the browser console. In a real app you’d wrap the call in a try/catch to show feedback to the user.

Clipboard writes are not visually confirmed:

The plugin does not display a toast or tooltip. You are responsible for showing a “Copied!” indication. Without it, the user has no idea whether the operation succeeded.

In Rust you can achieve the same thing from a Tauri command:

use tauri_plugin_clipboard_manager::ClipboardExt;
#[tauri::command]
fn copy_from_rust(app: tauri::AppHandle) {
    app.clipboard().write_text("Rust says hello".to_string()).unwrap();
}

The ClipboardExt trait gives the AppHandle (or App) a .clipboard() method, and write_text returns a Result. In real code you would handle the error instead of unwrapping.

Writing HTML content

Sometimes you need rich text on the clipboard, for example when pasting into an email client or a word processor. Use writeHtml. You can optionally pair it with a plain text fallback so that applications that don’t support HTML still receive readable text.

import { writeHtml } from "@tauri-apps/plugin-clipboard-manager";
async function copyRichContent() {
  const html = "<b>Bold heading</b><p>Some paragraph text</p>";
  await writeHtml(html, "Bold heading — Some paragraph text");
}

The second argument is the plain text alternative. When you paste into a plain text field, the OS will use that string. If you omit it, applications that only accept plain text might receive an empty paste. The Rust equivalent is app.clipboard().write_html(html, alt_text).

Writing images

The plugin accepts image data as a base64-encoded string. A typical flow in a React component: read a file from disk using Tauri’s file system APIs, convert the bytes to base64, then call writeImageBase64.

import { writeImageBase64 } from "@tauri-apps/plugin-clipboard-manager";
async function copyImageFromPath(path: string) {
  // Example: you already have the path from a file dialog.
  // Read the file using the fs plugin and encode it as base64.
  // For a complete example, see the Common Use Cases section.
  const base64 = await fileToBase64(path); // your helper
  await writeImageBase64(base64);
}

The image format on the clipboard is determined by the base64 content you provide. The plugin does not re-encode the data — it just places the raw bytes onto the clipboard. This means you need to supply a valid image encoding (PNG, JPEG, etc.) that the target application can decode.

Large images can be slow:

The clipboard system on some platforms has size limits or can become sluggish with multi-megabyte images. If you’re copying a high-resolution photo, consider scaling it down first or writing a thumbnail instead.

Clearing the clipboard

Call clear() to remove any content. This is useful if your app manages sensitive data and you want to offer a “clear clipboard” button after a timeout.

import { clear } from "@tauri-apps/plugin-clipboard-manager";
await clear();

clear requires the clipboard-manager:allow-clear permission.

Reading from the Clipboard

Reading is the paste side of the operation. The plugin provides functions to check what type of content is available (hasText, hasImage, hasHTML) and then to retrieve it.

Reading text

Import readText. It returns a promise that resolves to the current text on the clipboard, or an empty string if there is no text.

import { readText } from "@tauri-apps/plugin-clipboard-manager";
function PasteButton() {
  const handlePaste = async () => {
    const content = await readText();
    if (content) {
      // Insert content into a text area, log it, etc.
      console.log("Pasted text:", content);
    }
  };
  return <button onClick={handlePaste}>Paste text</button>;
}

When you press the button, the component reads whatever text the user last copied — even if it came from outside your Tauri app. The returned value is exactly what the operating system has stored as plain text.

You can guard the read with hasText() to avoid empty results:

import { hasText, readText } from "@tauri-apps/plugin-clipboard-manager";
const exists = await hasText();
if (exists) {
  const text = await readText();
  // use text
}

hasText returns a boolean. This is more of a convenience — calling readText on a clipboard that contains only an image still returns an empty string and does not throw.

Reading works cross‑application:

If you copy a line of text from your web browser or code editor, the Tauri clipboard plugin can read it. The system clipboard is shared, so your app behaves like any other paste-aware tool.

Reading HTML

readHtml returns the HTML string if the clipboard contains rich text. If only plain text is present, it returns an empty string.

import { readHtml } from "@tauri-apps/plugin-clipboard-manager";
const html = await readHtml();
if (html) {
  // Render or parse the HTML as needed
}

The corresponding check is hasHtml(). The Rust side provides app.clipboard().read_html().

Reading images

To read an image, call readImageBase64. It returns a base64 string that you can render in an <img> tag by prefixing it with data:image/png;base64,….

import { readImageBase64 } from "@tauri-apps/plugin-clipboard-manager";
const base64 = await readImageBase64();
if (base64) {
  // set image src in state
  setImageSrc(`data:image/png;base64,${base64}`);
}

Use hasImage() before reading if you want to conditionally show a “Paste image” button only when an image is actually on the clipboard.

Image format is not guaranteed:

The base64 data might not always be PNG — it could be JPEG, GIF, or another format the source application placed there. The browser can usually auto-detect the format from the data URI, but if you’re sending the bytes somewhere else, keep the original MIME type in mind.

Clipboard access from Rust

If you need to read clipboard content inside a Rust command, use the ClipboardExt trait just like for writing:

use tauri_plugin_clipboard_manager::ClipboardExt;
#[tauri::command]
fn paste_from_rust(app: tauri::AppHandle) -> Result<String, String> {
    app.clipboard().read_text().map_err(|e| e.to_string())
}

The read_text method returns Result<Option<String>>None means the clipboard holds no text, and an Err indicates a platform-level failure.

Common Use Cases

The isolated functions come together in everyday UI patterns. These examples assume the plugin is already installed and the necessary permissions are in your capability file.

A copy-to-clipboard button with visual feedback

This component copies a predefined text string and shows a confirmation message for two seconds. It handles the async nature and gracefully logs errors.

import { useState } from "react";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
function CopyCodeSnippet() {
  const [copied, setCopied] = useState(false);
  const snippet = `const greeting = "Hello, Tauri!";`;
  const handleCopy = async () => {
    try {
      await writeText(snippet);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch (error) {
      console.error("Clipboard write failed:", error);
    }
  };
  return (
    <div>
      <pre>{snippet}</pre>
      <button onClick={handleCopy}>
        {copied ? "Copied!" : "Copy to clipboard"}
      </button>
    </div>
  );
}

The critical detail here is the try/catch. If the plugin call fails — most commonly because the permission was not added — the catch block prevents an unhandled promise rejection. The user would see no feedback if we omitted it.

Pasting text into a controlled input

Reading on button press and inserting into a React input:

import { useState } from "react";
import { readText } from "@tauri-apps/plugin-clipboard-manager";
function PasteInput() {
  const [value, setValue] = useState("");
  const handlePaste = async () => {
    const text = await readText();
    if (text) {
      setValue((prev) => prev + text);
    }
  };
  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Paste something here"
      />
      <button onClick={handlePaste}>Paste from clipboard</button>
    </div>
  );
}

readText does not require the input to be focused. The plugin reads the system clipboard, not the element’s internal buffer. This means you can paste into the field even if the user had not previously copied inside your app.

Copying an image file to the clipboard

To copy an image that lives on the filesystem, you need the Tauri fs plugin to read the file bytes and then encode them as base64.

import { readFile } from "@tauri-apps/plugin-fs";
import { writeImageBase64 } from "@tauri-apps/plugin-clipboard-manager";
async function copyImageFile(filePath: string) {
  const fileBytes = await readFile(filePath);   // Uint8Array
  const base64 = arrayBufferToBase64(fileBytes);
  await writeImageBase64(base64);
}
function arrayBufferToBase64(buffer: Uint8Array) {
  let binary = "";
  buffer.forEach((byte) => (binary += String.fromCharCode(byte)));
  return btoa(binary);
}

You must have the fs plugin permissions configured as well. The base64 helper converts every byte to a character and then uses the browser’s btoa. This is standard and works for images of any size, though for large files it blocks the main thread — consider moving heavy conversion to a web worker if you routinely copy huge assets.

Clearing the clipboard after a sensitive copy

If your app copies a password or a generated token, you can offer a “clear clipboard after 30 seconds” feature:

import { writeText, clear } from "@tauri-apps/plugin-clipboard-manager";
async function copySecret(secret: string) {
  await writeText(secret);
  setTimeout(async () => {
    await clear();
  }, 30_000);
}

Make sure the allow-clear permission is in your capability file.

Combining operations:

All clipboard functions are independent and can be mixed freely. You can write an image, then later read text that someone else copied — the plugin always reflects the current system clipboard state.

The clipboard plugin fills the gap between a web app and the native desktop environment. With these patterns you can let your users copy and paste as naturally as they would in any native tool, while keeping all the control within your Tauri security model.

Introduction to the Clipboard API

Understand the system clipboard, set up the Tauri clipboard plugin, and read or write text from your React frontend

Writing Clipboard

How to programmatically copy text, HTML, and images to the system clipboard from a Tauri v2 app using the clipboard-manager plugin

Reading Clipboard

Learn how to read text and images from the system clipboard in a Tauri v2 application using the clipboard-manager plugin and a React frontend.

Common Use Cases for the Clipboard API

Practical patterns for integrating clipboard operations into a Tauri v2 app with React, covering copy buttons, share features, and productivity workflows.