Cheat Sheet
ChartHop CQL (Carrot) Formula Cheat Sheet
CQL (Carrot Query Language) is ChartHop's built-in expression language for searching, filtering, calculating, and displaying people data. The same logic works everywhere โ only the syntax wrapper changes.
๐งฉ Where Are You Writing This?
Context | Syntax | Example |
|---|---|---|
Smart Calc / Smart Bucket | Plain expression | base * fieldCode1 |
Data Sheet calculated column | Plain expression | diffYears(startDate, today()) |
Form content block | {{ }} | {{formatMoney(base)}} |
Form (live answer from same form) | formAnswers['fieldCode'] | formAnswers['fieldCode1'] * 100 |
Document / letter template | {{ }} | {{name}} |
Markdown (profile tabs, home page) | {{ }} values ยท {% %} logic | See Markdown section |
Dashboard chart (Advanced mode) | {{ }} | {{base / fieldCode1}} |
โก Operators
Symbol | Meaning | Example |
|---|---|---|
= | Exact match | department.func='Engineering' |
!= | Not equal | status!='Inactive' |
: | Contains / fuzzy match | title:'Director' |
> < >= <= | Comparison | base>100000 |
&& or and | AND (interchangeable in most cases) | department.func='Sales' && base>80000 |
|| | OR | department.func='Sales' || department.func='Marketing' |
! | NOT | !department:'Sales' |
fieldCode:* | Has any value | fieldCode1:* |
? | Ternary โ if/then/else | base > 100000 ? 'High' : 'Low' |
?: | Elvis โ value if exists, else fallback | fieldCode1 ?: 0 |
dateOf.fieldCode | Date the field was last written | dateOf.base |
&& vs and: These work interchangeably in most filter and Smart Field contexts. If a formula behaves unexpectedly, try switching to &&.
๐ Performance & Goals
Weight an individual goal's rating by its assigned weight
fieldCode1 = goal rating ยท fieldCode2 = goal weightage ยท ?: 0 prevents a blank weight from breaking the formula. Create one Smart Calc per goal, then sum them for the total.
Total weighted goal score
Each fieldCode = a per-goal weighted rating Smart Calc field. Add a term for each goal your org uses. Keeping this as its own field makes it easy to reference in forms and the final rating calc.
Total performance rating (goals + values)
fieldCode1 = total weighted goal score ยท fieldCode2 = values/brand score. Adjust the 0.80/0.20 split to match your org's weighting โ multipliers must sum to 1.0.
Average only the goals that were actually used (skip zeros and blanks)
Extend with fieldCodeN > 0 ? fieldCodeN : null for each additional goal. mean() skips nulls but not zeros โ this pattern ensures unused goals don't drag the average down.
Combine multiple rating categories into one weighted score
Weights must sum to 1.0. ?: 0 ensures a missing rating doesn't nullify the entire formula.
Count how many times an employee hit a specific rating in recent cycles
Full history (no limit):
.reversed = most recent first ยท .limit(N) = last N cycles ยท Always use it inside .count{} โ not the original field code. Gives reviewers a consistency signal, not just the most recent result.
Show a rating only if it was submitted before a review cutoff date
Use in a form content block. Repeat for each rating field. Prevents backdated or late entries from surfacing during calibration.
๐ฐ Compensation
Compa-ratio โ where someone sits relative to their band midpoint
fieldCode1 = band midpoint. Result of 1.0 = at midpoint. Format as percent for display. Foundation for pay equity analysis, merit planning, and outlier flagging.
Salary range penetration โ how far through the band (0 = min, 1 = max)
fieldCode1 = band min ยท fieldCode2 = band max. Useful alongside compa-ratio when bands are wide.
Total cash compensation (base + target bonus)
fieldCode1 = target bonus percentage field.
New base after a merit increase
fieldCode1 = merit percentage field. Best used as a Data Sheet calculated column during comp planning โ no permanent field needed.
Prorated salary for a mid-year hire
Replace '2026-01-01' with your fiscal year-end date.
Prorated annual cost with start and end date logic (full fiscal year)
Use this for scenario planning and headcount cost forecasting. Handles mid-year starts, mid-year terms, and full-year employees in one expression. To show proration as a % of annual cost: proratedFyCost / (monthlyCost * 12) * 100. Update the year dates each cycle.
Prior salary โ what someone earned before their last increase
dateOf.base = date of last base change. Subtracting 1 day retrieves the value just before it. No custom fields needed.
Year-over-year base salary change
As a percentage:
As a dollar amount:
Replace '2025-01-01' with your baseline date. Use asOfPrimary โ not asOf โ to exclude draft scenario data.
Total comp including equity vesting
Built-in fields only. Adds total cash to the value of equity vesting in the next 12 months.
๐ Tenure, Dates & Eligibility
Question | Formula |
|---|---|
How long has someone been here? | diffYears(startDate, today()) |
How long in their current role? | diffDays(titleDate, today()) |
When is their next work anniversary? | nextAnniversary(startDate) |
How old is this employee? | diffYears(birthdate, today()) |
Did their manager change recently? | diffDays(dateOf.manager, today()) <= 90 |
When were they last eligible for a raise? | dateOf.base |
When will they next be eligible? | dateOf.base + 365 |
How long has this req been open? | diffDays(openDate, today()) |
When did this person become a manager? | dateOf.directReports |
Did they become a manager in the last 30 days? | diffDays(dateOf.directReports, today()) <= 30 |
๐ชฃ Smart Bucket Templates
Smart Buckets assign a color-coded label based on CQL conditions. Use them on the org chart, Data Sheet, and dashboards for instant visual grouping.
Performance tiers ยท fieldCode1 = weighted score field
Label | Expression |
|---|---|
High Performer | fieldCode1 >= 4.5 |
Strong Performer | fieldCode1 >= 3.5 && fieldCode1 < 4.5 |
Meets Expectations | fieldCode1 >= 2.5 && fieldCode1 < 3.5 |
Needs Improvement | fieldCode1 < 2.5 |
Compa-ratio bands ยท fieldCode1 = compa-ratio Smart Calc field
Label | Expression |
|---|---|
Below Band | fieldCode1 < 0.80 |
In Range | fieldCode1 >= 0.80 && fieldCode1 <= 1.20 |
Above Band | fieldCode1 > 1.20 |
Tenure bands ยท Uses built-in startDate
Label | Expression |
|---|---|
New Hire (<1 yr) | diffYears(startDate, today()) < 1 |
Early Career (1โ3 yrs) | diffYears(startDate, today()) >= 1 && diffYears(startDate, today()) < 3 |
Established (3โ5 yrs) | diffYears(startDate, today()) >= 3 && diffYears(startDate, today()) < 5 |
Veteran (5+ yrs) | diffYears(startDate, today()) >= 5 |
Merit eligibility ยท fieldCode1 = performance rating field
Label | Expression |
|---|---|
Eligible | diffMonths(startDate, today()) >= 6 && fieldCode1 >= 3 |
Ineligible โ Too New | diffMonths(startDate, today()) < 6 |
Ineligible โ Rating | fieldCode1 < 3 |
Flight risk ยท fieldCode1 = band minimum field
Label | Expression |
|---|---|
High Risk | diffMonths(titleDate, today()) >= 24 && base < fieldCode1 |
Medium Risk | diffMonths(titleDate, today()) >= 18 || base < fieldCode1 |
Low Risk | diffMonths(titleDate, today()) < 18 && base >= fieldCode1 |
Span of control ยท Uses built-in directReports ยท Apply to Jobs
Label | Expression |
|---|---|
Under-leveraged (1โ3) | length(directReports) >= 1 && length(directReports) <= 3 |
Healthy (4โ8) | length(directReports) >= 4 && length(directReports) <= 8 |
Over-leveraged (9+) | length(directReports) >= 9 |
Open req age ยท Uses built-in openDate
Label | Expression |
|---|---|
Fresh (<30 days) | diffDays(openDate, today()) < 30 |
Active (30โ60 days) | diffDays(openDate, today()) >= 30 && diffDays(openDate, today()) < 60 |
Aging (60โ90 days) | diffDays(openDate, today()) >= 60 && diffDays(openDate, today()) < 90 |
Stale (90+ days) | diffDays(openDate, today()) >= 90 |
๐ Forms: Embedding Live CQL Data
How to add a CQL content block to a form
- Go to People Ops Tools โ Forms โ open or create a form
- Click + Add Question / Block โ select Content block type
- Type your text and embed CQL using {{expression}} syntax
- Save and preview โ expressions render live against the employee the form is about
Content blocks are read-only. They display data but cannot be edited by the reviewer. Use formAnswers['fieldCode'] (instead of just fieldCode) when reading a value entered earlier in the same form session.
Employee context card โ put this at the top of any review or comp form
Replace fieldCode1โ4 with your band min, band max, midpoint, and rating fields.
Compensation flag โ auto-surface outliers to the reviewer
Prior vs. current salary
YoY salary change
Total weightage validation โ confirm goal weights sum to 100%
Extend with +(fieldCodeN*100) for each additional goal weightage field.
List a manager's direct reports inline โ useful for manager attestation forms
Great for single manager attestation forms where you want the reviewer to see all their reports in one place without submitting separately per person.
๐ Markdown: Conditional Display
Use in profile tabs, the home page, and document templates. {{ }} renders a value. {% %} controls show/hide logic.
Show a block only if a condition is true
Show different content based on a condition
Show content only if a field was set on or before a specific date
fieldCode1:* confirms a value exists ยท dateOf.fieldCode1 confirms when it was written ยท asOfPrimary retrieves the locked value.
Nested conditions
๐ Common Filter Queries
Expression | What it returns |
|---|---|
is:active | All active employees |
is:manager | People managers only |
!is:manager | Individual contributors only |
department.func='Engineering' | Specific department |
!department:'Sales' | Exclude a department |
startDate<'2022-01-01' | Started before a date |
!fieldCode1:* | Missing a specific field value |
fieldCode1>=4 | Field at or above a threshold |
(base / fieldCode1) < 0.90 | Below 90% compa-ratio |
diffYears(startDate, today()) >= 3 | 3+ years tenure |
diffMonths(titleDate, today()) >= 18 | No title change in 18+ months |
diffDays(dateOf.manager, today()) <= 90 | Manager changed in last 90 days |
diffDays(dateOf.directReports, today()) <= 30 | Became a manager in last 30 days |
is:open && daysOpen>90 | Stale open reqs |
!location:* | Missing location |
fieldCode1 >= 4 && (base / fieldCode2) < 0.90 | High performer, underpaid |
anniversary=today | Work anniversary is today |
endDateOrg=today+11 | Departing in exactly 11 days |
โ๏ธ Actions & Approval Chains
Carrot powers both the filters that determine who receives an action and the conditional logic that controls whether an approval stage fires. These are some of the most common patterns.
Trigger an action on a specific date relative to end date
Use = not <= for scheduled actions. Using <= causes the action to fire every day until the date arrives. Replace 11 with however many days of lead time you need.
Trigger an action on work anniversary
For orgs that use a custom hire date field instead of the built-in startDate, create a Smart Calc using nextAnniversary(customHireDateField) and filter on that field equaling today instead.
Filter action audience to people who have not yet completed a form
Use this as a scheduled action filter to send reminders only to people who still have outstanding tasks. Swap status:pending for status:done to target completers instead.
Conditional Approval Stage Expressions
The expressions below go in the "Only include stage if" field on an approval stage. They control whether a given stage fires at all. Always test your expression as a filter on the scenario Changes tab first โ if it returns results there, it will fire in the approval chain.
Trigger only if a specific field changed
Combine multiple fields with ||: (change.before.base != change.after.base) || (change.before.fieldCode1 != change.after.fieldCode1)
Trigger only if manager changed
Trigger based on total cost impact across all changes in a scenario
Use when you want approval based on the combined salary impact of all changes, not just a single job. For example: require VP approval when the total cost of a scenario exceeds $500,000.
Trigger if any change in the scenario affects a specific department
Use when at least one change in the batch needs to match โ even if others don't. Good for routing Engineering Director approval whenever any Engineering job is touched.
Trigger only if every change in the scenario is in a specific department
Stricter than .any{} โ the stage only fires if the entire scenario batch is within that department. Skip it if the batch is mixed.
Trigger based on both scenario content and who is submitting
Combines a condition about the changes with a condition about the submitter. In this example: only include this stage when all changes are in Engineering AND the person submitting is the CEO. Mix and match to build precise routing logic.
Trigger only when a specific person submits a scenario
Set the condition type to Custom and use name: with the person's name. Confirmed working pattern for conditional routing based on the submitter's identity.
๐ Reporting & Survey Queries
These functions are used in dashboard charts (Advanced mode) to report on form completion, survey responses, and headcount metrics.
Form completion rate for a review cycle
One of the most common dashboard queries for performance and engagement reporting. If the form name contains a colon (:) or has a trailing space, use the form and assessment IDs instead of names โ special characters in names can break the query.
Count responses above a threshold for a specific question
Use for single-metric charts on engagement or pulse surveys. fieldCode is the field code of the question, not the form name.
Count responses to a form question filtered by org
Adding .filter{with(submitPerson,jobFilter)} applies your current org chart filter so the chart respects department, location, or other slices.
Survey completion rate with org filter
Rollup a field value across a manager's entire team โ apply to manager jobs
Average a field across the team:
underJobs traverses the full org tree below a person, not just direct reports. Great for manager scorecards, team cost rollups, and org chart visualizations. Use directJobs instead if you only want one level down.
Multi-level manager chain โ display reporting levels as separate fields
Level 3 (manager's manager's manager):
Level 4:
Create one Smart Calc field per level. Built-in fields cover levels 1 and 2. Many employees at higher levels will return blank โ that's expected. Use ?: '' to suppress null display if needed.
Count number of manager changes during tenure
Subtracts 1 to exclude the original hire assignment. Counts across all jobs the person has held at the org.
๐ Person Field Resolution
Assign a linked person (e.g., talent partner, buddy) based on conditions
Step 1 โ Smart Bucket: Each condition outputs a personId value Step 2 โ Smart Calc: Expression is just the bucket field code:
Step 3 โ Set expected return type to Person in the Smart Calc settings
Use personId โ not email. Email does not reliably resolve to the Person type in ChartHop. To find a personId: use the ChartHop API, or reference manager.id as a pattern example.
๐ฐ๏ธ asOf vs asOfPrimary
๏ปฟ | asOf | asOfPrimary |
|---|---|---|
Includes scenario/draft data | โ | โ |
Use for | What-if / scenario planning | Baselines, historical lookups, audit-safe reporting |
๐งฎ Utility Functions
Function | What it does | Example |
|---|---|---|
round(x, 2) | Round to decimal places | round(fieldCode1, 2) |
formatRound(x, 1) | Round + format as string | formatRound(fieldCode1, 1) |
formatMoney(x) | Format as currency | formatMoney(base) |
formatPercent(x) | Format as percent | formatPercent(fieldCode1) |
formatDate(d, 'pattern') | Format date as string | formatDate(startDate, 'MMMM d, yyyy') |
abs(x) | Absolute value | abs(base - fieldCode1) |
max(a, b) | Larger of two values | max(base, fieldCode1) |
min(a, b) | Smaller of two values | min(base, fieldCode1) |
mean(a, b, c) | Average, excluding nulls | mean(fieldCode1, fieldCode2, fieldCode3) |
length(list) | Count items in list | length(directReports) |
diffYears(d1, d2) | Years between dates | diffYears(startDate, today()) |
diffMonths(d1, d2) | Months between dates | diffMonths(startDate, today()) |
diffDays(d1, d2) | Days between dates | diffDays(openDate, today()) |
nextAnniversary(d) | Next anniversary date | nextAnniversary(startDate) |
asOf(date, {expr}) | Value on date (incl. scenario) | asOf('2024-07-01', {fieldCode1}) |
asOfPrimary(date, {expr}) | Value on date (primary only) | asOfPrimary('2025-01-01', {base}) |
findHistoryValues({field}) | All historical values as list | findHistoryValues({fieldCode1}) |
vestValue(d1, d2) | Equity vesting value in window | vestValue(today(), nextAnniversary(startDate)) |
distance(addr1, addr2, unit) | Distance between two addresses | distance(address, location.address, 'miles') |
db.job.find{condition} | Query jobs across the org | db.job.find{it.startDate >= today} |
๐ก Quick Reference: Common Mistakes
โ Mistake | โ Fix |
|---|---|
Field is blank and breaks formula | Add ?: 0 โ e.g., fieldCode1 ?: 0 |
mean() averaging zeros as if they're real scores | Use fieldCode1 > 0 ? fieldCode1 : null |
Using field code inside .count{} | Use it โ e.g., .count{it='Value'} |
asOf pulling in draft scenario data | Switch to asOfPrimary for baselines |
Person field returning blank | Check Smart Bucket outputs personId, not email |
Formula breaks after renaming a field | Field codes don't auto-update โ fix references manually |
and not working in a specific context | Switch to && |
findHistoryValues returning oldest values first | Add .reversed before .limit() |
Dashboard query breaks when form name has a colon or trailing space | Use the form/assessment ID instead of the name |
Scheduled action fires every day instead of once | Use = not <= for date-based action filters |
Approval chain stage fires even when condition isn't met | Test the expression as a filter on the scenario Changes tab first |
findFormTasksByAssessment chart breaks when adding a filter | Use .filter{with(person,jobFilter)} โ not with(submitPerson,jobFilter) for task-based queries |
