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.

The clipboard is the operating system’s shared, temporary storage for copied data. Reading it from a Tauri app gives you access to whatever the user last copied — a snippet of text, an image, or in some cases formatted content — directly from your React frontend. This page covers the two read operations provided by the @tauri-apps/plugin-clipboard-manager plugin: readText and readImage. For writing, see Writing Clipboard. You will also learn how to handle an empty clipboard, what permissions are required, and how to build a practical component that displays the result.

Reading Text from the Clipboard

The function readText returns the current text content of the clipboard as a string. If the clipboard is empty or contains something that is not plain text (like an image), it resolves to null. The call is asynchronous and must be awaited.

src/App.tsx
import { useState } from "react";
import { readText } from "@tauri-apps/plugin-clipboard-manager";
function App() {
  const [clipboardText, setClipboardText] = useState<string | null>(null);
  const handleReadClipboard = async () => {
    try {
      const text = await readText();
      setClipboardText(text);
    } catch (error) {
      console.error("Failed to read clipboard:", error);
      setClipboardText(null);
    }
  };
  return (
    <div>
      <button onClick={handleReadClipboard}>Read Clipboard Text</button>
      {clipboardText !== null && (
        <p>Clipboard content: {clipboardText || "(empty string)"}</p>
      )}
      {clipboardText === null && (
        <p>Clipboard does not contain text or an error occurred.</p>
      )}
    </div>
  );
}
export default App;

The component stores the result in state and updates it whenever the user clicks the button. readText can throw if the required permission is missing or if the underlying OS call fails, so the call is wrapped in a try-catch. When the function resolves, the state is set to the returned string (which may be an empty string) or to null if the clipboard held no text.

Empty string vs. null:

readText returns null when the clipboard is empty or contains non-text data. It returns an empty string ("") when the clipboard did contain text, but that text was empty (possible after copying an empty selection). Your UI should handle both cases to avoid confusing the user.

Handling an Empty Clipboard

A common real-world scenario is reading the clipboard when nothing has been copied yet, or when the user copied an image or a file. In both cases readText returns null. The example above already shows a basic check: if clipboardText === null the component displays a message that no text is available. For a production application you might instead disable the “read” button until you know the clipboard has changed, but that requires a clipboard monitor which the official plugin does not provide directly (you would need a platform-specific solution or a polling loop). The safest approach is to call readText only in response to a deliberate user action and handle the null result gracefully.

Reading Images from the Clipboard

The readImage function returns the raw binary data of the image currently on the clipboard, or null if the clipboard does not contain image data. The data is returned as a Uint8Array. To display the image in the browser you need to convert it to a format that an <img> element can consume, typically a blob URL or a data URI.

src/App.tsx
import { useState } from "react";
import { readImage } from "@tauri-apps/plugin-clipboard-manager";
function App() {
  const [imageUrl, setImageUrl] = useState<string | null>(null);
  const handleReadImage = async () => {
    try {
      const imageBytes = await readImage();
      if (imageBytes && imageBytes.length > 0) {
        const blob = new Blob([imageBytes], { type: "image/png" });
        const url = URL.createObjectURL(blob);
        setImageUrl(url);
      } else {
        setImageUrl(null);
      }
    } catch (error) {
      console.error("Failed to read image:", error);
      setImageUrl(null);
    }
  };
  return (
    <div>
      <button onClick={handleReadImage}>Read Clipboard Image</button>
      {imageUrl && <img src={imageUrl} alt="Clipboard content" style={{ maxWidth: "400px" }} />}
      {!imageUrl && <p>No image found on clipboard.</p>}
    </div>
  );
}
export default App;

The Blob constructor expects a MIME type. Here "image/png" is hardcoded, which works because the browser can often infer the correct format from the binary data. For production code you might want to detect the actual type (e.g., by examining the first few bytes) or simply set a generic image/* MIME type, though browser support for the latter varies. A safer alternative is to avoid Blob entirely and build a data URI:

const base64 = btoa(String.fromCharCode(...imageBytes));
setImageUrl(`data:image/png;base64,${base64}`);

This approach works everywhere, but it can be slow for large images because it forces the entire image into a base64 string and embeds it in the page. Use the blob URL method when performance matters.

Multiple image formats:

The clipboard may contain images in different formats (PNG, JPEG, BMP, etc.). The readImage function returns the raw bytes without telling you the format. If your application needs to handle multiple formats reliably, you should inspect the first few bytes of the returned array to identify the file signature, then set the MIME type accordingly.

Validation and Error Handling

Both readText and readImage can throw for reasons unrelated to the clipboard content:

  • Missing permissions – if you haven’t added the clipboard-manager:allow-read-text or clipboard-manager:allow-read-image permission to your capability file, the call will fail with a permission error.
  • Platform restrictions – on some Linux configurations or Wayland sessions, clipboard access may be limited or require additional setup.
  • Plugin not initialized – if the clipboard plugin is not registered in lib.rs (see the introduction page of this chapter), any call will throw.

Always wrap read operations in try-catch and present a user-friendly error message. Do not assume the clipboard contains what you expect. For example, a user may click “Read Image” when the clipboard actually holds a file path string, not binary image data.

The next example combines text and image reading with error feedback:

src/App.tsx
import { useState } from "react";
import { readText, readImage } from "@tauri-apps/plugin-clipboard-manager";
function App() {
  const [result, setResult] = useState<string | null>(null);
  const [imageUrl, setImageUrl] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
  const readClipboardText = async () => {
    try {
      setError(null);
      const text = await readText();
      setResult(text);
    } catch (e: any) {
      setError(e?.message ?? "Unknown error");
    }
  };
  const readClipboardImage = async () => {
    try {
      setError(null);
      const bytes = await readImage();
      if (bytes && bytes.length > 0) {
        const blob = new Blob([bytes], { type: "image/png" });
        setImageUrl(URL.createObjectURL(blob));
      } else {
        setImageUrl(null);
        setError("Clipboard does not contain an image.");
      }
    } catch (e: any) {
      setError(e?.message ?? "Unknown error");
    }
  };
  return (
    <div>
      <button onClick={readClipboardText}>Read Text</button>
      <button onClick={readClipboardImage}>Read Image</button>
      {error && <p style={{ color: "red" }}>{error}</p>}
      {result !== null && <p>Text: {result ?? "(empty)"}</p>}
      {imageUrl && <img src={imageUrl} alt="Clipboard" style={{ maxWidth: "400px" }} />}
    </div>
  );
}
export default App;

Permissions for Reading the Clipboard

The clipboard plugin ships with no default permissions. To read from the clipboard you must explicitly grant the relevant commands in your capability file. Add the following entries inside the permissions array of src-tauri/capabilities/default.json:

src-tauri/capabilities/default.json
{
  "identifier": "default",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "clipboard-manager:allow-read-text",
    "clipboard-manager:allow-read-image"
  ]
}

If you only need text access, omit the image permission. Conversely, if you read images but never text, you can skip the text permission. This follows the principle of least privilege.

Missing permissions cause runtime errors:

Calling readText without clipboard-manager:allow-read-text — or readImage without clipboard-manager:allow-read-image — will throw an error with a message indicating the command is not allowed. The fix is always to update the capability file and restart the app.

Permissions working correctly:

After adding the read permissions and rebuilding, your read calls should succeed whenever the clipboard contains appropriate data. No user gesture or additional prompt is required; Tauri treats your application configuration as the explicit grant.

How Reading Works Under the Hood

When your React code calls readText or readImage, the Tauri plugin dispatches an IPC command to the Rust backend. The backend uses operating-system-specific APIs — Win32 clipboard functions on Windows, NSPasteboard on macOS, and either X11 or Wayland clipboard protocols on Linux — to retrieve the clipboard contents. The data is then serialized and sent back to the frontend as a JavaScript value. This design means:

  • Clipboard reads happen synchronously from the OS perspective, but they return a Promise in JavaScript so the UI remains responsive.
  • The clipboard content you get is a snapshot taken at the moment of the call. If the user copies something new while you’re still processing the previous read, you will not see the newer data until you call the function again.
  • Because the clipboard is a system-wide shared resource, your app can read anything another application placed there. This is both powerful and a security consideration — avoid reading the clipboard automatically without the user’s knowledge.

Common Mistakes

  • Assuming the clipboard always contains text – many applications copy images or formatted data. Always check for null after readText.
  • Forgetting to add permissions – the error message can be cryptic if you skip this step. Verify your capability file first when a read call fails.
  • Using readImage without checking the returned length – an empty Uint8Array is not null, but it contains no usable image data. Guard with imageBytes && imageBytes.length > 0.
  • Leaking object URLs – every call to URL.createObjectURL should eventually be paired with URL.revokeObjectURL to free memory. In the examples above, the URLs are short-lived, but for long-running apps this can add up. Consider revoking the previous URL before creating a new one.

No built-in clipboard change events:

Tauri’s official clipboard plugin does not emit events when the clipboard content changes. If your app needs to react immediately to a new copy (for example, to update a preview automatically), you must either poll the clipboard at a short interval or integrate a platform-specific crate that monitors the clipboard. This is a deliberate design choice to keep the plugin small and secure; polling introduces overhead and is not recommended for battery-sensitive devices.


Summary

Reading the clipboard in Tauri v2 boils down to two functions — readText for plain text and readImage for images — each requiring an explicit permission and a null check. The API is intentionally narrow: it does not support formatted text (HTML, RTF) or file references, and it does not watch for changes. That simplicity makes the feature predictable and safe to use in a local-first desktop app.