Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/9001/copyparty/llms.txt

Use this file to discover all available pages before exploring further.

Event hooks allow you to trigger external programs or scripts when specific events occur in copyparty, such as file uploads, moves, renames, or deletions.
See --help-hooks for complete documentation and example hooks in the repository.

Hook Types

Copyparty supports hooks for various events:

Upload Hooks

xbu
before upload
Execute command before a file upload starts
[global]
  xbu: /usr/local/bin/check-upload.py

[/uploads]
  /mnt/uploads
  flags:
    xbu: /usr/local/bin/volume-specific.sh
xau
after upload
Execute command after a file upload finishes
[global]
  xau: /usr/bin/notify-send,"File uploaded",--
Most commonly used hook type for notifications and post-processing.
xiu
idle after upload
Execute command after all uploads finish and volume is idleUnlike xbu/xau (which execute for every file), xiu is given a list of recent uploads on STDIN after the server has been idle for N seconds.
[global]
  xiu: 30,/usr/local/bin/batch-process.py  # idle for 30 seconds

File Operation Hooks

xbc
before copy
Execute command before a file copy
xac
after copy
Execute command after a file copy
xbr
before rename
Execute command before a file rename/move
xar
after rename
Execute command after a file rename/move
xbd
before delete
Execute command before a file delete
xad
after delete
Execute command after a file delete

Special Hooks

xm
on message
Execute command when a message is received (via [📟] send-msg tab)
[global]
  xm: /usr/local/bin/handle-message.py
xban
on ban
Execute command when someone gets banned
[global]
  xban: /usr/local/bin/log-ban.sh

Hook Flags

Hooks can have additional flags to modify their behavior:
c
check flag
Hook exit code controls the action:
  • Exit 0 = allow the action, continue to next hook
  • Exit 100 = allow the action, stop running remaining hooks
  • Any other = reject/prevent the action, don’t run remaining hooks
[/restricted]
  /mnt/restricted
  flags:
    xbu: c,/usr/local/bin/check-file.py  # check before upload
j
json flag
Send extended upload info as JSON instead of just the filesystem path
[global]
  xau: j,/usr/local/bin/process-upload.py
JSON includes: file path, uploader IP, username, size, timestamp, etc.
t[N]
timeout flag
Timeout after N seconds (important for blocking hooks like REQ/PUSH)
[global]
  xau: t3,zmq:req:tcp://localhost:5555  # 3 second timeout
I
import flag
Import hook as Python module (140x faster startup, but bugs may crash copyparty)
Only use with well-tested hooks. A bug in an imported hook can crash the entire server.
[global]
  xau: I,/usr/local/bin/fast-hook.py

Hook Arguments

Basic Syntax

# Simple command
xau: /usr/bin/notify-send

# Command with arguments, use commas and trailing --
xau: /usr/bin/notify-send,"File uploaded",--

# Multiple flags
xau: c,j,t5,/usr/local/bin/check.py

Information Passed to Hooks

By default, hooks receive the filesystem path as the first (and only) argument. With the j flag, hooks receive a JSON object on STDIN with:
{
  "vpath": "/uploads/file.jpg",
  "rpath": "/mnt/uploads/file.jpg",
  "user": "alice",
  "ip": "192.168.1.100",
  "size": 1048576,
  "ts": 1709481600
}

ZeroMQ Hooks

Instead of running programs, hooks can send ZeroMQ messages:
[global]
  xau: zmq:pub:tcp://*:5556  # send PUB to all connected SUB clients
The PUSH and REQ patterns need t[N] (timeout) because they block if no clients are connected.

Example ZeroMQ Receiver

See zmq-recv.py for a complete example.
import zmq

context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://localhost:5556")
socket.subscribe(b"")  # subscribe to all messages

while True:
    message = socket.recv_string()
    print(f"Upload: {message}")

Common Use Cases

Desktop Notifications

[global]
  xau: /usr/bin/notify-send,"Upload complete",--
Or with more details using the notify2.py example:
[global]
  xau: j,/usr/local/bin/notify2.py

Discord Webhook Notifications

Using discord-announce.py:
[global]
  xau: j,/usr/local/bin/discord-announce.py

Reject Specific File Types

Using reject-extension.py:
[/uploads]
  /mnt/uploads
  flags:
    xbu: c,/usr/local/bin/reject-extension.py  # check flag prevents upload

Remove EXIF from Images

Using image-noexif.py:
[/photos]
  /mnt/photos
  flags:
    xau: /usr/local/bin/image-noexif.py  # strip EXIF after upload

Download URLs

Using wget.py:
[global]
  xm: /usr/local/bin/wget.py  # POST URLs to download them
Users can then POST URLs via the [📟] send-msg tab to download files.

Batch Processing

Using xiu-sha.py:
[global]
  xiu: 60,/usr/local/bin/xiu-sha.py  # create checksum after 60s idle

Custom Error Messages

Using reject-and-explain.py:
[/uploads]
  /mnt/uploads
  flags:
    xbu: c,/usr/local/bin/reject-and-explain.py

Hook Effects

Some hooks can return special instructions to copyparty:

Relocation

Redirect an upload to another destination. Example from reloc-by-ext.py:
import sys, os
path = sys.argv[1]
if path.endswith('.mp3'):
    # Relocate MP3s to /music folder
    print("reloc /music/" + os.path.basename(path))
    sys.exit(100)  # exit 100 = success, stop other hooks

Indexing

Tell copyparty about additional files to scan. Example from podcast-normalizer.py:
import sys, subprocess
infile = sys.argv[1]
outfile = infile.replace('.mp3', '-normalized.mp3')

# Create normalized version
subprocess.run(['ffmpeg', '-i', infile, '-af', 'loudnorm', outfile])

# Tell copyparty to index the new file
print(f"idx {outfile}")

Configuration Examples

Per-Volume Hooks

[/uploads]
  /mnt/uploads
  accs:
    w: *
  flags:
    xbu: c,/usr/local/bin/check-upload.sh   # before upload
    xau: /usr/local/bin/notify-upload.sh    # after upload

Multiple Hooks

Hooks are additive - you can specify multiple hooks of the same type:
[global]
  xau: /usr/bin/notify-send,Upload,--
  xau: j,/usr/local/bin/log-upload.py
  xau: zmq:pub:tcp://*:5556
All three hooks will execute after each upload.

Global + Volume Hooks

[global]
  xau: /usr/local/bin/global-hook.py  # runs for all volumes

[/special]
  /mnt/special
  flags:
    xau: /usr/local/bin/special-hook.py  # also runs for this volume

Conditional Hooks by File Type

Create a wrapper script:
check-and-process.sh
#!/bin/bash
FILE="$1"

if [[ "$FILE" == *.mp3 ]]; then
    /usr/local/bin/process-audio.sh "$FILE"
elif [[ "$FILE" == *.jpg ]]; then
    /usr/local/bin/process-image.sh "$FILE"
fi
[global]
  xau: /usr/local/bin/check-and-process.sh

Writing Custom Hooks

Basic Template (Shell)

hook-template.sh
#!/bin/bash
FILE_PATH="$1"

# Your logic here
echo "Processing: $FILE_PATH"

# Exit 0 for success (in 'c' check hooks)
exit 0

Basic Template (Python)

hook-template.py
#!/usr/bin/env python3
import sys

file_path = sys.argv[1]

# Your logic here
print(f"Processing: {file_path}")

# Exit 0 for success
sys.exit(0)

JSON Input Template (Python)

json-hook-template.py
#!/usr/bin/env python3
import sys
import json

# Read JSON from stdin (requires 'j' flag)
data = json.load(sys.stdin)

vpath = data['vpath']    # virtual path (URL path)
rpath = data['rpath']    # real path (filesystem)
user = data['user']      # username
ip = data['ip']          # client IP
size = data['size']      # file size
ts = data['ts']          # timestamp

print(f"User {user} from {ip} uploaded {vpath}")

sys.exit(0)

Check Hook Template

check-hook.py
#!/usr/bin/env python3
import sys
import os

file_path = sys.argv[1]
filename = os.path.basename(file_path)

# Reject files with 'bad' in the name
if 'bad' in filename.lower():
    print("ERROR: Filename contains 'bad'", file=sys.stderr)
    sys.exit(1)  # non-zero = reject

# Allow file
sys.exit(0)

Performance Considerations

Hook performance impact:
  • xbu and xau hooks run for every single file
  • Slow hooks will delay uploads
  • Use xiu for batch processing when possible
  • Use the I flag only for well-tested hooks
  • Fork expensive operations or use kn flag in mtp plugins
1

Test hooks thoroughly

Ensure hooks exit correctly and handle errors gracefully
2

Use xiu for batches

Process multiple uploads at once instead of one-by-one
3

Keep hooks fast

Offload heavy processing to background jobs
4

Monitor hook execution

Check server logs for hook failures or timeouts

Comparison: Hooks vs MTP Plugins

Copyparty has two systems for running external programs:
FeatureEvent Hooks (xau, etc.)MTP Plugins (mtp)
SimplicitySimple, minimal setupMore complex
InformationFile path (or basic JSON)Full metadata (tags, codecs, etc.)
BlockingBlocks uploadNon-blocking, multithreaded
TriggerEvery upload/actionOnly new unique files
PipelineSingle programCan chain multiple programs
Use caseNotifications, simple checksMetadata extraction, processing
For complex metadata processing, consider mtp plugins instead of hooks.

Troubleshooting

  • Check file permissions: chmod +x /path/to/hook.sh
  • Verify path is absolute, not relative
  • Check server logs for error messages
  • Test hook manually: /path/to/hook.sh /test/file.txt
For c (check) hooks:
  • Exit 0 to allow action
  • Exit non-zero to reject action
  • Check stderr for error messages
  • Add t[N] timeout flag for PUSH/REQ patterns
  • Ensure receiver is running and connected
  • Check ZeroMQ port is accessible
  • Remove I (import) flag
  • Fix Python syntax errors in hook
  • Add error handling to hook script
  • Add j flag to hook configuration
  • Read from stdin, not command-line arguments
  • Verify JSON parsing in hook script

Example Repository

See the official hooks directory for complete working examples:
  • notification hooks
  • validation/rejection hooks
  • message handlers
  • batch processing
  • webhook integrations
These can be used directly or as templates for your own hooks.

Build docs developers (and LLMs) love