Redesign the Python script extension interface to use a function-based contract instead of platform-specific clipboard access: - Scripts define a single convert() function that receives clipboard data as keyword arguments and returns the result - Format support is inferred from the function's parameter names (text, html, image_path, file_paths, work_dir) - Return value can be str, dict, Path, or list — runner normalizes it - Platform tag eliminated — unified execution on Windows and WSL - Only 'name' header remains required; desc, formats, platform all optional/inferred - Ships _runner.py bootstrap that handles JSON stdin/stdout plumbing - Full backward compat: scripts without convert() use legacy V1 paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
8.7 KiB
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
- Open the scripts folder — by default
%LOCALAPPDATA%\Microsoft\PowerToys\AdvancedPaste\Scripts. You can change this in Settings → Advanced Paste → Python scripts → Scripts folder. - Drop a
.pyfile into the folder. - Define a
convert()function (see V2 Interface). - Open the Advanced Paste UI (
Win+Shift+V) — your script will appear in the action list.
V2 Interface (Recommended)
The V2 interface is the simplest way to write scripts. You define a single convert() function
that receives clipboard data as arguments and returns the result. No platform-specific code needed.
Minimal example — reverse text:
# @advancedpaste:name Reverse Text
def convert(text):
"""Reverse the clipboard text."""
return text[::-1]
That's it. No import sys, no json.load, no clipboard libraries. Advanced Paste handles all the plumbing.
How it works
- Advanced Paste reads the clipboard and serializes it as JSON.
- A built-in runner inspects your
convert()function signature. - Only the parameters your function declares are passed as keyword arguments.
- Your function returns the result — Advanced Paste sets it on the clipboard and pastes.
Parameter convention
Declare only the parameters you need:
| Parameter | Type | Content |
|---|---|---|
text |
str |
Clipboard text content |
html |
str |
Clipboard HTML content |
image_path |
str |
Path to temp PNG file of clipboard image |
image |
str |
Alias for image_path |
file_paths |
list[str] |
List of clipboard file paths |
files |
list[str] |
Alias for file_paths |
file_path |
str |
First file path (convenience for single-file) |
work_dir |
str |
Writable temp directory (cleaned up after execution) |
format |
list[str] |
Detected clipboard format names |
Format inference: Advanced Paste infers which clipboard formats your script supports from
the parameter names. A script with def convert(text) only appears when the clipboard has text.
Use **kwargs to accept all formats.
Return value convention
| Return type | Effect |
|---|---|
str |
Sets clipboard to text |
dict |
Full control — must include result_type key (see Output schema) |
pathlib.Path |
Sets clipboard to that file |
list of paths |
Sets clipboard to multiple files |
None |
No-op (clipboard unchanged) |
More examples
Convert text to uppercase:
# @advancedpaste:name Upper Case
def convert(text):
return text.upper()
Convert image to grayscale:
# @advancedpaste:name Grayscale Image
# @advancedpaste:requires PIL=Pillow
from PIL import Image
from pathlib import Path
def convert(image_path, work_dir):
img = Image.open(image_path).convert("L")
out = Path(work_dir) / "gray.png"
img.save(out)
return out
Return HTML:
# @advancedpaste:name Wrap in Code Block
def convert(text):
return {
"result_type": "html",
"html": f"<pre><code>{text}</code></pre>",
"text": text, # fallback for apps that don't support HTML
}
Accept any format with kwargs:
# @advancedpaste:name Debug Clipboard
def convert(**kwargs):
"""Show what's on the clipboard as formatted text."""
import json
return json.dumps(kwargs, indent=2, default=str)
Header format
The only required header is name:
# @advancedpaste:name My Script Name
Optional tags
| Tag | Description |
|---|---|
name |
Required. Display name shown in the Advanced Paste UI. |
desc |
Short description / tooltip. (Can also use the convert() docstring.) |
formats |
Override auto-detected formats. Comma-separated: text, html, image, file, any. |
requires |
Declare Python package dependencies (see Declaring dependencies). |
enabled |
Set to false to disable the script without deleting it. |
Tags no longer needed in V2
| Tag | Why |
|---|---|
platform |
Eliminated — V2 scripts run identically on Windows and WSL. |
version |
Reserved, not useful in practice. |
Legacy Interface (V1)
Scripts that do NOT define a convert() function are treated as V1 (legacy) scripts and
continue to work as before:
Windows mode (platform windows)
The script runs directly and owns the clipboard via win32clipboard.
# @advancedpaste:name Reverse text
# @advancedpaste:formats text
# @advancedpaste:platform windows
import win32clipboard
win32clipboard.OpenClipboard()
text = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32clipboard.CF_UNICODETEXT, text[::-1])
win32clipboard.CloseClipboard()
WSL / Linux mode (platform linux)
The script reads JSON from stdin and writes JSON to stdout.
# @advancedpaste:name WSL Upper Case
# @advancedpaste:formats text
# @advancedpaste:platform linux
import sys, json
data = json.load(sys.stdin)
text = data.get("text", "")
json.dump({"result_type": "text", "text": text.upper()}, sys.stdout)
Input/Output JSON Schema (for V1 Linux and advanced V2 dict returns)
Input payload
{
"version": 2,
"format": ["text"], // array of detected clipboard format names
"work_dir": "C:\\Temp\\...", // writable temp directory
"text": "Hello, world!", // present when clipboard has text
"html": "<b>Hello</b>", // present when clipboard has HTML
"image_path": "C:\\...\\input.png", // present when clipboard has an image
"file_paths": ["C:\\...\\file.txt"] // present when clipboard has files
}
Output payload
{
"result_type": "text", // "text" | "html" | "image" | "file" | "files"
"text": "HELLO, WORLD!", // for result_type "text"
"html": "<b>HELLO</b>", // for result_type "html"
"image_path": "C:\\...\\output.png", // for result_type "image"
"file_paths": ["C:\\...\\out.txt"] // for result_type "file"/"files"
}
Declaring dependencies
Use requires to declare Python packages the script needs:
# @advancedpaste:requires markitdown='markitdown[all]'
# @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 following settings are available under Settings → Advanced Paste → Python scripts:
| Setting | Description | Default |
|---|---|---|
| Python interpreter | Path to the Python executable. Leave blank for auto-detection. | (auto-detect) |
| Scripts folder | Folder to scan for .py scripts. |
%LOCALAPPDATA%\Microsoft\PowerToys\AdvancedPaste\Scripts |
Tips
- Put reusable helper functions in a separate
.pyfile without a# @advancedpaste:nameheader — it will be ignored by the script discovery and can be imported by other scripts. - The
work_dirparameter points to a temporary directory that is cleaned up after execution. Use it for intermediate files (e.g., image processing output). - V2 scripts are testable from the command line:
echo {"text":"hello"} | python _runner.py my_script.py