A custom KPI is a formula you write once and then reuse anywhere a KPI can be selected in Feedier: insights, reports, KPI charts, score tables and benchmarks. It is recalculated live, against the filters and the team scope of the page it is displayed on.
Where to create a custom KPI
Go to Settings → Data model → KPI Formulas.
Click Add Formula.
Fill in the Name, an optional Description, and the Formula itself.
Choose a Suffix (
%,/5,min, a currency…) and a Format (0,0.0,0.00,0,0,0,00).Turn on Lower is better when a lower value is the desired outcome, then set the Visibility of the KPI for your teams.
The formula is executed when you save. If it cannot be computed, Feedier rejects it with “This formula is invalid.”
Best practices when creating KPIs
Use simple, intuitive names that are understood at a glance. Avoid jargon.
Add a short description whenever the scope of the KPI is not obvious from its name. Everyone reading a report should know what the number covers.
Always set the suffix and the format. A KPI with no unit is read wrong:
%for percentages,/5for ratings,minorhfor durations, a currency symbol for money. There is no prefix field — the suffix is appended after the value.Turn on Lower is better for KPIs where a drop is good (detractor rate, churn, response time, customer effort), so trends and colours are read the right way across the platform.
Prefer one clear KPI per question. Two simple formulas are easier to maintain than one formula doing everything.
Understanding the Formula Builder
A custom KPI is a mathematical expression built from one or several Feedier functions. Each function can be narrowed down with conditions.
➡️ A condition is always written as a group of three values:
function("parameter.property", "operator", "value")Part | Role | Example |
Function | The metric you want to measure. |
|
Parameter + property | The field you filter on, written with a dot: the parameter, then the property. |
|
Operator | How the value must match. See 3. Operators below. |
|
Value | A single value, or an array of values. |
|
Put together:
feedback("import.type", "=", "trustpilot")⚠️ The double quotes around the parameter, the operator and the value are mandatory. Only booleans and numbers inside an array can be written without quotes.
Several conditions
Repeat the group of three. Conditions are combined with AND, and the total number of arguments must always be a multiple of three.
feedback("survey.id", "=", "1526", "nps.profile", "=", "promoter")Several values for one condition
Pass an array. Values inside an array are combined with OR.
feedback("team.id", "=", ["1234", "323", "12332"])No condition at all
A function called with no argument returns the metric over the whole scope currently applied to the page.
feedback()
Every function is always restricted by the filters, the date range and the team scope of the page where the KPI is displayed, on top of the conditions you write. A formula can never return data a user is not allowed to see.
Combining functions
Functions can be combined with +, -, *, /, ^ (power), % (modulo) and parentheses.
feedback("nps.profile", "=", "detractor") / feedback() * 100A division by zero returns 0 instead of failing. A KPI stuck at 0 is often a denominator that matches nothing.
1. Functions
All the functions available in the formula builder, and what they return.
Function | Returns | Unit / scale |
| Number of feedback matching the conditions. | Count |
| Number of feedback having at least one matching answer. | Count |
| Number of feedback invitations sent (email + SMS). | Count |
| Net Promoter Score. | −100 to 100 |
| Satisfaction ratio. | 0 to 100 |
| Sentiment score of the analysed text. | 0 to 100 |
| Customer Effort Score. | Scale of the question |
| Average of rating questions. | Scale of the question |
| Average of rating table questions. | Scale of the question |
| Average time to complete a survey. | Minutes |
| Average time to resolve a feedback. | Seconds |
| Estimated revenue impact. | Currency |
| Aggregate of a numeric attribute. | Unit of the attribute |
| A value read from an external API. | Any |
feedback()
Number of feedback matching the conditions.
Accepts: attribute, feedback, import, nps, question, response, segment, survey, team, topic.
Formula | Result |
feedback() | All feedback in the current scope. |
feedback("import.type", "=", "google_review") | Feedback imported from Google reviews. |
feedback("team.id", "=", "9921") | Feedback of one team.
Feedback of several teams. |
feedback("segment.id", "=", "1234") | Feedback belonging to a segment. |
feedback("survey.id", "=", "1526") | Feedback collected on one source. |
feedback("question.type", "=", "nps") | Feedback that contains an NPS question. |
feedback("question.id", "=", "1526") | Feedback that contains question 1526.
Feedback that contains either question. |
feedback("question.option_name", "=", "Breakfast") | Feedback where the choice or rating-table option “Breakfast” was answered. |
feedback("topic.name", "=", "Shipping cost") | Feedback tagged with the topic “Shipping cost”. |
feedback("attribute.name", "=", "city") | Feedback that carries the attribute
Feedback where |
feedback("nps.profile", "=", "promoter") | Feedback from promoters. |
feedback("feedback.type", "=", "completed") | Completed feedback. See the note on |
⚠️ push.canal and push.status are not accepted by feedback(). They only work inside push(). A formula using them in feedback() is rejected as invalid.
response()
Number of feedback having at least one answer that matches the conditions.
Unlike feedback(), the conditions of response() are evaluated at answer level, which is what makes the strict operator == meaningful: several conditions written with == must be satisfied by the same answer.
Accepts: attribute, feedback, import, nps, question, response, segment, survey, team, topic.
Formula | Result |
response("question.id", "=", "11111") | Feedback in which question 11111 was answered. |
response("response.value", "=", [4, 5], | Feedback with at least one rating answered 4 or 5. |
response("response.value", "==", "5", | Feedback where the row “Breakfast” itself was rated 5 — both conditions on the same answer. |
response("feedback.type", "=", "incomplete", | Feedback left incomplete in which a text question was answered. |
push()
Number of feedback invitations sent through Feedier Surveys (emails and SMS).
Accepts: push and survey only. Only the = operator is supported.
Formula | Result |
push() | All invitations sent for the sources in scope. |
push("push.canal", "=", "sms") | Invitations sent by SMS. |
push("push.status", "=", "bounce") | Invitations that bounced. |
push("push.status", "=", ["opened", "clicked"]) | Invitations opened or clicked. |
push("push.includeFollowUps", "=", true, | All invitations including reminders / follow-ups. |
push("survey.id", "=", "1526") | Invitations sent for one source. |
push() reads the Feedier Survey sending data. It requires a Feedier Survey connection on your workspace and returns 0 when there is none, or when no Feedier Survey source is in scope. Invitations only exist for feedback collected with Feedier Surveys — never for CSV, API, SFTP or review imports.
nps()
Net Promoter Score = % of promoters − % of detractors, computed on NPS questions only.
Accepts: attribute, feedback, import, nps, question, response, segment, survey, team, topic.
Formula | Result |
nps() | NPS of the current scope. |
nps("survey.id", "=", "11") | NPS of one source. |
nps("team.id", "=", "9921") | NPS of one team. |
nps("segment.id", "=", "111") | NPS of one segment. |
nps("import.type", "=", "csv") | NPS of the feedback imported by CSV. |
nps("question.id", "=", "11") | NPS of one specific NPS question. The question must be of type |
nps("attribute.name", "=", "country:France") | NPS of the feedback whose |
satisfaction()
Average satisfaction ratio, from 0 to 100.
The satisfaction ratio is computed by Feedier on every feedback, from all the scored items it contains. Text questions excluded from the ratio at import time are not counted.
Accepts: attribute, feedback, import, nps, question, response, segment, survey, team, topic.
Formula | Result |
satisfaction() | Satisfaction ratio of the current scope. |
satisfaction("survey.id", "=", "11") | Satisfaction ratio of one source. |
satisfaction("team.id", "=", "9921") | Satisfaction ratio of one team. |
satisfaction("topic.name", "=", "Packaging") | Satisfaction ratio of the feedback tagged “Packaging”. |
satisfaction("nps.profile", "=", "detractor") | Satisfaction ratio of detractors. |
sentiment()
Average sentiment score of the analysed text, from 0 to 100.
Accepts: attribute, feedback, import, nps, question, response, segment, survey, team, topic.
sentiment() is the function where the difference between the standard and the strict operators matters most, because a feedback can contain several verbatims tagged with different topics.
Formula | Result |
sentiment("topic.name", "=", "Packaging") | Sentiment of the whole feedback that have the topic “Packaging” attached. |
sentiment("topic.name", "==", "Packaging") | Sentiment of the verbatims only that carry the topic “Packaging”. |
sentiment("topic.name", "!=", "Packaging") | Sentiment of the feedback that do not have the topic attached. |
sentiment("topic.name", "!==", "Packaging") | Sentiment of the feedback, excluding the verbatims tagged “Packaging”. |
sentiment("question.name", "==", "What did you find most challenging?") | Sentiment of that specific question only, across all feedback. |
ces()
Average Customer Effort Score, computed on CES questions only.
Accepts: attribute, feedback, import, nps, question, response, segment, survey, team, topic.
Formula | Result |
ces() | CES of the current scope. |
ces("topic.name", "=", "Delivery") | CES of the feedback tagged “Delivery”. |
ces("question.id", "=", "1526") | CES of one specific CES question. |
ratings()
Average value of rating questions (single rating questions only).
Accepts: attribute, feedback, import, nps, question, response, segment, survey, team, topic.
Formula | Result |
ratings() | Average rating of the current scope. |
ratings("question.id", "=", "1526") | Average of one rating question. |
ratings("attribute.name", "=", "gender:female") | Average rating of the feedback whose |
ratingsTable()
Average value of rating table questions (a question with several rows to rate).
Accepts: attribute, feedback, import, nps, question, response, segment, survey, team, topic.
Formula | Result |
ratingsTable() | Average of all rating tables in scope. |
ratingsTable("question.option_name", "=", "Breakfast") | Average of the “Breakfast” row. |
ratingsTable("question.type", "=", "nps") | Average of the rating tables, restricted to the feedback that also contain an NPS question. |
time()
Average time taken to complete a survey, returned in minutes.
Accepts: feedback, import, nps, question, segment, survey only.
Formula | Result |
time() | Average completion time of the current scope, in minutes. |
time("survey.id", "=", "1324") | Average completion time of source 1324. |
time() * 60 | The same value expressed in seconds. |
resolutionTime()
Average time between the creation of a feedback and the moment it is marked as resolved, returned in seconds.
Accepts: feedback, import, nps, question, segment, survey only.
Formula | Result |
resolutionTime() | Average resolution time, in seconds. |
resolutionTime() / 3600 | Average resolution time in hours. |
resolutionTime() / 86400 | Average resolution time in days. |
resolutionTime("survey.id", "=", "1324") | Average resolution time for one source. |
Only feedback that has been assigned to an owner is taken into account. Unassigned feedback never enters the average. Remember to divide the result and to set the matching suffix (h, d) on the KPI.
financial()
Estimated revenue impact of the feedback in scope.
The value is computed as (opportunities - risks) x customer average value. A feedback is classified as an opportunity or a risk from the sentiment of its verbatims.
Accepts: financial only.
Formula | Result |
financial() | Net impact: opportunities minus risks. |
financial("financial.type", "=", "risk") | Revenue at risk. The value is negative, since only the risk side is counted. |
financial("financial.type", "=", "opportunity") | Revenue opportunity. |
This function uses the Customer average value defined in Settings → Teams → Advanced. If that value is not set, financial() returns 0.
attribute_avg(), attribute_sum(), attribute_min(), attribute_max(), attribute_qty()
Aggregate the values of a numeric attribute over the feedback in scope.
These five functions all work the same way and only differ by the operation: average, sum, minimum, maximum, and number of values.
Accepts: aggregate_attribute (required), attribute, feedback, import, nps, response, segment, survey, team, topic. The question parameter is not available here.
Formula | Result |
attribute_sum("aggregate_attribute.name", "=", "order_amount") | Total of the |
attribute_avg("aggregate_attribute.name", "=", "order_amount") | Average order amount. |
attribute_avg("aggregate_attribute.name", "=", "order_amount", | Average order amount of detractors. |
attribute_max("aggregate_attribute.id", "=", "1232") | Highest value of attribute 1232. |
attribute_qty("aggregate_attribute.name", "=", "order_amount") | Number of feedback carrying a value for that attribute. |
aggregate_attribute designates which attribute is aggregated, while attribute is a plain filter. In attribute_avg("aggregate_attribute.name", "=", "order_amount", "attribute.name", "=", "city:Berlin"), Feedier averages order_amount over the feedback coming from Berlin. The attribute must hold numeric values.
url()
Call an external JSON endpoint and use one of its values in your KPI.
Accepts: link only, with the link.link property.
url("link.link", "=", "https://example.com/api/metric~data.value")Element | Description |
The URL | Must start with |
~ path | Everything after the |
| Replaced by the filters currently applied to the page, so your endpoint can return a value consistent with the dashboard. |
| Replaced by the identity of the user viewing the KPI. |
| Replaced by the private API key of the workspace. Useful to call the Feedier public API itself. |
⚠️ The request is sent with your workspace private API key attached, so only point url() at endpoints you control or trust. A slow endpoint makes the KPI time out, and the whole formula fails.
2. Parameters and properties
A parameter is the field you filter on. The property comes after the dot and tells Feedier which form the value takes — an id, a name, a type…
Parameter.property | Accepted values | Notes |
| An attribute id or name, optionally followed by |
|
| An attribute id or name. | Required by the |
| A feedback id. | — |
|
| Only applies to Feedier Survey feedback: a feedback imported from another source is neither completed nor incomplete and is excluded when you use this parameter. |
|
| Only for |
| The source the feedback comes from: | The value must exist in your workspace. There is no |
| An | Only for |
|
| Promoters score 9–10, passives 7–8, detractors 0–6. The value is |
|
| Only for |
|
| Only for |
|
| Only for |
| A question id, or its exact label. | The label must match exactly, accents and punctuation included. |
|
| Older sources may still carry the legacy types |
| The id or the label of a choice, or of a rating table row. | — |
| One value, or an array of values: | The answer itself. Combine it with a |
| A segment id. | — |
| A source id. | — |
| A team id. | — |
| The exact name of a topic. | Topics come from your text analysis taxonomy. |
Values are checked against your own workspace when you save. An unknown team id, topic name or question label makes the whole formula invalid, even if the rest is correct. The quickest way to get an id is to open the corresponding page in Feedier and read it in the URL.
3. Operators
The operator sits between the parameter and the value, and decides at which level the condition is evaluated.
Operator | Scope | Description |
| Whole feedback 📦 | Keeps the feedback for which the condition is true at feedback level. |
| Whole feedback 🚫 | Excludes the feedback for which the condition is true at feedback level. |
| Feedback item 🎯 | Strict. The condition must be satisfied by one single item — an answer, a verbatim, a topic occurrence. Several strict conditions must be met by the same item. |
| Feedback item 🚫 | Strict exclusion. Removes the matching items from the calculation, but keeps the rest of the feedback. |
Two rules to remember
The strict operators
==and!==are only allowed on thequestion,responseandtopicparameters. Used anywhere else, the formula is rejected.===,=!and==!do not exist. The four operators above are the only valid ones.
The clearest illustration is sentiment() on a topic:
Formula | What is measured |
sentiment("topic.name", "=", "Packaging") | The whole feedback that mention packaging — including what they say about everything else. |
sentiment("topic.name", "==", "Packaging") | Only the verbatims about packaging. |
sentiment("topic.name", "!=", "Packaging") | The feedback that never mention packaging. |
sentiment("topic.name", "!==", "Packaging") | All the feedback, minus what they say about packaging. |
4. The most used custom KPIs
Response rate
feedback("import.type", "=", "feedier_survey") / push() * 100Suffix %. Both sides of the division must come from the same source: invitations are only generated when you send a Feedier Survey campaign, so feedback coming from any other import (CSV, API, SFTP, Salesforce, reviews…) cannot contribute to a response rate and is excluded from the numerator.
Example — a source mixing CSV and Feedier Survey:
Feedback collected: 238, of which 57 from Feedier Survey and 181 from CSV
Invitations sent: 654
Response rate = 57 / 654 = 8.7 % — the 181 CSV feedback are excluded
If your scope contains no Feedier Survey feedback, this KPI returns 0. That is expected, not a bug.
Detractor rate
feedback("nps.profile", "=", "detractor")
/ (feedback("nps.profile", "=", "promoter")
+ feedback("nps.profile", "=", "passive")
+ feedback("nps.profile", "=", "detractor")) * 100Suffix %. Turn on Lower is better for this one.
Promoter rate
feedback("nps.profile", "=", "promoter")
/ (feedback("nps.profile", "=", "promoter")
+ feedback("nps.profile", "=", "passive")
+ feedback("nps.profile", "=", "detractor")) * 100Satisfaction rate on rating questions (CSAT)
feedback("response.value", "=", [4, 5], "question.type", "=", "ratings")
/ feedback("question.type", "=", "ratings") * 100Suffix %. Share of the feedback whose rating is 4 or 5, out of those that contain a rating question.
Response rate on a given score
response("response.value", "=", [3, 4, 5], "question.id", "=", "123274")
/ feedback("question.id", "=", "123274") * 100Share of feedback coming from Google reviews
feedback("import.type", "=", "google_review") / feedback() * 100The denominator is feedback() with no condition, which covers the whole scope. There is no "all" import type.
Share of respondents who picked a given choice
feedback("question.option_name", "=", "Option A")
/ feedback("question.type", "=", "choices") * 100Average resolution time in hours
resolutionTime() / 3600
Suffix h, format 0.0, Lower is better enabled.
Revenue at risk
financial("financial.type", "=", "risk")Suffix: your currency. The value is negative, which is what you want on a “revenue at risk” tile.
Average basket of detractors
attribute_avg("aggregate_attribute.name", "=", "order_amount",
"nps.profile", "=", "detractor")5. Troubleshooting
Symptom | Cause | Fix |
“This formula is invalid.” | The formula is executed when you save. Any error — syntax, unknown value, unsupported parameter — blocks the save. | Check the three points below in order: syntax, values, supported parameters. |
Syntax error | A missing quote, a missing comma, unbalanced parentheses, or a number of arguments that is not a multiple of three. | Every condition is exactly three arguments: |
Unknown value | A team id, a topic name, a question label or an import type that does not exist in your workspace. | Copy the exact label, accents included, or take the id from the URL of the corresponding page in Feedier. |
Unsupported parameter | The parameter is not accepted by that function — for example | Check the Accepts line of the function in section 1. |
Unsupported operator |
| Fall back to |
The KPI shows 0 | A division by zero returns 0. The scope may also simply contain no matching feedback. | Run the numerator and the denominator as two separate KPIs to find which one is empty. |
| No Feedier Survey connection on the workspace, or no Feedier Survey source in the current scope. | Invitations only exist for Feedier Surveys. Check the source of the feedback you are measuring. |
| The endpoint is too slow and the call times out, or the path after the | Test the URL outside of Feedier, and check the exact key path of the value. |
The value looks off by a factor | A unit was not converted: | Divide the result and set the matching suffix on the KPI. |
