$ cat /posts/tauri-20-clipboard-api-copy-and-paste-functionality.md
[tags]Tauri 2.0

Tauri 2.0 Clipboard API Copy and Paste Functionality

drwxr-xr-x2026-01-295 min0 views
Tauri 2.0 Clipboard API Copy and Paste Functionality

Clipboard API in Tauri 2.0 enables desktop applications to interact with system clipboard reading and writing text, images, and rich content providing seamless copy-paste functionality, clipboard monitoring detecting changes, temporary clipboard storage, and clipboard history management—essential feature for productivity tools, text editors, code editors, note-taking apps, and any application requiring data transfer through system clipboard maintaining user workflow efficiency. Clipboard operations combine native OS integration accessing Windows Clipboard, macOS Pasteboard, and Linux clipboard managers with security controls preventing unauthorized access, format detection supporting plain text, rich text, HTML, and images, automatic clipboard clearing for sensitive data, and clipboard event listening detecting external changes creating comprehensive clipboard management system. This comprehensive guide covers understanding clipboard architecture and security model, enabling clipboard permissions in configuration, reading text from clipboard with error handling, writing text to clipboard with validation, reading and writing images with format conversion, monitoring clipboard changes detecting external modifications, implementing clipboard history tracking past entries, building clipboard manager with search and filtering, handling rich text and HTML formats, and creating real-world examples including code snippet manager with syntax highlighting, password manager with auto-clear, and note-taking app with clipboard capture maintaining productivity through efficient clipboard workflows. Mastering clipboard patterns enables building professional desktop applications providing seamless data transfer maintaining security through permission controls while delivering clipboard functionality users expect from native applications. Before proceeding, understand command creation and event handling.

Clipboard API Setup

Clipboard API requires enabling plugin in Cargo.toml and configuring permissions in tauri.conf.json. Understanding clipboard configuration integrated with security model enables building clipboard-aware applications maintaining user privacy through explicit permission controls.

typescriptclipboard_setup.ts
// src-tauri/Cargo.toml
// Enable clipboard plugin
[dependencies]
tauri = { version = "2.0", features = ["clipboard"] }

// src-tauri/tauri.conf.json
// Configure clipboard permissions
{
  "tauri": {
    "allowlist": {
      "clipboard": {
        "all": true,
        "readText": true,
        "writeText": true
      }
    }
  }
}

// Frontend: Basic clipboard operations
import { readText, writeText } from "@tauri-apps/api/clipboard";

// Write text to clipboard
async function copyToClipboard(text: string) {
  try {
    await writeText(text);
    console.log("Text copied to clipboard");
  } catch (error) {
    console.error("Failed to copy:", error);
  }
}

// Read text from clipboard
async function pasteFromClipboard(): Promise<string | null> {
  try {
    const text = await readText();
    console.log("Text read from clipboard:", text);
    return text;
  } catch (error) {
    console.error("Failed to read clipboard:", error);
    return null;
  }
}

// Usage examples
await copyToClipboard("Hello, World!");
const clipboardContent = await pasteFromClipboard();

// Rust: Clipboard operations in commands
use tauri::ClipboardManager;

#[tauri::command]
fn copy_to_clipboard(
    app: tauri::AppHandle,
    text: String,
) -> Result<(), String> {
    app.clipboard_manager()
        .write_text(text)
        .map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn read_from_clipboard(
    app: tauri::AppHandle,
) -> Result<Option<String>, String> {
    app.clipboard_manager()
        .read_text()
        .map_err(|e| e.to_string())
}

#[tauri::command]
fn clear_clipboard(
    app: tauri::AppHandle,
) -> Result<(), String> {
    app.clipboard_manager()
        .write_text("")
        .map_err(|e| e.to_string())?;
    Ok(())
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![
            copy_to_clipboard,
            read_from_clipboard,
            clear_clipboard,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Text Copy and Paste Operations

Text operations provide basic clipboard functionality copying and pasting plain text with validation and error handling. Understanding text operations enables building reliable clipboard features maintaining data integrity through proper encoding handling and validation.

typescripttext_operations.ts
// Frontend: Advanced text clipboard utilities
import { readText, writeText } from "@tauri-apps/api/clipboard";

class ClipboardService {
  // Copy with notification
  static async copy(text: string, showNotification = true): Promise<boolean> {
    try {
      await writeText(text);
      if (showNotification) {
        this.showCopyNotification(text);
      }
      return true;
    } catch (error) {
      console.error("Copy failed:", error);
      return false;
    }
  }

  // Paste with validation
  static async paste(): Promise<string | null> {
    try {
      const text = await readText();
      return text || null;
    } catch (error) {
      console.error("Paste failed:", error);
      return null;
    }
  }

  // Copy multiple lines
  static async copyLines(lines: string[]): Promise<boolean> {
    const text = lines.join("\n");
    return this.copy(text);
  }

  // Copy with formatting preservation
  static async copyFormatted(
    text: string,
    format: "plain" | "markdown" | "html"
  ): Promise<boolean> {
    let content = text;
    
    if (format === "markdown") {
      // Add markdown formatting
      content = `\`\`\`\n${text}\n\`\`\``;
    } else if (format === "html") {
      // Add HTML formatting
      content = `<pre><code>${text}</code></pre>`;
    }
    
    return this.copy(content);
  }

  // Copy and trim whitespace
  static async copyTrimmed(text: string): Promise<boolean> {
    return this.copy(text.trim());
  }

  // Show notification
  private static showCopyNotification(text: string) {
    const preview = text.length > 50 
      ? text.substring(0, 47) + "..." 
      : text;
    console.log(`Copied: ${preview}`);
  }
}

// Usage examples
await ClipboardService.copy("Hello, World!");
await ClipboardService.copyLines(["Line 1", "Line 2", "Line 3"]);
await ClipboardService.copyFormatted("const x = 10;", "markdown");

const pastedText = await ClipboardService.paste();
if (pastedText) {
  console.log("Pasted:", pastedText);
}

// Rust: Enhanced text operations
use tauri::{AppHandle, ClipboardManager};

#[tauri::command]
fn copy_with_validation(
    app: AppHandle,
    text: String,
    max_length: Option<usize>,
) -> Result<(), String> {
    // Validate text length
    if let Some(max) = max_length {
        if text.len() > max {
            return Err(format!(
                "Text too long: {} characters (max: {})",
                text.len(),
                max
            ));
        }
    }
    
    // Validate UTF-8
    if !text.is_ascii() && text.chars().any(|c| c.is_control()) {
        return Err("Text contains invalid control characters".to_string());
    }
    
    app.clipboard_manager()
        .write_text(text)
        .map_err(|e| e.to_string())?;
    
    Ok(())
}

#[tauri::command]
fn paste_and_transform(
    app: AppHandle,
    transform: String,
) -> Result<String, String> {
    let text = app.clipboard_manager()
        .read_text()
        .map_err(|e| e.to_string())?
        .ok_or("Clipboard is empty")?;
    
    let transformed = match transform.as_str() {
        "uppercase" => text.to_uppercase(),
        "lowercase" => text.to_lowercase(),
        "trim" => text.trim().to_string(),
        "reverse" => text.chars().rev().collect(),
        _ => text,
    };
    
    Ok(transformed)
}

#[tauri::command]
fn get_clipboard_size(
    app: AppHandle,
) -> Result<usize, String> {
    let text = app.clipboard_manager()
        .read_text()
        .map_err(|e| e.to_string())?
        .unwrap_or_default();
    
    Ok(text.len())
}

Clipboard Change Monitoring

Clipboard monitoring detects external changes enabling applications to react to user copy actions. Understanding monitoring patterns enables building clipboard managers and automation tools maintaining awareness of clipboard state changes through polling or event-based detection.

rustclipboard_monitoring.rs
// Rust: Clipboard monitoring with background task
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use tauri::{AppHandle, Manager};

struct ClipboardMonitor {
    last_content: Arc<Mutex<String>>,
    is_running: Arc<Mutex<bool>>,
}

impl ClipboardMonitor {
    fn new() -> Self {
        ClipboardMonitor {
            last_content: Arc::new(Mutex::new(String::new())),
            is_running: Arc::new(Mutex::new(false)),
        }
    }
    
    fn start(&self, app: AppHandle) {
        let mut is_running = self.is_running.lock().unwrap();
        if *is_running {
            return; // Already running
        }
        *is_running = true;
        
        let last_content = Arc::clone(&self.last_content);
        let is_running_clone = Arc::clone(&self.is_running);
        
        thread::spawn(move || {
            while *is_running_clone.lock().unwrap() {
                // Check clipboard
                if let Ok(Some(current)) = app.clipboard_manager().read_text() {
                    let mut last = last_content.lock().unwrap();
                    
                    if current != *last && !current.is_empty() {
                        // Clipboard changed
                        println!("Clipboard changed: {}", 
                            if current.len() > 50 {
                                format!("{}...", &current[..47])
                            } else {
                                current.clone()
                            }
                        );
                        
                        // Emit event
                        app.emit_all("clipboard-changed", &current).ok();
                        
                        *last = current;
                    }
                }
                
                thread::sleep(Duration::from_millis(500));
            }
        });
    }
    
    fn stop(&self) {
        let mut is_running = self.is_running.lock().unwrap();
        *is_running = false;
    }
}

#[tauri::command]
fn start_clipboard_monitor(
    monitor: tauri::State<ClipboardMonitor>,
    app: AppHandle,
) -> Result<(), String> {
    monitor.start(app);
    Ok(())
}

#[tauri::command]
fn stop_clipboard_monitor(
    monitor: tauri::State<ClipboardMonitor>,
) -> Result<(), String> {
    monitor.stop();
    Ok(())
}

// Frontend: Listen for clipboard changes
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";

class ClipboardMonitorService {
  private isMonitoring = false;
  private listeners: ((text: string) => void)[] = [];

  async start() {
    if (this.isMonitoring) return;
    
    // Start monitoring
    await invoke("start_clipboard_monitor");
    this.isMonitoring = true;
    
    // Listen for changes
    await listen("clipboard-changed", (event) => {
      const text = event.payload as string;
      this.notifyListeners(text);
    });
  }

  async stop() {
    if (!this.isMonitoring) return;
    
    await invoke("stop_clipboard_monitor");
    this.isMonitoring = false;
  }

  onChange(callback: (text: string) => void) {
    this.listeners.push(callback);
  }

  private notifyListeners(text: string) {
    this.listeners.forEach((listener) => listener(text));
  }
}

// Usage
const monitor = new ClipboardMonitorService();

monitor.onChange((text) => {
  console.log("Clipboard changed:", text);
  addToClipboardHistory(text);
});

await monitor.start();

// Stop when done
// await monitor.stop();

Clipboard History Management

Clipboard history tracks past clipboard entries enabling users to access previously copied content. Understanding history management enables building clipboard managers maintaining searchable history with persistent storage and quick access to past entries.

rustclipboard_history.rs
// Rust: Clipboard history with persistence
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use std::sync::Mutex;
use chrono::{DateTime, Utc};

#[derive(Clone, Debug, Serialize, Deserialize)]
struct ClipboardEntry {
    id: String,
    content: String,
    timestamp: String,
    content_type: String,
}

struct ClipboardHistory {
    entries: Mutex<Vec<ClipboardEntry>>,
    max_entries: usize,
    storage_path: PathBuf,
}

impl ClipboardHistory {
    fn new(storage_path: PathBuf, max_entries: usize) -> Self {
        let mut history = ClipboardHistory {
            entries: Mutex::new(Vec::new()),
            max_entries,
            storage_path,
        };
        history.load().ok();
        history
    }
    
    fn add(&self, content: String) {
        let mut entries = self.entries.lock().unwrap();
        
        // Check for duplicates
        if let Some(pos) = entries.iter().position(|e| e.content == content) {
            entries.remove(pos);
        }
        
        // Add new entry
        let entry = ClipboardEntry {
            id: uuid::Uuid::new_v4().to_string(),
            content,
            timestamp: Utc::now().to_rfc3339(),
            content_type: "text".to_string(),
        };
        
        entries.insert(0, entry);
        
        // Limit size
        if entries.len() > self.max_entries {
            entries.truncate(self.max_entries);
        }
        
        self.save().ok();
    }
    
    fn get_all(&self) -> Vec<ClipboardEntry> {
        let entries = self.entries.lock().unwrap();
        entries.clone()
    }
    
    fn search(&self, query: &str) -> Vec<ClipboardEntry> {
        let entries = self.entries.lock().unwrap();
        entries
            .iter()
            .filter(|e| e.content.contains(query))
            .cloned()
            .collect()
    }
    
    fn delete(&self, id: &str) {
        let mut entries = self.entries.lock().unwrap();
        if let Some(pos) = entries.iter().position(|e| e.id == id) {
            entries.remove(pos);
            drop(entries);
            self.save().ok();
        }
    }
    
    fn clear(&self) {
        let mut entries = self.entries.lock().unwrap();
        entries.clear();
        drop(entries);
        self.save().ok();
    }
    
    fn save(&self) -> Result<(), Box<dyn std::error::Error>> {
        let entries = self.entries.lock().unwrap();
        let json = serde_json::to_string_pretty(&*entries)?;
        fs::write(&self.storage_path, json)?;
        Ok(())
    }
    
    fn load(&mut self) -> Result<(), Box<dyn std::error::Error>> {
        if self.storage_path.exists() {
            let json = fs::read_to_string(&self.storage_path)?;
            let loaded: Vec<ClipboardEntry> = serde_json::from_str(&json)?;
            let mut entries = self.entries.lock().unwrap();
            *entries = loaded;
        }
        Ok(())
    }
}

#[tauri::command]
fn add_to_history(
    history: tauri::State<ClipboardHistory>,
    content: String,
) -> Result<(), String> {
    history.add(content);
    Ok(())
}

#[tauri::command]
fn get_clipboard_history(
    history: tauri::State<ClipboardHistory>,
) -> Vec<ClipboardEntry> {
    history.get_all()
}

#[tauri::command]
fn search_clipboard_history(
    history: tauri::State<ClipboardHistory>,
    query: String,
) -> Vec<ClipboardEntry> {
    history.search(&query)
}

#[tauri::command]
fn delete_history_entry(
    history: tauri::State<ClipboardHistory>,
    id: String,
) -> Result<(), String> {
    history.delete(&id);
    Ok(())
}

#[tauri::command]
fn clear_clipboard_history(
    history: tauri::State<ClipboardHistory>,
) -> Result<(), String> {
    history.clear();
    Ok(())
}

// Initialize in main
fn main() {
    let app_data_dir = std::env::current_dir()
        .unwrap()
        .join("clipboard_history.json");
    
    let clipboard_history = ClipboardHistory::new(app_data_dir, 100);
    
    tauri::Builder::default()
        .manage(clipboard_history)
        .invoke_handler(tauri::generate_handler![
            add_to_history,
            get_clipboard_history,
            search_clipboard_history,
            delete_history_entry,
            clear_clipboard_history,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Handling Sensitive Data

Sensitive clipboard data requires special handling with auto-clear timers and secure storage. Understanding security patterns enables building password managers and secure applications maintaining data protection through automatic clipboard clearing and encrypted history.

rustsensitive_clipboard.rs
// Rust: Auto-clear clipboard for sensitive data
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use tauri::AppHandle;

#[tauri::command]
fn copy_sensitive(
    app: AppHandle,
    text: String,
    clear_after_seconds: u64,
) -> Result<(), String> {
    // Copy to clipboard
    app.clipboard_manager()
        .write_text(text.clone())
        .map_err(|e| e.to_string())?;
    
    println!("Sensitive data copied, will clear in {} seconds", clear_after_seconds);
    
    // Schedule auto-clear
    thread::spawn(move || {
        thread::sleep(Duration::from_secs(clear_after_seconds));
        
        // Clear clipboard
        if let Ok(current) = app.clipboard_manager().read_text() {
            if current == Some(text) {
                app.clipboard_manager().write_text("").ok();
                println!("Clipboard cleared");
                
                // Emit event
                app.emit_all("clipboard-cleared", ()).ok();
            }
        }
    });
    
    Ok(())
}

// Frontend: Password manager integration
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";

class SecureClipboard {
  private clearTimers: Map<string, number> = new Map();

  async copyPassword(
    password: string,
    autoCleanSeconds = 30
  ): Promise<void> {
    await invoke("copy_sensitive", {
      text: password,
      clear_after_seconds: autoCleanSeconds,
    });
    
    this.showNotification(
      `Password copied. Will clear in ${autoCleanSeconds}s`
    );
  }

  async copySecret(
    secret: string,
    clearAfter = 15
  ): Promise<void> {
    await invoke("copy_sensitive", {
      text: secret,
      clear_after_seconds: clearAfter,
    });
  }

  private showNotification(message: string) {
    console.log(message);
    // Show actual notification
  }
}

// Listen for clipboard clear
await listen("clipboard-cleared", () => {
  console.log("Sensitive data cleared from clipboard");
  showNotification("Clipboard cleared");
});

// Usage
const secureClipboard = new SecureClipboard();
await secureClipboard.copyPassword("mySecretPassword123", 30);

Real-World Example: Code Snippet Manager

Code snippet manager demonstrates complete clipboard integration with snippet storage, quick copy with keyboard shortcuts, syntax highlighting, and clipboard monitoring. Understanding real-world implementation enables building productivity tools maintaining efficient code reuse through clipboard-powered snippet management.

rustcode_snippet_manager.rs
// Complete code snippet manager with clipboard
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Mutex;
use tauri::AppHandle;

#[derive(Clone, Debug, Serialize, Deserialize)]
struct CodeSnippet {
    id: String,
    title: String,
    code: String,
    language: String,
    tags: Vec<String>,
    times_copied: u32,
}

struct SnippetManager {
    snippets: Mutex<HashMap<String, CodeSnippet>>,
}

impl SnippetManager {
    fn new() -> Self {
        SnippetManager {
            snippets: Mutex::new(HashMap::new()),
        }
    }
}

#[tauri::command]
fn save_snippet(
    manager: tauri::State<SnippetManager>,
    title: String,
    code: String,
    language: String,
    tags: Vec<String>,
) -> Result<String, String> {
    let id = uuid::Uuid::new_v4().to_string();
    let snippet = CodeSnippet {
        id: id.clone(),
        title,
        code,
        language,
        tags,
        times_copied: 0,
    };
    
    let mut snippets = manager.snippets.lock().unwrap();
    snippets.insert(id.clone(), snippet);
    
    Ok(id)
}

#[tauri::command]
fn copy_snippet(
    app: AppHandle,
    manager: tauri::State<SnippetManager>,
    id: String,
) -> Result<(), String> {
    let mut snippets = manager.snippets.lock().unwrap();
    
    if let Some(snippet) = snippets.get_mut(&id) {
        // Copy to clipboard
        app.clipboard_manager()
            .write_text(snippet.code.clone())
            .map_err(|e| e.to_string())?;
        
        // Increment counter
        snippet.times_copied += 1;
        
        Ok(())
    } else {
        Err("Snippet not found".to_string())
    }
}

#[tauri::command]
fn get_all_snippets(
    manager: tauri::State<SnippetManager>,
) -> Vec<CodeSnippet> {
    let snippets = manager.snippets.lock().unwrap();
    snippets.values().cloned().collect()
}

#[tauri::command]
fn search_snippets(
    manager: tauri::State<SnippetManager>,
    query: String,
) -> Vec<CodeSnippet> {
    let snippets = manager.snippets.lock().unwrap();
    snippets
        .values()
        .filter(|s| {
            s.title.contains(&query)
                || s.code.contains(&query)
                || s.tags.iter().any(|t| t.contains(&query))
        })
        .cloned()
        .collect()
}

// Frontend integration
import { invoke } from "@tauri-apps/api/core";

interface CodeSnippet {
  id: string;
  title: string;
  code: string;
  language: string;
  tags: string[];
  times_copied: number;
}

class SnippetService {
  async saveSnippet(
    title: string,
    code: string,
    language: string,
    tags: string[]
  ): Promise<string> {
    return await invoke("save_snippet", {
      title,
      code,
      language,
      tags,
    });
  }

  async copySnippet(id: string): Promise<void> {
    await invoke("copy_snippet", { id });
    this.showNotification("Snippet copied to clipboard");
  }

  async getAllSnippets(): Promise<CodeSnippet[]> {
    return await invoke("get_all_snippets");
  }

  async searchSnippets(query: string): Promise<CodeSnippet[]> {
    return await invoke("search_snippets", { query });
  }

  private showNotification(message: string) {
    console.log(message);
  }
}

// Usage
const snippets = new SnippetService();

// Save snippet from current clipboard
const clipboardCode = await readText();
if (clipboardCode) {
  await snippets.saveSnippet(
    "API Request",
    clipboardCode,
    "javascript",
    ["api", "fetch"]
  );
}

// Quick copy
await snippets.copySnippet("snippet-id-123");

Clipboard Best Practices

  • Request Permissions: Explicitly enable clipboard permissions in configuration
  • Validate Content: Check clipboard content length and format before operations
  • Handle Errors: Gracefully handle clipboard access failures with fallbacks
  • Clear Sensitive Data: Automatically clear passwords and secrets after timeout
  • Limit History Size: Cap clipboard history preventing excessive memory usage
  • Provide Feedback: Show visual confirmation when copying to clipboard
  • Monitor Responsibly: Use clipboard monitoring sparingly respecting privacy
  • Encrypt History: Encrypt stored clipboard history for sensitive applications
  • Support Formats: Handle multiple formats including text, HTML, and images
  • Test Cross-Platform: Verify clipboard behavior on Windows, macOS, and Linux
Security Warning: Clipboard monitoring can access sensitive user data including passwords and private information. Always request explicit user permission before monitoring clipboard. Implement auto-clear for sensitive data and encrypt clipboard history!

Next Steps

Conclusion

Mastering Clipboard API in Tauri 2.0 enables building professional desktop applications providing seamless copy-paste functionality through native system clipboard integration maintaining user workflow efficiency with text operations supporting reading and writing, clipboard monitoring detecting external changes, history management tracking past entries with search and persistence, sensitive data handling with auto-clear timers, and format support handling plain text, rich text, HTML, and images delivering clipboard functionality users expect from native applications. Clipboard system combines OS integration accessing Windows Clipboard, macOS Pasteboard, and Linux clipboard managers with security controls preventing unauthorized access through explicit permissions, validation ensuring data integrity, automatic clearing protecting sensitive information, and event-driven architecture enabling reactive clipboard-aware applications maintaining data synchronization across application components. Understanding clipboard patterns including basic operations with error handling, monitoring with background tasks polling changes, history management with persistent storage and search, security handling with auto-clear for passwords, and real-world implementations like code snippet managers with syntax highlighting establishes foundation for building professional desktop applications delivering efficient data transfer maintaining productivity through clipboard-powered workflows integrated seamlessly with native OS clipboard systems. Your Tauri applications now possess powerful clipboard capabilities enabling features like instant copy-paste operations, searchable clipboard history, automatic sensitive data protection, clipboard change detection, and cross-format support delivering professional desktop experiences maintaining user productivity through efficient clipboard workflows respecting privacy and security throughout clipboard interactions!

$ cat /comments/ (0)

new_comment.sh

// Email hidden from public

>_

$ cat /comments/

// No comments found. Be the first!

[session] guest@{codershandbook}[timestamp] 2026

Navigation

Categories

Connect

Subscribe

// 2026 {Coders Handbook}. EOF.