Timill Platform Documentation

Backend Scripts

Create server-side automation scripts using JavaScript or Gonja templates triggered by events or schedules.

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#

  1. Navigate to Group Settings → Backend Scripts
  2. Click New Script
  3. Choose a language: JavaScript or Gonja
  4. Write your script code
  5. Set the trigger type and schedule
  6. 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#

TypeDescriptionConfiguration
ManualExecute on demand from UINone
ScheduleRun on cron scheduleCron expression (e.g., 0 9 * * *)
EventFire on specific eventsEvent type + optional conditions
Save HookRun after every item saveOptional field conditions

Available Events#

EventTrigger
CREATEItem is created
UPDATEItem is updated
DELETEItem is deleted
STATUS_CHANGEItem status changes
FIELD_UPDATESpecific field is updated
ASSIGNItem is assigned/unassigned
COMMENT_CREATEComment 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, item objects
  • fetch() for HTTP requests
  • secrets.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, item objects
  • 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#

  1. Create a test item in your group
  2. Trigger the script manually
  3. Check the execution log for output
  4. 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#

LimitValue
Timeout30 seconds
Max API calls100
Max memory50 MB
Max fetch response1 MB

API Calls Count

The following count against your API call limit:

  • QueryItems
  • GetMemberById
  • GetMembers
  • fetch
  • secrets.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#