How a formula reads
A formula is one expression, worked out once for every row of its table. It's built from four kinds of name, and how a name is written decides which kind it is: a field in brackets, a function before a parenthesis, a relation or a LET name on its own. Nothing else changes what a name means, so a formula reads the same wherever you meet it.
| Written as | It is | Example |
|---|---|---|
[Name] | A field of this row | [Total float] |
Relation[Name] | A field of the rows a relation reaches | Predecessors[Finish date] |
NAME(…) | A function | MAX(…), WORKDAYS(…) |
name | A relation, or a name you gave with LET | Upstream, u |
Case doesn't matter for any of them: sum(…), Sum(…) and SUM(…) are the same function, and [total float] is [Total float]. The editor rewrites what you typed in the project's spelling when you save.
Fields
A field in brackets is always the value on the row being worked out, never on another row, even inside a function that is busy with other rows. To read another row's value, name the rows first: see Reading other rows.
A field reads what its cell shows. [Status] is "Not started", "In progress" or "Complete"; [Start date] is the date the grid shows (the actual start once the task has started, otherwise the early start); a task with no duration has a blank [Duration], not 0d.
The task fields:
| Field | Type | What it holds |
|---|---|---|
[Task code], [Name], [WBS] | Text | The task's ID, its name, its WBS path (levels joined by " / ") |
[Type] | One of Task, Milestone, LOE, Resource, WBS | |
[Status] | One of Not started, In progress, Complete | |
[Duration], [Remaining duration] | Duration | Original and remaining working time |
[% complete] | Number, 0 to 100 | |
[Physical % complete] | Number, 0 to 100 | As entered; blank when none was |
[% complete type], [Duration type] | Text | The task's own, else the project's |
[Expected finish] | Date | As entered: the remaining duration runs to it |
[Planned start (import)], [Planned finish (import)] | Date | A started activity's planned dates as imported from P6; blank otherwise |
[Start date], [Finish date] | Date | As the grid shows them |
[Early start], [Early finish], [Late start], [Late finish] | Date | From CPM's forward and backward passes |
[Actual start], [Actual finish] | Date | Blank until the task starts or finishes |
[Total float], [Free float] | Duration | |
[Critical], [Longest path] | TRUE or FALSE | |
[Constraint], [Constraint date], [Secondary constraint], [Secondary constraint date] | Constraint, Date | |
[Calendar] | Text | The name of the calendar CPM dates the task on. A field read off it is the calendar's: [Calendar][Short name] is its id, [Calendar][Hours per day] its day, from this task or any other (Predecessors[Calendar][Short name], other rows) |
[Driver] | A task | The predecessor that set this task's early start |
[Task] | A task | This task itself |
[Justification] | Text | Why the task is in the schedule, as its builder wrote it |
Your project's own fields work the same way. Task codes and other attributes are text, [Phase]; a task field you defined as a number or a date is one. Formula columns are fields too: name one in brackets, [Near critical], and the column that reads it updates with it.
A name with a ] in it writes it twice: [Cost [USD]]]. When the editor can't find a field, it says so and suggests the nearest: “[Finsh] isn't a field. Did you mean [Finish date]?”
Other tables have their own fields (a WBS node's [Path] and [Level], a resource's [Hourly rate], a relationship's [Lag]): see tables.
Operators
| Operator | Does | Example |
|---|---|---|
+ - * / | Arithmetic. Works on durations too: 5d * 2 is 10d | [Duration] * 1.2 |
^ | Power | 2 ^ 3 |
& | Joins text; anything else is shown as text first | [Task code] & " " & [Name] |
= <> < <= > >= | Compare two values: TRUE or FALSE | [Status] <> "Complete" |
AND, OR, NOT | Combine TRUE and FALSE. Also work as functions: AND(a, b, c) | [Critical] AND [Duration] > 20d |
AND binds tighter than OR, as in most languages; use parentheses when you mix them. A comparison can't be chained: 0 < [% complete] < 100 is refused, so write [% complete] > 0 AND [% complete] < 100.
Durations
A duration is working time, written as a number with a unit: 5d, 2w, 8h, 0.5d.
d and w count in the calendar of the task the value belongs to. [Total float] < 5d is five of that task's working days: on a 10-hour calendar that is 50 hours, on a standard calendar 40. Where a duration belongs to no task (5d * 2 on its own, a measure on the project), a day is the project's: the hours per day of the calendar most tasks use.
A plain number where a duration is expected is working days of the row's calendar, so [Total float] < 5 is the same as [Total float] < 5d. That is the only time Owl turns one kind of value into another for you.
| Expression | Gives |
|---|---|
| duration ± duration | A duration |
| duration × number, duration ÷ number | A duration |
| duration ÷ duration | A number: [Remaining duration] / [Duration] |
INDAYS(duration), INHOURS(duration) | A plain number of working days or hours |
A duration shows in the grid in working days. Wrap it in INDAYS or INHOURS for a number you can format or divide by money.
Dates
There are no date literals: write a date with DATE, DATE(2026, 12, 1). A typed date means different things in different countries, so Owl never guesses.
| You want | Write |
|---|---|
| Working time between two dates | WORKDAYS([Start date], [Finish date]) |
| Calendar days between two dates | CALDAYS([Start date], [Finish date]) |
| A date some working time later | [Finish date] + 5d, or ADDWORK([Finish date], 5d) |
| Today, or the data date | TODAY(), DATADATE() |
| Part of a date | YEAR(…), MONTH(…), DAY(…), WEEKDAY(…) |
| Its time of day | HOUR(…), MINUTE(…); a date at a time, DATE(2026, 12, 1, 8, 30) |
Subtracting one date from another is refused: “[Finish date] - [Start date] subtracts two dates. Use WORKDAYS(…) or CALDAYS(…).” The two answers differ by every weekend and holiday, so the formula says which it means.
Adding a duration to a date steps through the task's own calendar, as CPM does: a Friday finish plus 1d on a five-day calendar is Monday.
Dates compare by the day. A task starting at 08:00 and one starting at 17:00 on the same day have equal [Start date]s. To compare to the hour, compare the hour itself, HOUR([Start date]) >= 8, or the working time between two dates: WORKDAYS(a, b) > 0h. Times are read in UTC wall clock, the clock Owl keeps schedules in.
Text
Text goes in double quotes, "Civil"; a quote inside is written twice, "6"" pipe". Comparing text ignores capitals, as Excel does: "civil" = "Civil" is TRUE.
Fields with a fixed set of values ([Status], [Type], [Constraint], a relationship's [Type]) are compared with text too, and the editor checks the text against the values: “Completed isn't a status. Did you mean Complete?”
Text never turns into a number by itself (read one out of it with VALUE), and a number never turns into TRUE or FALSE.
Looking for text inside text (CONTAINS, STARTSWITH, TEXTBEFORE, TEXTAFTER) ignores accents and other marks as well as capitals: "becancour" finds Bécancour, and Arabic is matched without its harakat. = ignores capitals only.
Blanks and errors
A row's value can be blank, or an error, instead of a value. They're different things, and Owl never turns one into 0, "" or FALSE behind your back.
| Blank | Error | |
|---|---|---|
| Means | Nothing to work out: no baseline, no predecessors, a field not set | Working it out failed: a division by zero, ONLY on a set of three |
| In the grid | An empty cell. Hover for the reason | !. Hover for the reason and the task it came from |
| In a filter | Matches no condition, not even "is not". Use is empty | Matches no condition |
| In colors | Falls through to otherwise | Falls through to otherwise |
| Sorting | Last | Last |
| In a guardrail | The row isn't checked | The row is listed under Couldn't check |
How they travel through a formula:
- Arithmetic and functions pass them on.
[Total float] / 2is blank where float is blank, and an error anywhere in a calculation makes the result that error. &reads a blank as empty text:[Phase] & "!"is"!"where Phase isn't set.- Comparisons pass them on too.
[Actual finish] > DATE(2026, 6, 1)is blank, not FALSE, on an unfinished task. So[Actual finish] = BLANK()is blank on every row, never TRUE: to test for a blank, use ISBLANK. IFonly works out the branch it takes.IF([Duration] > 0d, [Remaining duration] / [Duration], 0)never divides by zero.ANDandORstop when they know.FALSE AND xis FALSE andTRUE OR xis TRUE, even whenxis blank or an error.- Aggregates skip blanks, but not errors.
MAX(Predecessors[Finish date])ignores a predecessor with no finish, and one predecessor whose value failed makes the whole result that error. A bad value is never quietly dropped.
On an empty list, or when every value is blank:
| Aggregate | Gives |
|---|---|
SUM, COUNT | 0 |
ANY | FALSE |
EVERY | TRUE |
TEXTJOIN | "" |
MIN, MAX, AVERAGE, MEDIAN, PERCENTILE | Blank ("no values") |
To choose what a blank or an error becomes:
| You want | Use |
|---|---|
| A fallback where the value is blank | COALESCE: COALESCE([Phase], "Unassigned") |
| A fallback where it failed | IFERROR |
| To test for either | ISBLANK, ISERROR |
| A blank on purpose | BLANK(), or IF with no else: IF([Critical], [Finish date]) |
Naming parts with LET
A long formula reads better with its parts named. LET takes names and values, then the result:
Formula=LET(open, FILTER(Upstream, Upstream[Status] <> "Complete"), COUNT(FILTER(open, open[Critical])))
How many unfinished tasks upstream of this one are critical. A name can't reuse a function's, a relation's or another LET name's: “Upstream is a relation name (Upstream); pick another name for this LET, e.g. my_upstream”.
Formulas can run over several lines; spaces and line breaks don't matter.
Common questions
Can I refer to the row above?
No. A formula never depends on the order of the rows, so there is no "previous row". To read a related row, name it: Predecessors, WBSNode, Task("A1000"). See Reading other rows.
Why does my formula give 5d when I wanted 5?
A result has the type of what it was worked out from, and a duration shows in working days. INDAYS(…) gives the plain number.
Why is -2^2 -4?
The minus applies after the power, as in mathematics. Excel gives 4. Write (-2)^2 for 4.
Are there comments?
No. Give the column a name that says what it is, and name the parts with LET.
