Create server-side automation scripts triggered by events, schedules, or manual execution. Supports two languages: JavaScript (Goja) and Gonja (Jinja2-style templates).
Getting Started#
Creating a Script#
- Navigate to Group Settings → Backend Scripts
- Click New Script
- Choose a language: JavaScript or Gonja
- Write your script code
- Set the trigger type and schedule
- Save and test
Script Structure#
JavaScript Example#
// Get the current item
var item = item;
// Update the status
item.SetStatus("approved");
item.Save();
// Add a comment
item.AddComment("Automatically approved by backend script");
// Send a notification
log("Item " + item.Title() + " has been approved");
Gonja (Jinja2) Example#
{# Update item status #}
{% set item = item %}
{{ item.SetStatus("completed") }}
{{ item.Save() }}
{# Log the change #}
{{ log("Item " ~ item.Title() ~ " completed") }}
Trigger Types#
| Type | Description | Configuration |
|---|---|---|
| Manual | Execute on demand from UI | None |
| Schedule | Run on cron schedule | Cron expression (e.g., 0 9 * * *) |
| Event | Fire on specific events | Event type + optional conditions |
| Save Hook | Run after every item save | Optional field conditions |
Available Events#
| Event | Trigger |
|---|---|
CREATE | Item is created |
UPDATE | Item is updated |
DELETE | Item is deleted |
STATUS_CHANGE | Item status changes |
FIELD_UPDATE | Specific field is updated |
ASSIGN | Item is assigned/unassigned |
COMMENT_CREATE | Comment is added |
Language Features#
JavaScript (Goja ES5.1)#
Best for: Complex logic, external API calls, data transformations
- Full ES5.1 JavaScript support
- Access to
user,group,itemobjects fetch()for HTTP requestssecrets.get()for encrypted secrets- 30-second timeout, 100 API calls
Gonja (Jinja2-compatible Go Templates)#
Best for: HTML generation, email templates, formatted outputs
- Jinja2 syntax with Go template power
- Access to
user,group,itemobjects - No
fetch()(use event webhooks instead) - Best for rendering, not data manipulation
Common Use Cases#
Auto-Assign Items#
// JavaScript: Assign to specific user based on priority
if (item.GetField("priority") === "critical") {
item.SetAssignedTo("admin-user-id");
item.Save();
log("Critical item auto-assigned to admin");
}
Status Transitions#
// JavaScript: Auto-advance status
var currentStatus = item.Status();
if (currentStatus === "in-progress" && item.IsCompleted()) {
item.SetStatus("done");
item.Save();
}
Daily Report#
// JavaScript: Generate daily report
var yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
var created = group.QueryItems(
"createdDate:" + yesterday.toISOString().split('T')[0]
);
var completed = group.QueryItems(
"status:done completedAt:" + yesterday.toISOString().split('T')[0]
);
log("Daily report: " + created.length + " created, " +
completed.length + " completed");
Field Validation#
// JavaScript: Validate required field
var estimate = item.GetField("estimate");
if (estimate === null || estimate === "") {
log("Error: Estimate is required for critical items");
// Note: Returning an error doesn't block save in hooks
}
Best Practices#
Performance#
- Keep scripts small and focused
- Use specific queries to minimize data transfer
- Cache frequently accessed values
- Avoid nested loops
Error Handling#
try {
// Your code
item.Save();
} catch (err) {
log("Error: " + err.message);
// Optionally re-throw or handle
}
Security#
- Never hardcode secrets — use
secrets.get() - Validate all inputs
- Be cautious with external API calls
- Remember scripts run with your group’s permissions
Testing#
- Create a test item in your group
- Trigger the script manually
- Check the execution log for output
- Verify the expected changes were made
Debugging#
Using log()#
log("Debug info:", items.length, "items found");
log("Current status:", item.Status());
console.log("User context:", user.Name());
Execution Log#
View script execution logs in:
- Group Settings → Backend Scripts → [Script] → Execution History
Logs include:
- Timestamp
- Trigger type
- Execution time
- Output from
log()calls - Errors
Resource Limits#
| Limit | Value |
|---|---|
| Timeout | 30 seconds |
| Max API calls | 100 |
| Max memory | 50 MB |
| Max fetch response | 1 MB |
API Calls Count
The following count against your API call limit:
QueryItemsGetMemberByIdGetMembersfetchsecrets.get
Examples#
Auto-Assign on Create#
// Auto-assign critical items to team lead
if (item.GetField("priority") === "critical") {
item.SetAssignedTo("team-lead-id");
item.Save();
}
// Assign by custom field
var team = item.GetField("team");
if (team === "engineering") {
item.SetAssignedTo("engineering-lead-id");
item.Save();
}
Notify on Status Change#
// Notify assignee on status change
var oldStatus = item.GetField("oldStatus");
var newStatus = item.Status();
if (oldStatus !== newStatus) {
log("Item " + item.Title() + " changed from " +
oldStatus + " to " + newStatus);
// Could trigger notification via webhook
var token = secrets.get("NOTIFICATION_WEBHOOK_TOKEN");
if (token) {
fetch("https://hooks.example.com/notify", {
method: "POST",
body: JSON.stringify({
item: item.Title(),
from: oldStatus,
to: newStatus
})
});
}
}
Cleanup Old Items#
// Schedule: Run weekly to archive old completed items
var thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
var completedItems = group.QueryItems("status:done");
var count = 0;
for (var i = 0; i < completedItems.length; i++) {
var item = completedItems[i];
if (item.CompletedAt() && new Date(item.CompletedAt()) < thirtyDaysAgo) {
// Mark as archived (or delete)
item.SetField("archived", true);
item.Save();
count++;
}
}
log("Archived " + count + " old completed items");
Next Steps#
- See Scripting Overview for all scripting environments
- Read API Reference for complete API docs
- View Examples Gallery for copy-paste examples