Dashboards: How to Build & Customize
ChartHop Dashboards: How to Build & Customize
What You Can Add to a Dashboard
Block Type | What It Does |
|---|---|
Chart | Visualizes data (bar, line, pie, etc.) |
Text Block | Displays dynamic text, AI summaries, or personalized messages |
Header | Organizes sections with a title |
Key Concepts & Definitions
jobFilter โ The combination of filters a user has selected on the dashboard (e.g., filtered to Engineering, or New York). Always include this in your formula so your chart responds to those selections.
intervalStart / intervalUntil โ When a chart displays data over time (e.g., month by month), each bar or data point represents one interval. intervalStart is the first day of that period, and intervalUntil is the first day of the next period. These are dynamic variables automatically set by the reporting engine for each period โ you don't type dates manually here.
Example: For the January bar on a chart, intervalStart = '2026-01-01' and intervalUntil = '2026-02-01'. For February, they shift automatically to '2026-02-01' and '2026-03-01'.
target โ The person a form response is about (e.g., the employee being reviewed).
submit โ The person who submitted the form response (e.g., the manager filling out a review).
groupBy{} โ Groups results by a field (like department or location), automatically updating as your org changes. No manual edits needed when teams are added or renamed.
value โ The actual response value from a form (a score, rating, or text answer).
Chart Types
When adding a chart to your dashboard, you can choose how the data is displayed. The formula you write determines what data shows up โ the chart type determines how it looks.
Chart Type | Best Used For |
|---|---|
Bar / Column Chart | Comparing values across groups (e.g., headcount by department) |
Line Chart | Showing trends over time (e.g., headcount growth month over month) |
Pie / Donut Chart | Showing proportional breakdown (e.g., workforce by employment type) |
Single Value | Displaying one number or metric prominently (e.g., total headcount, current turnover rate) |
Gauge | Visualizing a metric against a target or range (e.g., turnover rate vs. goal) |
Table | Showing detailed, multi-column data side by side |
Crosstab Table | Comparing two dimensions at once (e.g., headcount by department and gender) |
NPS Chart | Displaying Net Promoter Score breakdowns from survey data |
Bubble Chart | Exploring individual-level people data across up to four dimensions (X axis, Y axis, size, and color) โ available in Data Explorer |
Time Series Charts
Any chart can be displayed as a time series โ meaning ChartHop will calculate your formula for each time period (monthly, quarterly, etc.) and plot them together. This is what powers over-time reports like headcount growth, monthly new hires, or rolling turnover rate.
To make a chart work correctly as a time series, use intervalStart and intervalUntil in your change or form response functions. The engine automatically substitutes the correct dates for each period.
Example: A bar chart using findHires(intervalStart, intervalUntil).count() will show a separate bar for each month, each reflecting only the hires from that month.
Data Explorer
Data Explorer is an interactive, full-screen environment for exploring your people data. Unlike standard dashboard charts that display aggregate data, Data Explorer surfaces individual-level data points โ making it ideal for spotting patterns across your workforce. Clicking on a person opens their side panel so you can drill directly into their profile. Charts built in Data Explorer can be added directly to any dashboard. One of its most powerful uses is building a Nine Box visualization by plotting two performance or potential rating fields against each other.
How Chart Formulas Work
Every chart formula follows the same pattern:
Part | What It Does |
|---|---|
db.job | Look in the jobs table |
.find(jobFilter) | Respect the dashboard's active filters |
.groupBy{field} | (Optional) Break results into groups |
.sum{} / .count() / .mean{} | Calculate the number |
Brackets & syntax
Use { } curly braces around filters and expressions (e.g., {department="Engineering"}), and ( ) parentheses only for function call parameters (e.g., find(jobFilter)). Mixing these up is one of the most common formula errors.
Common Chart Formulas
Goal | Formula |
|---|---|
Total headcount | db.job.find(jobFilter).sum{headcount} |
Number of employees | db.job.find(jobFilter).count() |
Average base salary | db.job.find(jobFilter).mean{baseComp.annualized} |
Headcount by department | db.job.find(jobFilter).groupBy{department}.sum{headcount} |
Headcount by location | db.job.find(jobFilter).groupBy{location}.sum{headcount} |
Avg salary by level | db.job.find(jobFilter).groupBy{level}.mean{baseComp.annualized} |
Headcount by dept & gender | db.job.find(jobFilter).groupBy{department, gender}.sum{headcount} |
Tracking Hires, Departures & Promotions
Use these functions to report on workforce changes. Use intervalStart and intervalUntil as the date parameters in dashboard charts โ the engine automatically substitutes the correct dates (e.g., '2026-01-01', '2026-02-01') for each period.
Goal | Formula |
|---|---|
New hires | findHires(intervalStart, intervalUntil).count() |
Departures | findDepartures(intervalStart, intervalUntil).count() |
Promotions | findPromotions(intervalStart, intervalUntil).count() |
Hires in Engineering | findHires(intervalStart, intervalUntil).filter{dept:engineering}.count() |
Hires by department | findHires(intervalStart, intervalUntil).groupBy{department}.count() |
Voluntary departures | findDepartures(intervalStart, intervalUntil).filter{depart:voluntary}.count() |
Turnover Rate
Calculated as: departures รท average(headcount at start, headcount at end)
Goal | Formula |
|---|---|
Overall turnover (rolling 12 months) | turnoverRate('-1y+1d', intervalUntil) |
Voluntary turnover only | turnoverRate('-1y+1d', intervalUntil, jobFilter, {depart:voluntary}) |
Voluntary turnover in Engineering | turnoverRate('-1y+1d', intervalUntil, {dept:engineering}, {depart:voluntary}) |
Analyzing Form & Survey Responses
Terminology reminder
- target = the person the form is about (e.g., the employee being reviewed)
- submit = the person who submitted the form (e.g., the reviewing manager)
Finding your question field ID
Every form question in ChartHop is linked to a field ID (e.g., managerReviewRating). You'll need this to write chart formulas for survey or review data. To find it, open the form editor and look for the Copy question ID to clipboard button on the question you want to report on.
Pull responses from any form
Goal | Formula |
|---|---|
Average rating score | findAnswers('managerReviewRating', intervalStart, intervalUntil).mean{value} |
Avg rating grouped by reviewee's department | findAnswers('managerReviewRating', intervalStart, intervalUntil).groupBy{target.department}.mean{value} |
Ratings submitted by Engineering, about people in Sales | findAnswers('rating', intervalStart, intervalUntil).filter{submit.department:engineering and target.department:sales}.mean{value} |
Pull responses from a specific review or survey
The assessment name must match exactly as it appears in ChartHop, including capitalization.
Goal | Formula |
|---|---|
Avg rating from a specific review, grouped by reviewee's dept | findAnswersByAssessment('downwardRating', '1H Performance Review').groupBy{target.department}.mean{value} |
Favorable response % for an engagement question | (findAnswersByAssessment('questionId', 'Engagement Survey').count{value="Agree"} + findAnswersByAssessment('questionId', 'Engagement Survey').count{value="Strongly Agree"}) / findAnswersByAssessment('questionId', 'Engagement Survey').count() |
eNPS score grouped by team | findAnswersByAssessment('questionId', 'Q4 Engagement Survey').groupBy{submit.team}.nps{value} |
Calculating NPS
Instead of manually writing out promoter and detractor counts, use the built-in .nps{} method:
This calculates NPS automatically based on response values (9โ10 = promoters, 7โ8 = passives, 0โ6 = detractors).
Fields available for filtering or grouping
Field | Definition |
|---|---|
value | The response value (score, rating, text) |
target | The person the form is about |
target.department | Department of the person being reviewed |
submit | The person who submitted the form |
submit.department | Department of the person who submitted |
date | Date the response was submitted |
Customizing Chart Labels & Display Options
Editing labels โ Chart labels appear greyed out but are editable. Click directly on a label to rename it. A reset icon appears once you've made a change, which reverts it back to the default.
Controlling what groups appear โ In the chart's Sort/Show settings, two checkboxes let you control grouping behavior:
- Group remaining as "Other" โ rolls up any groups outside your top N into a single "Other" bucket
- Include "None" in results โ shows records where the grouped field has no value
Chart-level filters vs. formula filters โ If you add a filter at the top of the chart AND inside your groupBy{} formula, they work independently and won't override each other. Use the chart-level filter for broad audience scoping and the formula-level filter for specific calculation logic.
Sharing Dashboards
Access Level | What They Can Do |
|---|---|
Owner | Full edit access, can share with others |
Editor | Can edit charts and content |
Viewer | Can view the dashboard; can apply filters if the dashboard does not use full data sharing |
Note on filtering: Viewers can only use dashboard filters if the dashboard is set to normal data access. If a dashboard uses full data sharing (exposing all org data regardless of the viewer's permissions), filters are intentionally disabled as a privacy safeguard.
Text Blocks: Dynamic & Personalized Content
Wrap any field in {{ }} and it populates with the viewer's own data:
Every employee sees their own information โ no need to duplicate dashboards by team.
Add an AI Summary of any chart
The chart name must match exactly, including capitalization.
Quick-Reference Cheat Sheet
Tips & Known Behaviors
- Building a column-style table? Temporarily disable the reporting engine, create your chart, then re-enable it.
- Comparing to a prior period? Use asOf('-1m', {...}) to pull data from a past point in time and subtract from the current value.
- Chart not responding to filters? Make sure jobFilter is included in your .find() call.
- Chart showing 0 values? Bar and column charts suppress zero results by default. If you need zeros to appear, use a Crosstab Table instead.
- AI summaries not respecting dashboard filters? The AI summary reflects the underlying chart data. Make sure your charts include jobFilter so the summary reflects the filtered view.
- Currency in charts: All monetary values are converted to your organization's primary currency. If your org spans multiple countries, be aware that dashboard charts normalize everything to the org-level currency.
- "Other" grouping and filters: The "Other" bucket is calculated at submission time, not dynamically. Applying a manager-level or audience filter after the fact will not re-calculate what falls into "Other."
- Filtering out specific groups: You can filter out a specific group value using {field!="Value"} in your formula. However, for multi-select fields like Teams, filtering out one value may remove a person from all groups they belong to โ not just the filtered one. In those cases, build each group as a separate formula line rather than using exclusion filters.
- De-identified survey dashboards: Use findAnswersByAssessment() to pull results. Groupings like department or location are calculated at submission time, not dynamically per manager filter โ this can occasionally surface small group results unexpectedly.
