www/docs/development/plugins/script-plugin.md
Script plugins are lightweight, single-file plugins that provide a simple way to extend Wox functionality. They are perfect for quick automation tasks, personal utilities, and learning plugin development.
Script plugins communicate with Wox using JSON-RPC over stdin/stdout. Each script is executed on-demand when a query is made, making them ideal for simple, stateless operations.
For a ready-to-use example, see this gist: https://gist.github.com/qianlifeng/82a2f748177ce47a900b4c4da3abfd28
Use the wpm plugin to create a new script plugin:
wpm create <name>
Available templates:
python - Python script templatejavascript - JavaScript/Node.js script templatebash - Bash script templateA script plugin consists of a single executable file with metadata defined as a JSON object in comments:
#!/usr/bin/env python3
# {
# "Id": "my-calculator",
# "Name": "My Calculator",
# "Author": "Your Name",
# "Version": "1.0.0",
# "MinWoxVersion": "2.0.0",
# "Description": "A simple calculator plugin",
# "Icon": "emoji:🧮",
# "TriggerKeywords": ["calc"],
# "SettingDefinitions": [
# {
# "Type": "textbox",
# "Value": {
# "Key": "precision",
# "Label": "Decimal Precision",
# "Tooltip": "Number of decimal places to show",
# "DefaultValue": "2",
# "Style": {
# "Width": 100
# }
# }
# }
# ],
# "Features": [
# {
# "Name": "debounce",
# "Params": {
# "intervalMs": "300"
# }
# }
# ]
# }
# Your plugin code here...
The JSON metadata block must:
# for Python/Bash or // for JavaScriptId - Unique plugin identifier (use UUID format recommended)Name - Display name of the pluginTriggerKeywords - Array of trigger keywordsIcon - Plugin icon (emoji:🧮, relative:path/to/icon.png, or absolute path)Version - Plugin version (default: "1.0.0")Author - Plugin author (default: "Unknown")Description - Plugin description (default: "A script plugin")MinWoxVersion - Minimum required Wox version (default: "2.0.0")SettingDefinitions - Array of setting definitions (see Settings section below)Features - Array of plugin features (debounce, querySelection, etc.)Commands - Array of plugin commandsSupportedOS - Array of supported operating systems (default: all platforms)You can define settings that users can configure in the Wox settings UI. Settings are defined in the SettingDefinitions array:
#!/usr/bin/env python3
# {
# "Id": "weather-plugin",
# "Name": "Weather",
# "TriggerKeywords": ["weather"],
# "SettingDefinitions": [
# {
# "Type": "textbox",
# "Value": {
# "Key": "api_key",
# "Label": "API Key",
# "Tooltip": "Your weather API key",
# "DefaultValue": "",
# "Style": {
# "Width": 400
# }
# }
# },
# {
# "Type": "select",
# "Value": {
# "Key": "units",
# "Label": "Temperature Units",
# "DefaultValue": "celsius",
# "Options": [
# {"Label": "Celsius", "Value": "celsius"},
# {"Label": "Fahrenheit", "Value": "fahrenheit"}
# ]
# }
# },
# {
# "Type": "checkbox",
# "Value": {
# "Key": "show_forecast",
# "Label": "Show 7-day forecast",
# "DefaultValue": "true"
# }
# }
# ]
# }
Settings are automatically passed to script plugins as environment variables. Each setting is prefixed with WOX_SETTING_ and the key is converted to uppercase.
For example, if you define a setting with key api_key, it will be available as the environment variable WOX_SETTING_API_KEY.
Python Example:
import os
# Get setting value
api_key = os.getenv('WOX_SETTING_API_KEY', '')
enable_feature = os.getenv('WOX_SETTING_ENABLE_FEATURE', 'false')
output_format = os.getenv('WOX_SETTING_OUTPUT_FORMAT', 'json')
# Use the settings
if api_key:
print(f"Using API key: {api_key[:4]}...")
JavaScript Example:
// Get setting value
const apiKey = process.env.WOX_SETTING_API_KEY || "";
const enableFeature = process.env.WOX_SETTING_ENABLE_FEATURE === "true";
const outputFormat = process.env.WOX_SETTING_OUTPUT_FORMAT || "json";
// Use the settings
if (apiKey) {
console.log(`Using API key: ${apiKey.substring(0, 4)}...`);
}
Bash Example:
# Get setting value
API_KEY="${WOX_SETTING_API_KEY:-}"
ENABLE_FEATURE="${WOX_SETTING_ENABLE_FEATURE:-false}"
OUTPUT_FORMAT="${WOX_SETTING_OUTPUT_FORMAT:-json}"
# Use the settings
if [ -n "$API_KEY" ]; then
echo "Using API key: ${API_KEY:0:4}..."
fi
Additional Environment Variables:
Script plugins also have access to these environment variables:
WOX_PLUGIN_ID - The plugin's unique IDWOX_PLUGIN_NAME - The plugin's display nameWOX_DIRECTORY_USER_SCRIPT_PLUGINS - Directory where script plugins are storedWOX_DIRECTORY_USER_DATA - User data directoryWOX_DIRECTORY_WOX_DATA - Wox application data directoryWOX_DIRECTORY_PLUGINS - Plugin directoryWOX_DIRECTORY_THEMES - Theme directoryScript plugins communicate with Wox using JSON-RPC 2.0 protocol.
Wox sends requests to your script via stdin:
{
"jsonrpc": "2.0",
"method": "query",
"params": {
"search": "user search term",
"trigger_keyword": "calc",
"command": "",
"raw_query": "calc 2+2"
},
"id": "request-id"
}
Your script should respond via stdout:
{
"jsonrpc": "2.0",
"result": {
"items": [
{
"title": "Result: 4",
"subtitle": "2 + 2 = 4",
"score": 100,
"actions": [
{
"id": "copy-result",
"data": "4"
}
]
}
]
},
"id": "request-id"
}
Handles user queries and returns search results.
Parameters:
search - The search term entered by usertrigger_keyword - The keyword that triggered this plugincommand - Command if using plugin commandsraw_query - The complete raw query stringHandles user selection of a result item.
Parameters:
id - The action ID from the result itemdata - The action data from the result itemsearch, trigger_keyword, command, and raw_query. Selection payloads and query environment data are not passed to scripts.Script plugins can return a preview object per result. The preview is shown when the result is selected.
{
"title": "Example with preview",
"subtitle": "Select to view preview",
"preview": {
"type": "markdown",
"data": "# Preview Title\n\nDetails here",
"properties": {
"height": "300"
},
"scrollPosition": "bottom"
},
"actions": [
{
"id": "copy-to-clipboard",
"text": "Example"
}
]
}
Supported preview types: markdown, text, image, url, file, remote.
Script plugins can return tails to display extra badges or text in the result detail view.
{
"title": "Result with tails",
"subtitle": "Extra details are in tails",
"tails": [
{ "type": "text", "text": "tag:urgent" },
{ "type": "image", "image": "emoji:⭐" }
],
"actions": [
{ "id": "copy-to-clipboard", "text": "Example" }
]
}
Supported tail types: text, image.
Script plugins have access to these environment variables:
WOX_DIRECTORY_USER_SCRIPT_PLUGINS - Script plugins directoryWOX_DIRECTORY_USER_DATA - User data directoryWOX_DIRECTORY_WOX_DATA - Wox application data directoryWOX_DIRECTORY_PLUGINS - Plugin directoryWOX_DIRECTORY_THEMES - Theme directoryScript plugins can use two types of actions:
Each result must have an actions field with an array of action objects (even for a single action).
Each action object can have:
id (required): The action identifiername (optional): Display name in UI (defaults to "Execute")text for clipboard, url for open-url)Example - Single Action:
{
"title": "Copy text",
"actions": [
{
"name": "Copy to Clipboard",
"id": "copy-to-clipboard",
"text": "Hello World"
}
]
}
Example - Multiple Actions:
{
"title": "Multiple options",
"actions": [
{
"name": "Copy",
"id": "copy-to-clipboard",
"text": "Hello World"
},
{
"name": "Open URL",
"id": "open-url",
"url": "https://example.com"
}
]
}
Built-in actions are handled automatically by Wox. You can use them directly in your query results without implementing the action method in your script.
Important: When using built-in actions, you don't need to implement handle_action() in your script. Wox will handle the action automatically. The action method is still called as a hook, but you can simply return an empty result.
Copies text to the clipboard:
{
"title": "Copy this text",
"subtitle": "Click to copy",
"actions": [
{
"name": "Copy",
"id": "copy-to-clipboard",
"text": "Text to copy"
}
]
}
Opens a URL in the default browser:
{
"title": "Open website",
"subtitle": "Click to open",
"actions": [
{
"name": "Open in Browser",
"id": "open-url",
"url": "https://example.com"
}
]
}
Opens a directory in the file manager:
{
"title": "Open folder",
"subtitle": "Click to open",
"actions": [
{
"name": "Open Folder",
"id": "open-directory",
"path": "/path/to/directory"
}
]
}
Shows a notification message:
{
"title": "Show notification",
"subtitle": "Click to notify",
"actions": [
{
"name": "Notify",
"id": "notify",
"message": "Notification message"
}
]
}
For custom actions, you need to implement the action method in your script:
def handle_action(params, request_id):
action_id = params.get("id", "")
action_data = params.get("data", "")
if action_id == "my-custom-action":
# Handle your custom action
return {
"jsonrpc": "2.0",
"result": {},
"id": request_id
}
# For built-in actions or unknown actions, return empty result
return {
"jsonrpc": "2.0",
"result": {},
"id": request_id
}
Note: The action method is called for ALL actions (built-in and custom) as a hook. This allows you to add additional logic even for built-in actions if needed. However, for built-in actions, you can simply return an empty result and Wox will handle them automatically.
#!/usr/bin/env python3
# @wox.id simple-calculator
# @wox.name Simple Calculator
# @wox.keywords calc
import json
import sys
import re
def handle_query(params, request_id):
search = params.get('search', '').strip()
if not search:
return {
"jsonrpc": "2.0",
"result": {"items": []},
"id": request_id
}
try:
# Simple math evaluation (be careful with eval in real plugins!)
if re.match(r'^[0-9+\-*/().\s]+$', search):
result = eval(search)
return {
"jsonrpc": "2.0",
"result": {
"items": [{
"title": f"Result: {result}",
"subtitle": f"{search} = {result}",
"score": 100,
"actions": [
{
"id": "copy-result",
"data": str(result)
}
]
}]
},
"id": request_id
}
except:
pass
return {
"jsonrpc": "2.0",
"result": {"items": []},
"id": request_id
}
def handle_action(params, request_id):
# Handle copy action
return {
"jsonrpc": "2.0",
"result": {},
"id": request_id
}
if __name__ == "__main__":
request = json.loads(sys.stdin.read())
method = request.get("method")
params = request.get("params", {})
request_id = request.get("id")
if method == "query":
response = handle_query(params, request_id)
elif method == "action":
response = handle_action(params, request_id)
else:
response = {
"jsonrpc": "2.0",
"error": {"code": -32601, "message": "Method not found"},
"id": request_id
}
print(json.dumps(response))
#!/bin/bash
# @wox.id file-search
# @wox.name File Search
# @wox.keywords fs
# Read JSON-RPC request
read -r request
# Parse request
search=$(echo "$request" | jq -r '.params.search // ""')
id=$(echo "$request" | jq -r '.id')
if [ -z "$search" ]; then
echo '{"jsonrpc":"2.0","result":{"items":[]},"id":"'$id'"}'
exit 0
fi
# Search files
results=()
while IFS= read -r -d '' file; do
basename=$(basename "$file")
results+=("{\"title\":\"$basename\",\"subtitle\":\"$file\",\"score\":90,\"action\":{\"id\":\"open-file\",\"data\":\"$file\"}}")
done < <(find "$HOME" -name "*$search*" -type f -print0 2>/dev/null | head -z -10)
# Build JSON response
items=$(IFS=,; echo "${results[*]}")
echo '{"jsonrpc":"2.0","result":{"items":['$items']},"id":"'$id'"}'
#!/usr/bin/env node
// @wox.id weather-plugin
// @wox.name Weather
// @wox.keywords weather
const https = require("https");
function handleQuery(params, requestId) {
const search = params.search || "";
if (!search) {
return {
jsonrpc: "2.0",
result: { items: [] },
id: requestId,
};
}
// Mock weather data (replace with real API)
const weatherData = {
temperature: "22°C",
condition: "Sunny",
location: search,
};
return {
jsonrpc: "2.0",
result: {
items: [
{
title: `${weatherData.temperature} - ${weatherData.condition}`,
subtitle: `Weather in ${weatherData.location}`,
score: 100,
action: {
id: "show-details",
data: JSON.stringify(weatherData),
},
},
],
},
id: requestId,
};
}
// Main execution
const input = process.stdin.read();
if (input) {
const request = JSON.parse(input.toString());
const response = handleQuery(request.params || {}, request.id);
console.log(JSON.stringify(response));
}
eval() or executing commandsTest your script manually:
# Create test input
echo '{"jsonrpc":"2.0","method":"query","params":{"search":"test"},"id":"1"}' | ./your-script.py
# Expected output format
{"jsonrpc":"2.0","result":{"items":[...]},"id":"1"}
chmod +x your-script.py#!/usr/bin/env python3 or similarIf your script plugin grows complex, consider migrating to a full-featured plugin:
wox-plugin@wox-launcher/wox-plugin