P4 Discord Reporter

19 Jul 2025

How to use Python to send Perforce commit messages to Discord.

Introduction

If you just want the finished script you’ll find it at the end of this article.

Honestly when I started this task I wasn’t expecting to have to spend so many hours on it. It’s a simple premise after all: Whenever someone submits a change to the perforce depot the contents of the submission message should be posted to Discord. After all, I’ve created similar integrations for a mix and match between Git, Jenkins, Discord and Slack before. Secondly because I expected the use case to be quite common.

Yet I have had a hard time finding out what needed to be done, not least of all because of the “sparse” way the Perforce docs are often written. Also all official samples are still in Python 2?!

Prerequisites

This tutorial is written for Python 3. To run the code you’ll need p4python to communicate with the server and requests to send data to the webhook.

A working Perforce server that you can add triggers to.

The url for a Discord Webhook. If you don’t have one, check out how to make a webhook.

The Script

Most tutorials cover how to connect to P4 like this and then run commands. However, there is a catch, because certain command calls will error out either saying the client workspace is invalid, P4PASSWD isn’t set or the session expired.

from P4 import P4, P4Exception

p4 = P4()

p4.user = "SomeUsername"
p4.password = "SomePassword"
p4.port = "1666"
p4.connect()

To fix all these errors at once you need to call

p4.run_login()

Next get the latest change list info. The order of arguments is important.

Argument Purpose
changes The command
-l By default commit messages are truncated. This returns the full message
-m 1 Get only the latest change
//depot/… The changes command lists only Changelists that affect a given directory or file.
Passing in the whole depot lists all changes
changes = p4.run("changes","-l","-m 1", "//depot/...")

changes is a list with one entry. The entry is an object with all the infos from the latest change.

[
  {
    "change": "47", 
    "time": "1752951440", 
    "user": "freetimecoder", 
    "client": "freetimecoder_studio_pc", 
    "status": "submitted", 
    "changeType": "public", 
    "path": "//depot/mainline/Tools/P4/*", 
    "desc": "- Added final report python script\n"
  }
]

So here we store the relevant info for later.

clnr = changes[0]["change"]
user = changes[0]["user"]
desc = changes[0]["desc"]

Now that we got the info it’s time to send it to Discord. Provided the webhook is active this is as simple as doing a json post request to the url.

import requests

...

data = {
    "content" : "",
    "username" : "Perforce",
    "embeds": [
        {
            "title" : f"{user} submitted CL-{clnr}",
            "description" : desc
        }
    ]
}

result = requests.post(discord_webhook_url, json = data)

Running the script yields a message like this in the designated Discord channel.

A lot of blueprint nodes that the plugin offers.

Trigger Setup

Now that the script is done, all that’s left is to make Perforce actually execute it. Triggers are made for that. Whenever a specific action happens Perforce can execute custom code. To start adding a trigger run this in the terminal:

p4 triggers

This will open a text file in the text editor that is configured for Perforce. Scroll way down to the bottom and add

Triggers:
    discord change-commit //depot/... "py /path/to/script/reporter.py"

Save and close the file and Perforce should say “Triggers saved.”

The config is composed of these parts:

Argument Purpose
discord Unique name of the trigger
change-commit Event to listen for (this one is after the push was successful)
//depot/… Only trigger for changes below this folder path
“py /path/to/script/reporter.py” Command to execute


And there you go, pushing a change should now post messages in Discord.

Finished Script

The final script has a few more bells and whistles, but essentially works the same. A thing to note is that if you want to sync the script inside the repository you need to put the credentials into environment variables or an external config file.

#!/usr/bin/env python3
from P4 import P4, P4Exception
import requests

# You'll need to change these to fit your setup 
p4_user = "SomeUsername"
p4_password = "SomePassword"
p4_port = "server:1666"
# This is optional depending on your ssl setup
p4_fingerprint = None 
# What path to watch for changes, in case you have multiple depots
p4_depot_path = "//depot/..."
# Endpoint for the messages
discord_webhook_url = "https://discord.com/..."

p4 = P4()

try:
    p4.user = p4_user
    p4.password = p4_password
    p4.port = p4_port
    p4.connect()
    if p4_fingerprint not None:
        p4.run_trust("-i", p4_fingerprint)
    p4.run_login()
    
    # Fetch the long description of the latest submission
    changes = p4.run("changes","-l","-m 1", p4_depot_path)

    # Get the parts of the changelog that interest us for the message
    clnr = changes[0]["change"]
    user = changes[0]["user"]
    desc = changes[0]["desc"]
        
    # An empty message "from Perforce" with the change list message in an embed
    data = {
        "content" : "",
        "username" : "Perforce",
        "embeds": [
            {
                "title" : f"{user} submitted CL-{clnr}",
                "description" : desc
            }
        ]
    }
    
    # Send message to Discord
    result = requests.post(discord_webhook_url, json = data)
    result.raise_for_status()
    
except P4Exception:
    # Send any P4 errors to Discord and output
    for e in p4.errors: 
        requests.post(config["discord_webhook"], json = { "content" : str(e), "username" : "Perforce"})
        print(e)
except Exception as e:
    # Send any other exceptions to Discord as well 
    requests.post(config["discord_webhook"], json = { "content" : str(e), "username" : "Perforce"})
    print(e)

Errors

Perforce does throw errors, but it’s hard to know what they mean and how to fix them. So I’ve listed all of them down here in hopes a search engine might pick up on it.

-l - must create client 'Computer-Name' to access local files.


The -l parameter must be passed in before the depot path

Perforce password (P4PASSWD) invalid or unset


p4.run_login() must be called before running the command

Your session has expired, please login again.


Most tutorial go right to where p4.connect() is called, however p4.run_login() needs to be called before running some commands.