To create a custom text-based progress bar in Notion (Formula 2.0), you need to divide your completed metrics by your total metrics and convert the output into visual block characters. Assuming you have Number properties named Completed and Total, paste this exact formula:
JavaScript
let(
progress, if(prop("Total") == 0, 0, min(prop("Completed") / prop("Total"), 1)),
repeat("█", round(progress * 10)) + repeat("░", 10 - round(progress * 10)) + " " + format(round(progress * 100)) + "%"
)
The Syntax Breakdown
let(): A Formula 2.0 function that allows you to assign a local variable (in this case,progress) to simplify the rest of the code and prevent redundant calculations.if(prop("Total") == 0, 0, ...): A failsafe that checks if the Total column is empty or zero. If it is, it forces the progress to0, preventing a fatal division-by-zero (NaN) error.min( ... , 1): Another failsafe that caps the maximum progress at1(100%). This ensures that if your “Completed” number exceeds your “Total”, the visual bar will not break by attempting to render negative empty blocks.repeat(string, number): Duplicates a text character a specified number of times. We use it to generate both the filled (█) and empty (░) blocks.round(progress * 10): Multiplies the decimal percentage (e.g.,0.75) by10(7.5) and rounds it (8). This dictates exactly how many of the 10 total visual blocks should be “filled”.format(round(progress * 100)): Converts the raw decimal into a clean whole number (e.g.,75) and transforms it into text so it can be combined with the%symbol.
Real-World Example: Tracking Q3 OKR Key Results
You manage quarterly OKRs (Objectives and Key Results) for a marketing department. You need a scannable dashboard that shows exactly how close the team is to hitting specific numeric targets, rather than just relying on raw numbers.
Here is your raw data structure in Notion:
| Key Result (Title) | Completed (Number) | Total (Number) |
| Publish New Blog Posts | 12 | 20 |
| Acquire New Backlinks | 45 | 50 |
| Host Webinars | 1 | 4 |
| Launch Ad Campaigns | 0 | 0 |
The Application:
Add a new property, select Formula, and paste the code from the Quick Answer section.
The Output:
The formula will automatically process the math, apply the failsafes, and output a clean, 10-block visual string.
| Key Result | Completed | Total | Visual Progress (Formula Output) |
| Publish New Blog Posts | 12 | 20 | ██████░░░░ 60% |
| Acquire New Backlinks | 45 | 50 | █████████░ 90% |
| Host Webinars | 1 | 4 | ███░░░░░░░ 25% |
| Launch Ad Campaigns | 0 | 0 | ░░░░░░░░░░ 0% |
Common Errors & How to Fix Them
Type mismatch: prop("Completed") is not a NumberError: You are trying to divide a Text, Select, or Rollup property that Notion does not recognize as an integer. Fix: Ensure both reference properties are strictly set to the Number type. If you are using a Rollup, click the Rollup property settings and ensure the Calculate option is set to a numeric output like Sum or Count all.- Formula returns
NaNor Infinity: You skipped using theif()statement failsafe, and a row has an emptyTotalproperty, resulting in a division by zero. Fix: Wrap your initial math equation in anif()statement to output0whenTotalis0, exactly as shown in the provided snippet. Function repeat expects number between 0 and 100Error: YourCompletedvalue is much larger than yourTotalvalue, resulting in a negative number for the empty blocks calculation. Fix: Wrap your division calculation in themin()function to cap the maximum output at1, e.g.,min(prop("Completed") / prop("Total"), 1).