CQL in Approval Chains: Reference Guide
approval chains in charthop control who needs to sign off on a scenario before it's merged carrot expressions are used in two places the "only include stage if" condition on a stage (to control whether the stage fires at all) and the approver definition itself (to route to the right person dynamically) this guide covers both how approval chain cql is evaluated before building conditional logic, it helps to understand how charthop evaluates these expressions conditional stage expressions are evaluated against each change in the scenario, not the scenario as a whole — unless you use scenariochanges, which operates at the scenario level approver expressions (the "custom cql expression" approver option) are evaluated differently from inclusion conditions a bare change / change after is not in scope there to route an approver based on what changed, you must reach the change through scenariochanges (see custom cql approver expressions /#custom cql approver expressions below) the best way to test a change before / change after expression is to use it as a filter on the scenario changes tab if it returns results there, it will fire in the approval chain if a stage's approver resolves to nobody and no fallback approver is set, the chain will stall always configure a fallback approver for any stage that uses dynamic routing per change filter (no wrapper needed) in a stage's "only include stage if" field, change is an implicit per change context variable charthop evaluates each change individually, so you write the condition directly (e g , change type="create" && levelauto=\["ic0","ic1"] ) wrapping in scenariochanges filter{} is not valid in this context and will throw an "unknown field scenariochanges" error when to use each pattern if the condition is "does this change meet a condition?" write it directly using change field=value if the condition is "do all applicable changes together meet a condition?" use scenariochanges filter{} aggregation{} > threshold (see scenariochanges expressions /#scenariochanges expressions below) stage approver options when setting up a stage, the stage approvers field supports several modes option what it does manager routes to the direct manager of the job being changed grand manager routes to the manager's manager specific person routes to a named individual — always fires for that person person field routes to whoever is in a custom person type field (e g , hiring manager, talent partner) multiple stages, one per manager in the chain creates a dynamic stage for every manager in the hierarchy, all the way up — each approves in sequence custom cql expression define your own routing logic if you use a person field as the approver and the field is blank on a given job, the fallback approver will be used instead conditional stage expressions these go in the "only include stage if" field the stage only fires when the expression evaluates to true trigger only if a specific field changed change before base != change after base works for any field combine multiple fields with || (change before base != change after base) || (change before fieldcode1 != change after fieldcode1) trigger only if manager changed change before manager != change after manager trigger only if a specific person submits the scenario name 'first last' set the condition type to custom and use name with the person's full name this is the confirmed working pattern for submitter based routing scenariochanges expressions scenariochanges operates at the scenario level rather than per change use it when the approval decision should be based on the combined contents of the scenario rather than any individual change these are called approval group inclusion expressions in the platform trigger based on total cost impact across all changes scenariochanges sum > 500000 use when you want approval based on the total cost of all changes combined example require vp approval when the combined salary impact of all changes exceeds $500,000 replace the number with your org's threshold trigger if any change in the scenario affects a specific condition scenariochanges any{department='engineering'} use when you want approval if at least one change matches a condition example include the engineering director as an approver whenever any change in the batch affects an engineering employee — even if the rest of the scenario touches other departments trigger only if every change in the scenario matches a condition scenariochanges all{department='engineering'} use when you want approval only if every change matches a condition example route to a specialized engineering approval chain only when the entire batch contains engineering changes skip it if the scenario is mixed across departments combine scenario content with submitter identity scenariochanges all{department='engineering'} && title='ceo' use when you want to combine a condition about the changes with a condition about who is submitting the request example only include this approval stage when all changes are in engineering and the person submitting the request is the ceo this lets you create different approval flows based on both what's changing and who's requesting it custom cql approver expressions the custom cql expression approver option lets you compute the approver(s) dynamically instead of picking a manager or a named person the expression must resolve to a person (or a list of people) — for example a person type field on the job, or a value looked up from a table charthop routes the request to that person's job if the expression resolves to nobody — or to a person with no active job — the stage uses the fallback approver always set one for a stage that routes dynamically referencing the change being approved (important) when the approver depends on what changed — for example, routing to the owner of the department, region, or cost center a job is moving into — you must reach the change through scenariochanges a bare change / change after is empty when an approver expression is evaluated, so the expression resolves to nobody and the stage silently falls back to the fallback approver wrap the lookup in scenariochanges filter map scenariochanges filter{change after region } map{db regionowner regionname\[change after region] owner} this reads for each change that sets a region, look up that region in the regionowner table and return its owner (a person) each matching owner is added as an approver this is the same reason scenariochanges is required for scenario level inclusion conditions change / change after are only in scope inside a scenariochanges any / all / filter / map block in the "only include stage if" field a bare change after works because that field is evaluated per change; in a custom cql approver it is not, so you must use scenariochanges route to an owner looked up from a table store the mapping in a table — for example, regionowner with a unique key column (regionname) and a person owner column (owner) add the key to the job as a field — for example, a single select region field set the stage's stage approvers to a custom cql expression that filters scenariochanges to the changes that set the key, then maps each to the table's owner column (see the expression above) set a fallback approver — the stage falls back if a change's key has no matching row, or if the resolved owner has no active job common approval chain patterns route to a talent partner or custom role via a person field create a custom person type field (e g , talentpartner) on the job in the approval stage, set stage approvers to that person field charthop will route to whoever is in that field for each job — different jobs can route to different people automatically set a fallback approver for jobs where the field is blank this is one of the most common ways teams handle talent partner or hrbp approval routing without hardcoding names route to the owner of the value a job is moving into use a custom cql approver with a table lookup wrapped in scenariochanges when the approver isn't stored on the job itself but in a lookup table keyed by a field the change sets (region, cost center, department owner, etc ) scenariochanges filter{change after region } map{db regionowner regionname\[change after region] owner} remember the change data must come through scenariochanges — a bare change after in an approver expression resolves to nobody and falls back always configure a fallback approver send approvals up the entire manager chain in the stage approvers dropdown, select "multiple stages, one per manager in the manager chain" charthop will dynamically create one approval stage per manager level, from the direct manager up to the top of the org each approves in sequence this feature was built specifically for customers who need every manager in the hierarchy to sign off before a scenario is merged route differently based on department create separate conditional stages, each with its own "only include stage if" condition stage 1 (engineering director) change after department='engineering'stage 2 (finance lead) change after department='finance' stage 3 (final approver) always fires each stage only fires for changes in the relevant department the final stage can be set without a condition so it always fires as the last step regardless of department require approval only above a cost threshold scenariochanges sum > 100000 add this as a conditional stage for senior leadership changes below the threshold skip the stage entirely and route only through the standard chain key things to know test before you configure use the expression as a filter on the scenario changes tab first if it returns the changes you expect, it will behave the same way in the approval chain fallback approver matters any stage that uses dynamic routing — person fields, custom cql, manager chain — should have a fallback approver configured if the expression resolves to nobody, the chain falls to the fallback if there's no fallback, the chain stalls change data in a custom approver needs scenariochanges in a custom cql approver expression, a bare change / change after is empty and the stage will fall back wrap change references in scenariochanges filter map inclusion conditions in "only include stage if" don't need this — they already run per change one scenario, one approval chain a single scenario routes through one approval chain you can build a lot of conditional logic within that chain, but you can't branch a scenario into two separate chains simultaneously approvers can edit during their stage once it's their turn in the chain, approvers can edit the scenario before approving or rejecting once they act, the scenario is locked for that stage the changes tab is your best debugging tool if a conditional stage isn't firing as expected, check whether your expression returns results there if it doesn't, the expression syntax or field reference needs adjustment
