Portal Community

visibleTo — Simple Role List

The visibleTo property accepts a list of roles. The widget renders only if the user has at least one of the listed roles:

// Widget placement config
{
  "widgetId": "delete-lead-button",
  "type": "Button",
  "config": {
    "label": "Delete Lead",
    "variant": "danger",
    "action": { "type": "trigger-workflow", "workflowId": "DeleteLead" }
  },
  "visibleTo": ["admin", "sales-manager"]   // hidden from sales, viewer, etc.
}

visibilityExpression — Full Expression Power

For complex conditions, use visibilityExpression with a token expression that evaluates to a boolean:

// Visible only when the user is an admin AND the lead is in "qualified" status
{
  "widgetId": "qualify-action-panel",
  "type": "ActionPanel",
  "visibilityExpression": "{{ context.roles.includes('admin') && row.status === 'qualified' }}"
}

// Visible to admin OR when the user is the assigned owner of this record
{
  "widgetId": "edit-button",
  "type": "Button",
  "visibilityExpression": "{{ context.roles.includes('admin') || row.ownerId === context.userId }}"
}

visibleTo vs. visibilityExpression

PropertySyntaxUse when
visibleToArray of role stringsSimple role whitelist (OR logic among listed roles)
visibilityExpressionToken expression → booleanAND logic, data-conditional, user-ownership checks, complex rules

If both are set, the widget is visible only when both conditions are true.

Hidden vs. Disabled

App Studio widget visibility always means hidden — the widget is not rendered at all. There is no built-in "render but disable based on role" behavior. To show a widget in a disabled state for certain roles, use two widget instances:

// Pattern: show editable for editors, read-only for viewers
// Widget A — editable version, shown to editors
{
  "widgetId": "name-field-edit",
  "type": "TextInput",
  "visibleTo": ["editor", "admin"]
}

// Widget B — read-only version, shown to viewers
{
  "widgetId": "name-field-readonly",
  "type": "Text",
  "config": { "value": "{{ variables.leadName }}" },
  "visibilityExpression": "{{ !context.roles.includes('editor') && !context.roles.includes('admin') }}"
}

Practical Examples

// Admin-only section header
{
  "widgetId": "admin-section-label",
  "type": "SectionHeader",
  "config": { "label": "Admin Controls" },
  "visibleTo": ["admin"]
}

// Approval button — only for users with 'approver' role
{
  "widgetId": "approve-btn",
  "type": "Button",
  "config": { "label": "Approve", "action": { "type": "trigger-workflow", "workflowId": "ApproveRecord" } },
  "visibleTo": ["approver", "admin"]
}

// Revenue column in data grid — only for managers
{
  "widgetId": "leads-grid",
  "type": "DataGrid",
  "config": {
    "columns": [
      { "field": "name", "header": "Name" },
      { "field": "status", "header": "Status" },
      {
        "field": "dealValue",
        "header": "Deal Value",
        "visibleTo": ["manager", "admin"]   // Column-level visibility
      }
    ]
  }
}
Widget visibility is client-side Widget visibility controls rendering — it does not prevent API calls made by visible widgets from returning data. Use data scoping (see next section) to ensure role-based data filtering happens server-side.