Files
PowerToys/doc/devdocs/modules/advancedpaste-python-scripts.md
Muyuan Li (from Dev Box) 7a2b07a3c9 Advanced Paste: Add Python script extension support
Enable users to write custom clipboard transformation scripts in Python
that integrate directly into the Advanced Paste menu. Scripts are auto-
discovered from a configurable folder and can run on native Windows
Python or inside WSL.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-30 15:42:18 +08:00

10 KiB
Raw Blame History

Advanced Paste Python Scripts

Advanced Paste supports user-defined Python scripts that transform clipboard content. Scripts are discovered automatically from a configurable folder and appear as actions in the Advanced Paste UI.

Quick start

  1. Open the scripts folder — by default %LOCALAPPDATA%\Microsoft\PowerToys\AdvancedPaste\Scripts. You can change this in Settings → Advanced Paste → Python scripts → Scripts folder.
  2. Drop a .py file into the folder.
  3. Define one advanced_paste_from_<input>_to_<output> function (see Writing a script).
  4. Open the Advanced Paste UI (Win+Shift+V) — your script will appear in the action list.

Important: Each .py file must define exactly one advanced_paste_from_<input>_to_<output> function. Scripts with zero or multiple such functions are ignored.

Writing a script

You write a single Python function whose name declares both what clipboard input it accepts and what output type it produces. No imports from PowerToys are needed — zero setup, zero dependencies on our side.

Function naming convention

The function name follows the pattern:

advanced_paste_from_<input>_to_<output>(<param>)

Input types (what the function receives):

Input Parameter When it runs
text str — clipboard text Clipboard has text
html str — clipboard HTML Clipboard has HTML
image str — path to temp image file Clipboard has an image
audio str — path to audio file Clipboard has an audio file
video str — path to video file Clipboard has a video file
files list[str] — file paths Clipboard has files

Output types (what the function produces — declared via _to_ suffix):

Output Effect
text Sets clipboard to text
html Sets clipboard to HTML
image Sets clipboard to image
audio Sets clipboard to audio file
video Sets clipboard to video file
file Sets clipboard to a file
files Sets clipboard to multiple files

Return value

The return value is interpreted according to the declared output type:

Output type Expected return value
text str (or any value — will be converted via str())
html str containing HTML
image str or pathlib.Path pointing to an image file
file str or pathlib.Path pointing to a file
files list of str/pathlib.Path file paths

Returning None produces an empty result (no-op).

Examples

Minimal — uppercase text

def advanced_paste_from_text_to_text(text):
    return text.upper()

That's it. No headers required, no imports from PowerToys.

With optional metadata

# @advancedpaste:name   Reverse Text
# @advancedpaste:desc   Reverses clipboard text character by character

def advanced_paste_from_text_to_text(text):
    return text[::-1]

Text to HTML

# @advancedpaste:name   Markdown Table to HTML
# @advancedpaste:desc   Convert a markdown table to an HTML table

def advanced_paste_from_text_to_html(text):
    headers = text.splitlines()[0].split("|")
    return f"<table><tr>{''.join(f'<th>{h.strip()}</th>' for h in headers if h.strip())}</tr></table>"

Image to text (OCR)

# @advancedpaste:requires pytesseract

def advanced_paste_from_image_to_text(image_path):
    import pytesseract
    return pytesseract.image_to_string(image_path).strip()

Save text as file

import os
from pathlib import Path
import tempfile

def advanced_paste_from_text_to_file(text):
    # Use ADVANCED_PASTE_WORK_DIR for WSL compatibility; falls back to temp dir on Windows.
    out_dir = os.environ.get("ADVANCED_PASTE_WORK_DIR") or tempfile.gettempdir()
    out = Path(out_dir) / "clipboard.txt"
    out.write_text(text, encoding="utf-8")
    return out

Image processing (image → image)

import os
from PIL import Image
from pathlib import Path
import tempfile

def advanced_paste_from_image_to_image(image_path):
    """Convert image to grayscale."""
    img = Image.open(image_path).convert("L")
    out_dir = os.environ.get("ADVANCED_PASTE_WORK_DIR") or tempfile.gettempdir()
    out = Path(out_dir) / "gray.png"
    img.save(out)
    return out

File listing (files → text)

import os

def advanced_paste_from_files_to_text(file_paths):
    lines = []
    for p in file_paths:
        size = os.path.getsize(p)
        lines.append(f"{os.path.basename(p)} ({size} bytes)")
    return "\n".join(lines)

Header tags

All header tags are optional. Tags are placed in comment lines at the top of the script.

Tag Description
name Display name in the Advanced Paste UI. If omitted, the filename is used.
desc Short description / tooltip.
disabled Presence of this tag disables the script (it won't appear in the UI).
requires Declare Python package dependencies (see Dependencies).

Example header

# @advancedpaste:name   My Formatter
# @advancedpaste:desc   Formats clipboard text as markdown table

To disable a script without deleting it, add:

# @advancedpaste:disabled

Remove the line to re-enable.

Declaring dependencies

Use requires to declare Python packages the script needs:

# @advancedpaste:requires PIL=Pillow
# @advancedpaste:requires cv2=opencv-python-headless numpy requests

Each token is either:

  • import_name — the pip package is assumed to have the same name (e.g. requests).
  • import_name=pip_package — when the import name differs from the pip package (e.g. cv2=opencv-python-headless, PIL=Pillow).

Automatic import detection

Advanced Paste also scans the script body for import and from ... import statements and cross-references them against the Python standard library. Any non-stdlib import that is not already installed triggers a prompt to install it automatically.

Security — script trust

The first time a script is executed (or after it has been modified), Advanced Paste shows a confirmation dialog. Upon approval the SHA-256 hash of the script is stored. Subsequent runs of the unchanged file skip the dialog.

Error handling

When a script fails, Advanced Paste extracts the Python traceback from stderr and displays a user-friendly summary in the UI:

  • ModuleNotFoundError — identifies the missing module and suggests installing it.
  • SyntaxError — shows the file and line number.
  • Timeout — shows the configured timeout value (default 30 s; configurable in Settings).
  • Other errors — shows the last line of the traceback as a summary, with the full traceback available in the expandable Details section.

Settings

The Python scripts feature uses a mode selector (dropdown) with three options:

Mode Description
Disabled Python scripts are not active.
Windows Scripts run using a native Windows Python interpreter.
WSL Scripts run inside Windows Subsystem for Linux.

Each mode maintains its own independent settings (scripts folder, interpreter path, etc.), so switching between Windows and WSL does not lose your previous configuration.

Windows mode settings

Setting Description Default
Scripts folder Folder to scan for .py scripts. %LOCALAPPDATA%\Microsoft\PowerToys\AdvancedPaste\Scripts
Python interpreter Path to the Python executable. Leave blank for auto-detection. (auto-detect)

WSL mode settings

Setting Description Default
Scripts folder Folder to scan for .py scripts (Windows path — auto-translated to /mnt/...). %LOCALAPPDATA%\Microsoft\PowerToys\AdvancedPaste\Scripts
WSL distribution Which WSL distro to use (e.g. Ubuntu). Leave blank for the default distribution. (default)

Scripts list

The Settings page shows a read-only list of discovered scripts. For each script you can see:

  • Name — from @advancedpaste:name tag, or the filename if not set.
  • Description — from @advancedpaste:desc tag.
  • Conversion — the input → output types detected from the function name (e.g. "text → image").

The list is not editable from Settings. To change a script's name, description, enabled state, or any other metadata, open the script file directly (click the "Open in editor" button) and edit the # @advancedpaste:... header tags. After saving, click Refresh in Settings to reload.

WSL mode details

When WSL mode is selected:

  • Scripts are executed via wsl.exe bash -l -c "python3 ..." using the configured distribution.
  • The scripts folder remains on the Windows filesystem; paths are automatically translated to /mnt/c/... format for WSL access.
  • Package installation uses pip3 install inside the WSL environment.
  • Output files from scripts must be written under /mnt/ (the Windows-mounted filesystem) so they can be accessed from Windows. The runner sets the ADVANCED_PASTE_WORK_DIR environment variable to a temp directory under /mnt/c/... — use it instead of tempfile.gettempdir() when producing file output for cross-platform compatibility.

Tip: If you have Python installed only in WSL (not on Windows), select WSL mode to use your existing WSL Python environment with all its packages.

Tips

  • Each .py file must contain exactly one advanced_paste_from_<input>_to_<output> function. If you need to handle multiple input types, create separate script files for each.
  • A .py file without any matching function is ignored — use this for helper modules that other scripts can import.
  • Scripts can be tested from the command line:
    echo {"format":["text"],"text":"hello"} | python _runner.py my_script.py
    
  • The script's directory is added to sys.path at runtime, so you can import sibling .py files as helper modules.