{"id":118,"date":"2026-10-12T08:52:12","date_gmt":"2026-10-12T08:52:12","guid":{"rendered":"http:\/\/www.scmgalaxy.com\/tutorials\/2009\/01\/30\/writing-trigger-in-perforce\/"},"modified":"2026-10-12T08:52:12","modified_gmt":"2026-10-12T08:52:12","slug":"writing-trigger-in-perforce","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/writing-trigger-in-perforce\/","title":{"rendered":"Perforce Triggers Guide: Types, Execution Order, and Practical Patterns"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><em>Originally published on 2009-01-30; fully revised and updated.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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 <a href=\"https:\/\/www.devopsschool.com\/blog\/writing-trigger-in-perforce\/\">original article<\/a> so you can choose the right trigger type, understand execution order, and implement safer automation.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img decoding=\"async\" src=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2009\/01\/trigger-in-perforce.png\" alt=\"Perforce trigger workflow overview\" \/><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">What a Perforce trigger is<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Pre-submit triggers<\/strong> react to changelist submission events.<\/li><li><strong>Form triggers<\/strong> react to form lifecycle events (create\/edit\/save\/delete flows).<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Trigger types and execution points<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Trigger type<\/th><th>When it runs<\/th><th>Can inspect file content<\/th><th>Can modify form text<\/th><th>Can reject operation<\/th><th>Typical use<\/th><\/tr><\/thead><tbody><tr><td>submit<\/td><td>After changelist creation, before file transfer<\/td><td>No<\/td><td>No<\/td><td>Yes<\/td><td>Validate changelist metadata and file relationships<\/td><\/tr><tr><td>content<\/td><td>After file transfer, before commit<\/td><td>Yes<\/td><td>No<\/td><td>Yes<\/td><td>Inspect submitted content for policy checks<\/td><\/tr><tr><td>commit<\/td><td>After commit completes<\/td><td>Yes<\/td><td>No<\/td><td>No<\/td><td>Start follow-on automation such as CI signals<\/td><\/tr><tr><td>out<\/td><td>When a form is generated<\/td><td>N\/A<\/td><td>Yes<\/td><td>Yes<\/td><td>Prepopulate required form sections<\/td><\/tr><tr><td>in<\/td><td>When a form is returned, before parse<\/td><td>N\/A<\/td><td>Yes<\/td><td>Yes<\/td><td>Normalize or validate incoming form text<\/td><\/tr><tr><td>save<\/td><td>After parse, before save<\/td><td>N\/A<\/td><td>No<\/td><td>Used for validation\/authorization in many setups<\/td><td>Fine-grained policy controls on form updates<\/td><\/tr><tr><td>delete<\/td><td>Before form deletion<\/td><td>N\/A<\/td><td>No<\/td><td>Yes<\/td><td>Protect important specs from deletion<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Triggers vs other Perforce automation contexts<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The original paper contrasts triggers with wrapper scripts, review daemons, and journal tailers. The main differences are enforceability, timing, and feedback behavior.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Context<\/th><th>Execution model<\/th><th>Enforceable across all clients<\/th><th>User feedback path<\/th><th>Best fit<\/th><\/tr><\/thead><tbody><tr><td>Server triggers<\/td><td>Synchronous with server event<\/td><td>Yes<\/td><td>Direct on error<\/td><td>Policy gates and mandatory checks<\/td><\/tr><tr><td>Wrapper scripts<\/td><td>Synchronous when wrapper is used<\/td><td>No<\/td><td>Direct on success and error<\/td><td>Team-local convenience workflows<\/td><\/tr><tr><td>Review daemons<\/td><td>Asynchronous polling\/event processing<\/td><td>Yes<\/td><td>Indirect (logs, notifications)<\/td><td>Bulk or delayed post-processing<\/td><\/tr><tr><td>Journal tailers<\/td><td>Asynchronous near-real-time processing<\/td><td>Yes<\/td><td>Indirect (external systems)<\/td><td>Broad repository event observation<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Trigger execution flow and ordering<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The source lists this execution order: <strong>out<\/strong>, <strong>in<\/strong>, <strong>save<\/strong>, <strong>submit<\/strong>, <strong>content<\/strong>, <strong>commit<\/strong>. Not every operation runs every type. For example, form lifecycle hooks apply to form operations, while submit\/content\/commit apply to changelist submission.<\/p>\n\n\n\n<pre class=\"mermaid\">\nflowchart TD\n    A[User action in Perforce] --> B{Action type}\n    B -- Form edit --> C[Out trigger]\n    C --> D[In trigger]\n    D --> E[Save trigger]\n    E --> F[Form operation completes]\n    B -- Submit --> G[Submit trigger]\n    G --> H[Content trigger]\n    H --> I[Commit trigger]\n    I --> J[Submit completes]\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Configuring triggers in Perforce<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In practice, administrators define trigger entries in the server trigger table. Start by checking local syntax and supported variables in your server version.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs language-bash\">p4 help triggers\np4 triggers<\/code><\/span><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript language-text\">Triggers:\n    require_tests submit <span class=\"hljs-comment\">\/\/depot\/main\/... \"python3 \/p4\/triggers\/require_tests.py %change% %user%\"<\/span>\n    check_headers content <span class=\"hljs-comment\">\/\/depot\/...\/*.h \"python3 \/p4\/triggers\/check_headers.py %change%\"<\/span>\n    enqueue_ci commit <span class=\"hljs-comment\">\/\/depot\/main\/... \"bash \/p4\/triggers\/enqueue_ci.sh %change%\"<\/span>\n    add_review_tag out change <span class=\"hljs-string\">\"python3 \/p4\/triggers\/add_review_tag.py\"<\/span>\n    protect_rel_labels <span class=\"hljs-keyword\">delete<\/span> label <span class=\"hljs-string\">\"python3 \/p4\/triggers\/protect_labels.py %formname% %user%\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use this as a pattern, then validate exact trigger names, form spec tokens, and substitution variables against your installed Perforce\/Helix Core documentation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Exit status and error feedback<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php language-bash\"><span class=\"hljs-comment\">#!\/usr\/bin\/env bash<\/span>\nchange_id=<span class=\"hljs-string\">\"$1\"<\/span>\n<span class=\"hljs-keyword\">if<\/span> \/p4\/triggers\/validate_change.sh <span class=\"hljs-string\">\"$change_id\"<\/span>; then\n  <span class=\"hljs-keyword\">exit<\/span> <span class=\"hljs-number\">0<\/span>\n<span class=\"hljs-keyword\">else<\/span>\n  <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Validation failed: update related test files before submit.\"<\/span> <span class=\"hljs-number\">1<\/span>&gt;&amp;<span class=\"hljs-number\">2<\/span>\n  <span class=\"hljs-keyword\">exit<\/span> <span class=\"hljs-number\">1<\/span>\nfi<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Multiple trigger entries and ordering<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You can define multiple triggers on the same object\/path. They run in listed order until one fails.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript language-text\">success1 submit <span class=\"hljs-comment\">\/\/... \"ruby submit_trigger.rb\"<\/span>\nfailure1 submit <span class=\"hljs-comment\">\/\/... \"ruby always_fails.rb\"<\/span>\nsuccess2 submit <span class=\"hljs-comment\">\/\/... \"ruby another_trigger.rb\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Tagged output for form transformation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The original article highlights tagged\/marshaled output as a safer alternative to ad-hoc text parsing when reading and rewriting forms.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs language-bash\">p4 -G client -o | python3 transform_client.py | p4 -G client -i<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Common trigger use cases<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>Require related files to be submitted together (for example, source and tests).<\/li><li>Enforce copyright\/banner rules in header files through content inspection.<\/li><li>Reject zero-content deltas during submit.<\/li><li>Signal CI\/build processing after commit without long blocking runtime.<\/li><li>Auto-propagate integration from release branches to forward branches (with safeguards).<\/li><li>Protect critical labels\/branches\/client specs from unauthorized form edits or deletion.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Best practices and common pitfalls<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>Keep trigger runtime short; long-running logic should be queued to background workers.<\/li><li>Fail with clear, actionable messages so users can self-correct quickly.<\/li><li>Avoid brittle text parsing; prefer structured\/tagged output where available.<\/li><li>Order triggers intentionally, because one failing trigger prevents later ones from running.<\/li><li>Test in a non-production server first, especially for content and form-modifying hooks.<\/li><li>Document ownership and rollback steps for every trigger script in production.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">References and attribution<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This article is based on the original DevOpsSchool post and its cited conference\/admin-guide references. Original author attribution in the source: <strong>S. Vance<\/strong> and <strong>J. Bowles<\/strong>, with references to [Baum2005], [Bowles2005], [Goldstone2005], and [Perforce2004.2].<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Perforce triggers let you enforce workflow rules and launch automation directly from the server. This revised guide explains trigger types, execution order, trade-offs, and practical configuration patterns.<\/p>\n","protected":false},"author":1,"featured_media":3976,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[370],"tags":[350,545,1467,2942,373,2945,2946,2941,1097,818,2939,2948,2940,2943,2973,2944,1049,2938,2947],"class_list":["post-118","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-perforce","tag-create","tag-how","tag-implement","tag-p4-triggers","tag-perforce","tag-perforce-trigger-build","tag-perforce-trigger-types","tag-perforce-triggers","tag-tips","tag-trigger","tag-trigger-in-perforce","tag-trigger-techniques","tag-trigger-types","tag-triggers-guide","tag-triggers-tips","tag-triggers-tutorial","tag-write","tag-writing-trigger","tag-writing-trigger-in-perforce"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/118","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=118"}],"version-history":[{"count":3,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/118\/revisions"}],"predecessor-version":[{"id":78967,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/118\/revisions\/78967"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media\/3976"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=118"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=118"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=118"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}