Originally published on 2009-01-30; fully revised and updated.
Perforce triggers are server-side scripts that run on repository events, making them one of the strongest ways to enforce workflow policy in a centralized way. This guide distills and modernizes the concepts from the original article so you can choose the right trigger type, understand execution order, and implement safer automation.

What a Perforce trigger is
A trigger is a program executed by the Perforce server when a defined event occurs. Because execution happens on the server, trigger-based checks are enforceable regardless of whether users submit via CLI, GUI, or other client integrations.
- Pre-submit triggers react to changelist submission events.
- Form triggers react to form lifecycle events (create/edit/save/delete flows).
Trigger types and execution points
The source material describes a historical rollout: Perforce 99.1 introduced the first server-side pre-submit trigger, release 2004.2 added five more types, and release 2005.1 added the delete form trigger.
| Trigger type | When it runs | Can inspect file content | Can modify form text | Can reject operation | Typical use |
|---|---|---|---|---|---|
| submit | After changelist creation, before file transfer | No | No | Yes | Validate changelist metadata and file relationships |
| content | After file transfer, before commit | Yes | No | Yes | Inspect submitted content for policy checks |
| commit | After commit completes | Yes | No | No | Start follow-on automation such as CI signals |
| out | When a form is generated | N/A | Yes | Yes | Prepopulate required form sections |
| in | When a form is returned, before parse | N/A | Yes | Yes | Normalize or validate incoming form text |
| save | After parse, before save | N/A | No | Used for validation/authorization in many setups | Fine-grained policy controls on form updates |
| delete | Before form deletion | N/A | No | Yes | Protect important specs from deletion |
Triggers vs other Perforce automation contexts
The original paper contrasts triggers with wrapper scripts, review daemons, and journal tailers. The main differences are enforceability, timing, and feedback behavior.
| Context | Execution model | Enforceable across all clients | User feedback path | Best fit |
|---|---|---|---|---|
| Server triggers | Synchronous with server event | Yes | Direct on error | Policy gates and mandatory checks |
| Wrapper scripts | Synchronous when wrapper is used | No | Direct on success and error | Team-local convenience workflows |
| Review daemons | Asynchronous polling/event processing | Yes | Indirect (logs, notifications) | Bulk or delayed post-processing |
| Journal tailers | Asynchronous near-real-time processing | Yes | Indirect (external systems) | Broad repository event observation |
Trigger execution flow and ordering
The source lists this execution order: out, in, save, submit, content, commit. Not every operation runs every type. For example, form lifecycle hooks apply to form operations, while submit/content/commit apply to changelist submission.
flowchart TD
A[User action in Perforce] --> B{Action type}
B -- Form edit --> C[Out trigger]
C --> D[In trigger]
D --> E[Save trigger]
E --> F[Form operation completes]
B -- Submit --> G[Submit trigger]
G --> H[Content trigger]
H --> I[Commit trigger]
I --> J[Submit completes]
Configuring triggers in Perforce
In practice, administrators define trigger entries in the server trigger table. Start by checking local syntax and supported variables in your server version.
p4 help triggers
p4 triggers
Triggers:
require_tests submit //depot/main/... "python3 /p4/triggers/require_tests.py %change% %user%"
check_headers content //depot/.../*.h "python3 /p4/triggers/check_headers.py %change%"
enqueue_ci commit //depot/main/... "bash /p4/triggers/enqueue_ci.sh %change%"
add_review_tag out change "python3 /p4/triggers/add_review_tag.py"
protect_rel_labels delete label "python3 /p4/triggers/protect_labels.py %formname% %user%"Code language: JavaScript (javascript)
Use this as a pattern, then validate exact trigger names, form spec tokens, and substitution variables against your installed Perforce/Helix Core documentation.
Exit status and error feedback
Trigger success/failure is communicated through exit code. A zero exit code allows the operation; a non-zero code blocks it (where that trigger type is blocking) and returns the error message to the user.
#!/usr/bin/env bash
change_id="$1"
if /p4/triggers/validate_change.sh "$change_id"; then
exit 0
else
echo "Validation failed: update related test files before submit." 1>&2
exit 1
fiCode language: PHP (php)
Multiple trigger entries and ordering
You can define multiple triggers on the same object/path. They run in listed order until one fails.
success1 submit //... "ruby submit_trigger.rb"
failure1 submit //... "ruby always_fails.rb"
success2 submit //... "ruby another_trigger.rb"Code language: JavaScript (javascript)
Tagged output for form transformation
The original article highlights tagged/marshaled output as a safer alternative to ad-hoc text parsing when reading and rewriting forms.
p4 -G client -o | python3 transform_client.py | p4 -G client -i
Common trigger use cases
- Require related files to be submitted together (for example, source and tests).
- Enforce copyright/banner rules in header files through content inspection.
- Reject zero-content deltas during submit.
- Signal CI/build processing after commit without long blocking runtime.
- Auto-propagate integration from release branches to forward branches (with safeguards).
- Protect critical labels/branches/client specs from unauthorized form edits or deletion.
Best practices and common pitfalls
- Keep trigger runtime short; long-running logic should be queued to background workers.
- Fail with clear, actionable messages so users can self-correct quickly.
- Avoid brittle text parsing; prefer structured/tagged output where available.
- Order triggers intentionally, because one failing trigger prevents later ones from running.
- Test in a non-production server first, especially for content and form-modifying hooks.
- Document ownership and rollback steps for every trigger script in production.
References and attribution
This article is based on the original DevOpsSchool post and its cited conference/admin-guide references. Original author attribution in the source: S. Vance and J. Bowles, with references to [Baum2005], [Bowles2005], [Goldstone2005], and [Perforce2004.2].
I’m Rajesh Kumar, a DevOps, SRE, DevSecOps, Cloud, and Platform Engineering expert passionate about sharing practical knowledge, real-world experiences, and industry best practices. I have worked at Cotocus and regularly write about technology, travel, investing, health, product reviews, and digital marketing through my various platforms.
I publish technical articles at DevOps School, travel stories at Holiday Landmark, stock market insights at Stocks Mantra, health and fitness guidance at My Medic Plus, product reviews at TrueReviewNow, and SEO and digital marketing strategies at Wizbrand.
Find Trusted Cardiac Hospitals
Compare heart hospitals by city and services — all in one place.
Explore Hospitals