Timill Platform Documentation

Scripted Widgets

Create custom HTML widgets using Go templates with RBAC enforcement in Timill Platform.

Create custom HTML widgets using Go’s html/template syntax with built-in query helpers and automatic RBAC enforcement. Widgets are rendered server-side and integrated into pages and dashboards.

Getting Started#

Creating a Widget#

  1. Navigate to any page or dashboard
  2. Click Add WidgetScripted Widget
  3. Enter a name and description
  4. Write your Go template code
  5. Save and preview

Widget Structure#

Every scripted widget consists of:

<!-- Widget parameters (optional) -->
{{define "params"}}
  {"itemType": "task", "status": "open"}
{{end}}

<!-- Widget HTML -->
<div class="bg-white rounded-lg shadow p-4">
  {{$items := queryItems (printf "type=%s status=%s" .itemType .status)}}
  <h3 class="text-lg font-semibold mb-3">Open Items ({{len $items}})</h3>
  
  {{range $items}}
    <div class="p-2 border-b">
      <span class="font-medium">{{.Title.Get}}</span>
      <span class="{{statusColor .Status.Get}} ml-2">{{.Status.Get}}</span>
    </div>
  {{else}}
    <p class="text-gray-500">No items found</p>
  {{end}}
</div>

Template Functions#

Query Functions#

FunctionReturnsDescription
queryItems(query)Item[]Query items with RBAC
getGroup()GroupGet current group
getUser(id)UserGet user by ID
getItem(id)ItemGet item by ID

Helper Functions#

FunctionReturnsDescription
statusColor(status)stringCSS class for status color
priorityColor(priority)stringCSS class for priority
formatDate(date)stringFormat date for display
truncate(text, len)stringTruncate text to length

Data Access#

Item Fields#

Items have both default fields and custom fields:

<!-- Default fields -->
{{.Title.Get}}       <!-- Item title -->
{{.Description.Get}}  <!-- Item description (HTML) -->
{{.Status.Get}}       <!-- Status ID -->
{{.AssignedTo.Get}}   <!-- Assignee user ID -->
{{.DueDate.Get}}      <!-- Due date (ISO 8601) -->
{{.ItemType.Get}}     <!-- Item type ID -->
{{.CreatedAt.Get}}    <!-- Creation date -->
{{.UpdatedAt.Get}}    <!-- Last update date -->

<!-- Custom fields -->
{{.GetField "priority"}}     <!-- Get custom field value -->
{{.GetFieldHTML "description"}} <!-- Get rendered HTML -->

User Fields#

{{.Name.Get}}        <!-- Display name -->
{{.Email.Get}}       <!-- Email address -->
{{.FirstName.Get}}   <!-- First name -->
{{.LastName.Get}}    <!-- Last name -->
{{.ProfilePicture.Get}} <!-- Avatar URL -->

Group Fields#

{{.Name.Get}}        <!-- Group name -->
{{.Description.Get}}  <!-- Group description -->
{{.Icon.Get}}         <!-- Group icon -->

Query Syntax#

Use the Timill query syntax to filter items:

{{/* Simple query */}}
{{$items := queryItems "type = 'task'"}}

{{/* Query with multiple filters */}}
{{$items := queryItems "type = 'task' status = 'open'"}}

{{/* Query with variables */}}
{{$items := queryItems (printf "type = '%s' assignee = '%s'" .itemType .userId)}}

{{/* Complex query */}}
{{$items := queryItems "type = 'task' (status = 'open' OR status = 'in-progress')"}}

Query Operators#

OperatorDescriptionExample
=Equalsstatus = 'open'
!=Not equalsstatus != 'closed'
>Greater thandueDate > 2024-01-01
<Less thandueDate < 2024-12-31
>=Greater or equal
<=Less or equal
INIn liststatus IN ('open', 'in-progress')
ORLogical OR
ANDLogical AND (implicit)

Examples#

Simple Item List#

See full example: examples/scripted-widgets/01_simple_item_list.html

<div class="bg-white rounded-lg shadow">
  {{$items := queryItems (printf "type='%s'" .itemType)}}
  <h3 class="text-lg font-semibold p-4 border-b">{{.title}} ({{len $items}})</h3>
  
  <div class="divide-y">
    {{range $items}}
      <div class="p-3 hover:bg-gray-50">
        <div class="flex items-center justify-between">
          <span class="font-medium">{{.Title.Get}}</span>
          <span class="text-sm {{statusColor .Status.Get}}">{{.Status.Get}}</span>
        </div>
      </div>
    {{else}}
      <p class="p-4 text-gray-500">No items found</p>
    {{end}}
  </div>
</div>

Status Dashboard#

See full example: examples/scripted-widgets/02_status_dashboard.html

<div class="grid grid-cols-4 gap-4">
  {{$items := queryItems "type = 'task'"}}
  {{$open := filter $items "status = 'open'"}}
  {{$progress := filter $items "status = 'in-progress'"}}
  {{$review := filter $items "status = 'review'"}}
  {{$done := filter $items "status = 'done'"}}
  
  <div class="bg-blue-100 p-4 rounded text-center">
    <div class="text-2xl font-bold">{{len $open}}</div>
    <div class="text-sm">Open</div>
  </div>
  
  <div class="bg-yellow-100 p-4 rounded text-center">
    <div class="text-2xl font-bold">{{len $progress}}</div>
    <div class="text-sm">In Progress</div>
  </div>
  
  <div class="bg-purple-100 p-4 rounded text-center">
    <div class="text-2xl font-bold">{{len $review}}</div>
    <div class="text-sm">Review</div>
  </div>
  
  <div class="bg-green-100 p-4 rounded text-center">
    <div class="text-2xl font-bold">{{len $done}}</div>
    <div class="text-sm">Done</div>
  </div>
</div>

Recent Activity#

See full example: examples/scripted-widgets/03_recent_activity.html

<div class="bg-white rounded-lg shadow">
  {{$items := queryItems "type = 'task'"}}
  {{$recent := sort $items "updatedAt" "desc" | first 5}}
  
  <h3 class="text-lg font-semibold p-4 border-b">Recent Activity</h3>
  
  <div class="divide-y">
    {{range $recent}}
      <div class="p-3">
        <div class="flex items-center space-x-2">
          <span class="font-medium">{{.Title.Get}}</span>
          <span class="text-xs text-gray-500">{{.UpdatedAt.Get | formatDate}}</span>
        </div>
        <div class="text-sm text-gray-600">{{.Description.GetHTML | truncate 100}}</div>
      </div>
    {{else}}
      <p class="p-4 text-gray-500">No recent activity</p>
    {{end}}
  </div>
</div>

RBAC Enforcement#

All queries automatically enforce Role-Based Access Control:

  • Users only see items they have permission to view
  • Field-level permissions are applied automatically
  • Widget rendering respects group membership

Performance Tips#

Use Specific Queries#

<!-- Good: Specific query -->
{{$items := queryItems "type = 'task' status = 'open'"}}

<!-- Bad: Too broad -->
{{$items := queryItems ""}}

Limit Results#

{{/* Use first to limit */}}
{{$recent := first 10 $items}}

Cache When Possible#

{{/* Store in variable to avoid repeated queries */}}
{{$items := queryItems "type = 'task'"}}
{{range $items}}
  <!-- Use $items multiple times -->
{{end}}

Security#

FeatureDescription
Sandboxed ExecutionTemplates cannot access raw Go objects
RBAC EnforcementAll queries respect permissions
Script TimeoutWidgets timeout after 30 seconds
No External RequestsTemplates cannot make HTTP requests
HTML EscapingAll output is automatically escaped

Troubleshooting#

Widget Shows Blank#

  1. Check for template syntax errors
  2. Verify the query returns data
  3. Check browser console for JavaScript errors

Query Returns No Data#

  1. Verify item type names are correct
  2. Check RBAC permissions
  3. Test with a simpler query

Template Errors#

Common template errors:

<!-- Wrong: Missing quotes -->
{{$items := queryItems type = 'task'}}

<!-- Right: Proper quoting -->
{{$items := queryItems "type = 'task'"}}

Next Steps#