Documentation Index
Fetch the complete documentation index at: https://docs.perscom.io/llms.txt
Use this file to discover all available pages before exploring further.
Automations enable you to create powerful, event-driven workflows that execute automatically when specific actions occur in PERSCOM. Instead of manually performing repetitive tasks, you can configure automations to send notifications, trigger webhooks, or deliver messages whenever personnel records change, forms are submitted, or users are updated.
Use Cases
Automations are useful for:
- Notifications: Send messages to team members when a user receives a new award or promotion
- External integrations: Trigger webhooks to sync data with external systems like Discord, Slack, or custom applications
- Approval workflows: Notify supervisors when new form submissions require review
- Audit trails: Send automated messages when critical personnel changes occur
- Onboarding: Welcome new users with automated messages when their accounts are created
How Automations Work
Each automation consists of three components:
- Trigger: The event that starts the automation (e.g., “User Created” or “Award Record Updated”)
- Condition (optional): An expression that determines whether the action should execute
- Action: What happens when the automation runs (send a webhook or create a message)
When the trigger event occurs and any configured conditions are met, the automation executes the specified action and logs the result.
Create an Automation
-
In the sidebar, select Integrations > Automations.
-
Select New automation.
-
Configure the automation settings:
- Name: A descriptive name for the automation
- Description: Optional explanation of what the automation does
- Priority: Lower numbers run first when multiple automations share the same trigger (default is 0)
- Enabled: Toggle to activate or deactivate the automation
-
Select the 1. Trigger tab and choose the event that starts this automation.
-
(Optional) Select the 2. Condition tab to add a conditional expression.
-
Select the 3. Action tab and configure either a webhook or message action.
-
Select Create.
Triggers
Triggers define which events start your automation. Select one trigger per automation. To respond to multiple events, create separate automations for each.
Available Triggers
| Category | Events |
|---|
| User | User Created, User Updated, User Deleted |
| Assignment Record | Assignment Record Created, Updated, Deleted |
| Award Record | Award Record Created, Updated, Deleted |
| Combat Record | Combat Record Created, Updated, Deleted |
| Qualification Record | Qualification Record Created, Updated, Deleted |
| Rank Record | Rank Record Created, Updated, Deleted |
| Service Record | Service Record Created, Updated, Deleted |
| Calendar | Calendar Created, Updated, Deleted |
| Event | Event Created, Updated, Deleted |
| Message | Message Created, Updated, Deleted |
| Submission | Submission Created, Updated, Deleted |
Conditions
Conditions allow you to control when an automation executes based on the data in the triggered event. Conditions use expression syntax to evaluate whether the action should run.
Expression Syntax
Conditions use the Symfony Expression Language syntax. Expressions must evaluate to true for the action to execute.
Basic comparisons:
model["status"] == "active"
model["rank_id"] > 5
causer["id"] != model["id"]
Logical operators:
model["status"] == "active" and model["approved"] == true
model["rank_id"] > 5 or model["position_id"] == 1
Null checks:
causer != null
model["notes"] != null
Available Context Variables
When writing conditions, you have access to these variables:
| Variable | Type | Description |
|---|
model | object | The record that triggered the event (e.g., the user or award record) |
model_type | string | The fully qualified class name of the model (e.g., App\Models\User) |
model_id | integer | The ID of the model that triggered the event |
changes | object | An object containing changed fields with old and new values for each (update triggers only) |
causer | object | The user who triggered the event, if available |
causer_id | integer | The ID of the user who triggered the event |
now | datetime | The current date and time when the automation runs |
Access properties using array syntax: model["name"], model["email"], causer["id"].
The Changes Object
For update triggers, the changes object contains only the fields that were modified. Each changed field has old and new properties:
{
"rank_id": {
"old": 5,
"new": 6
},
"position_id": {
"old": 2,
"new": 3
}
}
Access specific change values:
changes.rank_id.old — The previous rank ID
changes.rank_id.new — The new rank ID
Full Context Example
Here’s an example of the complete context data available for a user update trigger:
{
"model": {
"id": 42,
"name": "John Doe",
"email": "john@example.com",
"rank_id": 6,
"position_id": 3,
"unit_id": 2,
"status_id": 1
},
"model_type": "App\\Models\\User",
"model_id": 42,
"changes": {
"rank_id": {
"old": 5,
"new": 6
}
},
"causer": {
"id": 1,
"name": "Admin User",
"email": "admin@example.com"
},
"causer_id": 1,
"now": "2024-01-15T14:30:00.000000Z"
}
Built-in Functions
Use these functions in your condition expressions:
| Function | Description | Example |
|---|
in_array(needle, haystack) | Check if a value exists in an array | in_array(model["status_id"], [1, 2, 3]) |
contains(haystack, needle) | Check if a string contains a substring | contains(model["email"], "@example.com") |
starts_with(haystack, needle) | Check if a string starts with a prefix | starts_with(model["name"], "Admin") |
ends_with(haystack, needle) | Check if a string ends with a suffix | ends_with(model["email"], ".gov") |
changed(key) | Check if a specific field was changed (checks if key exists in changes) | changed("rank_id") |
old_value(key) | Get the previous value of a changed field (shorthand for changes.key.old) | old_value("rank_id") |
new_value(key) | Get the new value of a changed field (shorthand for changes.key.new) | new_value("rank_id") |
blank(value) | Check if a value is empty | blank(model["notes"]) |
filled(value) | Check if a value is not empty | filled(model["discord_user_id"]) |
Condition Examples
Run only when a user is approved:
model["approved"] == true
Run only when rank changes:
Run only when rank increases:
changed("rank_id") and new_value("rank_id") > old_value("rank_id")
Run only for users with a specific email domain:
ends_with(model["email"], "@agency.gov")
Run only when triggered by a specific user:
causer != null and causer["id"] == 1
Testing Conditions
Before saving your automation, test your condition:
- Select a trigger to populate the available fields.
- Enter your condition expression.
- Select Test to evaluate the expression against sample data.
- Select View Fields to see all available fields for the selected trigger.
- Select View Functions to see all available functions.
Actions
Actions define what happens when an automation triggers and conditions pass. Each automation performs one action.
Webhook Action
Webhook actions send HTTP requests to external URLs, enabling integration with third-party services.
- Select Send Webhook as the action type.
- Choose an existing webhook from the dropdown.
- (Optional) Define a custom payload template using JSON and Twig syntax.
If you don’t specify a custom payload, the automation sends the full model data.
Note: Before creating a webhook automation, you must first create a webhook in Integrations > Webhooks.
Custom Payload Templates
Create custom JSON payloads using Twig template syntax. Use {{ variable }} to insert dynamic values.
Example: Discord webhook payload
{
"content": "New user registered: {{ model.name }}",
"username": "PERSCOM Bot"
}
Example: Custom integration payload
{
"event": "user_promoted",
"user": {
"id": "{{ model.id }}",
"name": "{{ model.name }}",
"email": "{{ model.email }}"
},
"promoted_by": "{{ causer.name }}",
"timestamp": "{{ now | date('Y-m-d H:i:s') }}"
}
Example: Payload with change details
{
"event": "rank_changed",
"user_id": {{ model_id }},
"previous_rank_id": {{ changes.rank_id.old }},
"new_rank_id": {{ changes.rank_id.new }},
"changed_by": {{ causer_id }}
}
Message Action
Message actions create notifications sent through your configured notification channels.
- Select Send Message as the action type.
- Select one or more notification channels (Email, Database, Discord, SMS).
- Enter the message content using Twig syntax for dynamic values.
- (Optional) Specify a recipients expression to target specific users.
Message Content
Write your message content using Twig template syntax:
{{ model.name }} has been promoted to {{ model.rank.name }}.
This change was made by {{ causer.name }} on {{ now | date('F j, Y') }}.
Example with change details:
{{ model.name }}'s rank has been updated from ID {{ changes.rank_id.old }} to ID {{ changes.rank_id.new }}.
Recipients Expression
Define who receives the message using an expression that returns user IDs:
Send to the affected user:
Send to the user’s supervisor (for record models):
Send to multiple users:
[model["id"], causer["id"]]
Conditional recipient:
model["supervisor_id"] != null ? model["supervisor_id"] : causer["id"]
If left empty, the message uses the default notification behavior.
Update Resource Action
Update Resource actions modify existing records in your system based on automation triggers. This enables you to automatically update user profiles or other resources when events occur.
Note: Currently, only User resources are supported for updates.
- Select Update Resource as the action type.
- Select the Target Resource (currently Users).
- Choose a Lookup Type to identify which record to update:
- Expression: Write an expression that returns the ID of the record to update
- Query: Define field conditions to find the record
- Configure the Field Updates with the fields and values to set.
Lookup Types
Expression Lookup
Use an expression that evaluates to the ID of the record you want to update:
This example updates the user who triggered the event. You can also use more complex expressions:
This updates the user associated with a record (e.g., when an award record is created, update the user who received the award).
Query Lookup
Define field-value conditions to find the target record. The system finds the first record matching all conditions.
| Field | Value |
|---|
email | {{ model.email }} |
status_id | 1 |
Query values support Twig syntax for dynamic matching.
Field Updates
Define which fields to update and their new values. Values support Twig template syntax, giving you access to all context variables plus a special target variable.
| Field | Value |
|---|
status_id | 2 |
notes | Updated by automation on {{ now | date('Y-m-d') }} |
rank_id | {{ target.rank_id | increment }} |
The Target Variable
When configuring field updates, you have access to a target variable containing the current data of the record being updated. This allows you to reference existing values when setting new ones.
Example: Increment a user’s rank:
{{ target.rank_id | increment }}
Example: Append to existing notes:
{{ target.notes }} - Updated on {{ now | date('Y-m-d') }}
Example: Conditional value based on target data:
{{ target.status_id == 1 ? 2 : target.status_id }}
Resource Update Examples
Update user status when a form is submitted:
- Trigger: Submission Created
- Condition:
model["form_id"] == 5
- Action: Update Resource
- Lookup Type: Expression
- Lookup Expression:
model["user_id"]
- Field Updates:
Promote a user when they receive a specific award:
- Trigger: Award Record Created
- Condition:
model["award_id"] == 10
- Action: Update Resource
- Lookup Type: Expression
- Lookup Expression:
model["user_id"]
- Field Updates:
| Field | Value |
|---|
rank_id | {{ target.rank_id | increment }} |
Update user profile based on external data:
- Trigger: User Updated
- Condition:
changed("external_id") and filled(model["external_id"])
- Action: Update Resource
- Lookup Type: Query
- Query Conditions:
| Field | Value |
|---|
id | {{ model.id }} |
- Field Updates:
| Field | Value |
|---|
verified | true |
verified_at | {{ now | date('Y-m-d H:i:s') }} |
Twig Filters
Use Twig filters to transform values in your templates. Apply filters using the pipe syntax: {{ value | filter }}.
| Filter | Description | Example |
|---|
capitalize | Capitalize the first character | {{ model.name | capitalize }} |
date | Format a date | {{ model.created_at | date("Y-m-d") }} |
decrement(n) | Subtract from a number (default: 1) | {{ model.rank_id | decrement(2) }} |
default | Provide a fallback value | {{ model.nickname | default("N/A") }} |
escape | Escape HTML entities | {{ model.notes | escape }} |
first | Get the first array element | {{ model.tags | first }} |
increment(n) | Add to a number (default: 1) | {{ model.rank_id | increment(5) }} |
join | Join array elements | {{ model.tags | join(", ") }} |
json_encode | Encode value as JSON | {{ model | json_encode }} |
last | Get the last array element | {{ model.tags | last }} |
length | Get string or array length | {{ model.name | length }} |
lower | Convert to lowercase | {{ model.name | lower }} |
nl2br | Convert newlines to HTML breaks | {{ model.notes | nl2br }} |
raw | Output without escaping | {{ model.html_content | raw }} |
replace | Replace text | {{ model.text | replace({"foo": "bar"}) }} |
round | Round a number | {{ model.score | round }} |
slice | Extract a portion | {{ model.notes | slice(0, 100) }} |
split | Split string into array | {{ model.tags_string | split(",") }} |
striptags | Remove HTML tags | {{ model.description | striptags }} |
title | Convert to title case | {{ model.name | title }} |
trim | Remove surrounding whitespace | {{ model.notes | trim }} |
upper | Convert to uppercase | {{ model.name | upper }} |
View Automation Logs
Every automation execution is logged for debugging and auditing purposes.
Access Logs
- In the sidebar, select Integrations > Automations.
- Select an automation to view its details.
- Scroll down to the Logs section to see execution history.
Alternatively, view all automation logs:
- In the sidebar, select Integrations > Automations > Logs.
Log Details
Each log entry includes:
| Field | Description |
|---|
| Status | The execution result: Executed, Condition Failed, or Failed |
| Trigger | The event that triggered the automation |
| Subject | The record type and ID that triggered the event |
| Execution Time | How long the automation took to run (in milliseconds) |
| Executed At | When the automation ran |
Select a log entry to view detailed information:
- Overview: Status, timing, and error messages (if any)
- Subject: The triggering record details and the user who caused the event
- Condition: The expression evaluated and whether it passed
- Context: The full data context available to the automation
- Action Result: The payload sent or message created
Log Statuses
| Status | Description |
|---|
| Executed | The automation ran successfully |
| Condition Failed | The condition expression evaluated to false; no action was taken |
| Failed | An error occurred during execution |
Debugging Automations
When an automation doesn’t work as expected, use these strategies to diagnose the issue.
Check the Automation Is Enabled
Disabled automations do not run. Verify the Enabled toggle is active on the automation’s settings page.
Review Log Entries
- Open the automation and check the Logs section.
- Look for entries with Failed or Condition Failed status.
- Select a log entry to view the error message and context.
Test Your Condition
- Edit the automation and go to the 2. Condition tab.
- Select Test to evaluate your expression against sample data.
- Select View Fields to verify you’re using the correct field paths.
Verify Field Paths
Field paths are case-sensitive and must use array syntax in expressions. Use View Fields to see the exact structure of the context data, including:
- Property names (e.g.,
model["name"], not model["Name"])
- Nested relationships (e.g.,
model["user"]["email"])
- Available fields for the selected trigger type
Common Issues
Condition always fails:
- Verify the field path matches the actual data structure
- Check for typos in field names
- Ensure you’re using the correct comparison operators
Webhook not received:
- Verify the webhook URL is correct and accessible
- Check that the target service is running
- Review the webhook configuration in Integrations > Webhooks
Message not delivered:
- Verify the notification channels are configured correctly
- Check the recipients expression returns valid user IDs
- Ensure users have the appropriate notification settings enabled
Preview Templates
Before saving, preview your templates to catch errors:
- For webhooks: Select Preview next to the payload template field
- For messages: Select Preview next to the message content field
The preview shows your template rendered with sample data from the selected trigger.
Best Practices
- Use descriptive names: Name automations clearly so their purpose is obvious (e.g., “Notify Discord on User Promotion”)
- Start with conditions: Add conditions to prevent automations from running unnecessarily
- Test before enabling: Use the test and preview features to verify your expressions and templates
- Monitor logs: Regularly review automation logs to catch failures early
- Use priority: Set priority values to control execution order when multiple automations share triggers
- Keep templates simple: Complex templates are harder to debug; break complex workflows into multiple automations