Skip to main content
A dashboard is a tab in your Forest project where you group charts to track KPIs and operational metrics. You can create as many dashboards as needed, one per team, use case, or business domain.

Creating a dashboard

  1. Enable Layout Editor mode from the top navigation.
  2. Click + New next to the dashboard tabs.
  3. Give the dashboard a name.
  4. Add charts to it (see Charts).

Managing dashboards

In Layout Editor mode, you can:
  • Rename a dashboard by clicking its tab name.
  • Reorder dashboards by dragging the tabs.
  • Delete a dashboard by clicking the trash icon next to its name.
Deleting a dashboard also deletes all charts it contains.

Adding charts

Charts are the building blocks of a dashboard. Forest supports two ways to populate chart data:
  • Simple mode, configure charts from the UI by selecting a collection, an aggregate function, and optional filters. No code required.
  • Query mode, write a raw SQL query directly in the UI for advanced analytics.
See Charts for the full reference on chart types and configuration.

API-powered charts

When chart data requires custom business logic, calling an external API, joining data across sources, or applying transformations not possible in SQL, you can implement the data retrieval in your agent. Use agent.addChart() to register a named chart handler:
agent.addChart('monthlyRecurringRevenue', async (context, resultBuilder) => {
  const rows = await context.dataSource
    .getCollection('payments')
    .aggregate(
      { conditionTree: { field: 'status', operator: 'equal', value: 'paid' } },
      { operation: 'Sum', field: 'amount' }
    );

  return resultBuilder.value(rows[0].value);
});
@agent.add_chart('monthlyRecurringRevenue') do |context, result_builder|
  result = context.datasource.get_collection('payment').aggregate(
    Filter.new(condition_tree: Nodes::ConditionTreeLeaf.new('status', Operators::EQUAL, 'paid')),
    Aggregation.new(operation: 'Sum', field: 'amount')
  )
  result_builder.value(result[0]['value'])
end
Then in the UI, create a chart on the dashboard, select API as the data source, and enter the chart URL:
/forest/_charts/monthlyRecurringRevenue
The chart type selected in the UI must match the resultBuilder method used in your back-end (value, timeBased, distribution, percentage, objective, leaderboard). The chart name must be URL-safe.

Record-specific API charts

To scope a chart to a specific record, register it on the collection instead:
agent.customizeCollection('customers', collection => {
  collection.addChart('revenueByCustomer', async (context, resultBuilder) => {
    const rows = await context.dataSource
      .getCollection('payments')
      .aggregate(
        {
          conditionTree: {
            aggregator: 'And',
            conditions: [
              { field: 'status', operator: 'equal', value: 'paid' },
              { field: 'customer:id', operator: 'equal', value: context.recordId },
            ],
          },
        },
        { operation: 'Sum', field: 'amount' }
      );

    return resultBuilder.value(rows[0].value);
  });
});
@agent.customize_collection('customer') do |collection|
  collection.add_chart('revenueByCustomer') do |context, result_builder|
    result = context.datasource.get_collection('payment').aggregate(
      Filter.new(
        condition_tree: Nodes::ConditionTreeBranch.new('And', [
          Nodes::ConditionTreeLeaf.new('status', Operators::EQUAL, 'paid'),
          Nodes::ConditionTreeLeaf.new('customer:id', Operators::EQUAL, context.get_record_id)
        ])
      ),
      Aggregation.new(operation: 'Sum', field: 'amount')
    )
    result_builder.value(result[0]['value'])
  end
end
The chart URL for collection-scoped charts follows this pattern:
/forest/_charts/customers/revenueByCustomer

Access control

Dashboard visibility follows your project’s role permissions. Users only see the charts they have access to based on their role’s collection permissions.