Fennick Docs Open Fennick

Queries

A query block lists the entities that match your query, live, right on the page.

A query is written in Fennick's formula language, the same one formulas use. For example, #task.Where(Done = false).Sort(Due) lists your open tasks, the soonest first. Fennick reads the query as a filter and a sort, the same filters and sorts a tag page's view uses, and shows the matching entities under the block. When the entities change, the results change too.

Make a query block

  1. On an empty block, type ??. The formula builder opens under it (see The builder).
  2. Pick a tag, such as #task, and press Tab. Type . for its fields and steps, then write the rest: #task.Where(Done = false).Sort(Due). The first results show in the builder as you type.
  3. Press Enter. The block becomes a query block, with its results under it.
  • Or press / and pick Query, then type the query on the block.
  • A query block has a ? mark in place of its bullet.
  • A # in a query block names a tag. It never tags the block.
  • Enter at the end of a query block starts a plain block after it.

The builder

?? opens the builder anywhere in a block: at its start, after a space, or inside a word. It shows:

  • The formula, at the top. A tag or an entity you pick shows as a chip in its tag's color, with its Type's icon.
  • The answer, under it, after =: for a query, the first few names it lists and how many more.
  • What can come next, on the left: Related(ThisPage()), your tags, most used first, your saved queries, this page's fields, entities once you type two letters, and functions. After a tag and a ., the tag's fields and the steps Where, Sort, Limit, Without, Sum, Average, Count and List. Inside a Where, after a field, the rules that fit its Type, each with what it does, then the field's own values, such as Active or On Hold. Picking a value closes the Where.
  • The one picked, explained, on the right: what it is, with a preview of a tag's first entities, or a function's help and an example.

The keys:

  • ↑ and ↓ move through the list, and Tab puts the one picked in.
  • Enter puts the formula there. A query in an empty block makes it a query block. Anywhere else, it puts in the names it lists, kept up to date, or a value, such as a count (see A formula in a block).
  • Esc or Cancel leaves ?? and what you typed as letters in the block.
  • With nothing typed yet, a space, a punctuation mark or a third ? leaves the letters too, so ?? in your writing stays as you typed it.
  • Add name, at the top, names the query block as it is made.
  • f shows the formula as words in place of chips.

On a phone the builder opens as a sheet from the bottom, and the keyboard bar has a ?? key.

A query's view

A query holds one view. Its results show as a list at first. The icon beside the count opens its panel:

  • Shows: its tag, and Or a tag to add another.
  • Edit Query: the query itself, to change as you read the results.
  • Layout: List, Outline, Prose, Numbered list, Checklist, Table, Board, Calendar, Gallery, Chart or Canvas. See Views.
  • The layout's own settings, then Filter, Sort, Group and Search.
  • Total: the sum or the average of a Number field (see Sum and average).
  • + New when folded: whether New stays beside the count while the query is folded.
  • Tag and filter bar: whether the query's tag and filter chips show.
  • Duplicate the query: for another look at the same results, duplicate it and change the copy's view.
  • Open full page.

Name a query, and change it with chips

  • To add a filter or a sort, use the panel. While you set a filter, the query's tag and each of its filters show under it as chips: click a chip to change its rule or value, or to remove it. The chips go when you leave the page, and the panel's Filter row says how many filters it has. Tag and filter bar keeps them showing. A query Fennick can't read, or one naming a tag that isn't made yet, keeps them too, so you can fix it. A chip writes the query's words for you.
  • Once you use a chip, the block waits for a name: until you type one, the query shows dimmed in its place. Click it and type the name, like a page title. Until then, the query goes by its words wherever it shows: as its page's title, dimmed, on its tab and in Recents.
  • To change the query itself, right-click its ? (or open its menu on a phone) and pick Edit query. The query is back on the block: edit it and press Enter. Esc leaves it as it was.
  • A query Fennick can't read is never kept: the query before stays and keeps running, and the block under it says what it can't read.
  • Chips, names and edits sync to your other devices, and ⌘Z undoes them.

Queries from before

Queries used to be written in words, such as #task where Done is not checked sort Due. Those words aren't read any more. A query block that holds them stays as it is and says Can’t read: These query words are from before the new query language. Make it again in the formula language: that one is #task.Where(Done = false).Sort(Due). A query of only a tag, such as #task, or Everything, reads as before.

The shape of a query

A query starts with what to list: most often a tag. Then come its steps, each after a ., and all of them optional:

#tag.Without(#tag).Where(rule and rule).Sort(Column).ThenBy(Column).Limit(n)

PartWhat it doesExample
#tagWhich entities to list: every block tagged with that tag. Name more than one in brackets, with or (see Several tags).#task, (#task or #bug)
EverythingIn place of a tag: every page and every entity.Everything.Where(Status = Doing)
Under([[page]])In place of a tag: the pages and entities under that page, at any depth, and those shown on it.Under([[Projects]]).Sort(Edited, desc)
LinksTo([[page]])In place of a tag: every page and entity that links to that page, or that has a field pointing at it.LinksTo([[Kit]])
[[query]]Starts from another named query: what it lists, through its rules, and then this query's own steps.[[Open tasks]].Where(Due.In(ThisWeek()))
Related(ThisPage())In place of a tag: the pages and entities most related to the page the query is on, best first, as Related finds them. Related([[page]]) does it for any page. See Related.Related(ThisPage()).Limit(3)
Search("…")In place of a tag: every block that holds each of the words in quotes. In Start from it is Search…. Query blocks are never in its results.Search("garden hose")
#dayYour day pages, each by its date. In Start from it is Day pages. Sorted by Name, they go by date. #week, #month, #quarter and #year work the same way; Other date pages in Start from has them, with #weekday and the eternal ones.#day.Sort(Name, desc)
.Without(#tag)Leaves out the entities of that tag (see Leave a tag out).#task.Without(#routine)
.Where(…)Only the entities that pass its rule are listed. .Filter(…) is the same.#task.Where(Done = false)
andInside Where: one more rule. An entity must pass all of them. && is the same.#task.Where(Done = false and Due.IsNotBlank())
orInside Where: one rule of a field or another (see One or another). || is the same.#book.Where(Status.In(Reading, "To read"))
Link.FieldIn a rule: a field of what a Select from tag points at (see Through a link). › works too.#task.Where(Project.Status = Doing)
.Sort(…)Puts the results in the order of a column (see Sort).#task.Sort(Due)
.Limit(n)Shows only the first so many results.#task.Sort(Due).Limit(5)
.Sum(Field), .Average(Field)At the end: adds up a Number field over every result, and says the answer beside the count (see Sum and average).#book.Sum(Pages)
  • Capitals don't matter in a query's own words: .Where is .where, #Task is #task, and due is Due.
  • A tag with a space in its name works as it is: #reading list.Sort(Name).
  • A column with a space in its name goes in square brackets: [Edited by], [Blocks under it].
  • Spaces between the parts don't matter: Pages>=432 is Pages >= 432.
  • With Everything, Under, LinksTo or Search, the columns are every field your tags have, plus the columns every entity has, such as Name and Mentions.

The examples below use #task, with its fields Due (a Date) and Done (a Checkbox), and a #book tag with these fields: Status (a Select: To read, Reading, Read), Pages (a Number), Author (a Text), Started (a Date), Owned (a Checkbox) and Shelf (a Select from tag, pointing at #shelf entities).

Columns

A rule or a sort names a column. These are the columns a query knows. They are the same columns as on the tag's page.

ColumnWhat it holdsExample
NameThe entity's own words.#book.Where(Name.Contains("pride"))
Each of the tag's fieldsThat field's value, by the field's name.#book.Where(Status = Reading)
MentionsEvery page it is on or named in: each page it shows on, and each page with a block that links to it, by those pages' names. A day reads like Monday 12 October 2026.#book.Where(Mentions.Contains("October"))
EditedThe day it was last changed.#book.Where(Edited = Today())
CreatedThe day it was made.#book.Sort(Created, desc)
Edited byWho changed it last.#book.Where([Edited by] = Me())
Created byWho made it.#book.Where([Created by] = "Maya")
TagsThe tags it wears. Sorted by, it goes by the first tag's name, then the next, in the order they show; an entity with no tag comes last.#book.Where(Tags.Contains(#classic)), #book.Sort(Tags)

A name that is not one of these columns is not understood: #book.Where(Color = "blue") shows Can’t read: #book has no field "Color".

Rules for each field Type

A rule names a column, then what its value must be. Which rules fit depends on the column's field Type.

Text, Name, and Select from tag

RulePasses whenExample
.Contains("…")The value has these letters anywhere in it. Capitals do not matter.#book.Where(Author.Contains("austen"))
not … .Contains("…")The value does not have these letters.#book.Where(not Name.Contains("emma"))
=The value is exactly this, capitals too.#book.Where(Author = "George Eliot")
!=, ≠ or <>The value is not exactly this.#book.Where(Author != "George Eliot")
.IsBlank()The field has no value.#book.Where(Author.IsBlank())
.IsNotBlank()The field has a value.#book.Where(Shelf.IsNotBlank())

Blocks under it reads the same way: #task.Where([Blocks under it].IsNotBlank()) lists the tasks with blocks under them, and .IsBlank() those with none.

A Select from tag field is matched by the name of the entity it points at: #book.Where(Shelf.Contains("study")). [[Name]] names the entity itself: #task.Where(Work = [[Kitchen]]). Type [[ in the query's words and a list offers the entities by that name. For Mentions, .Contains matches when any of its pages does. A query written with its old name, Written on, still works.

Select

RulePasses whenExample
=The choice is this one. Capitals do not matter: reading finds Reading.#book.Where(Status = Reading)
!=, ≠ or <>The choice is another one, or none.#book.Where(Status != Read)
.In(…)The choice is any one of these.#book.Where(Status.In(Reading, "To read"))
.Contains(…)The choice's name has these letters.#book.Where(Status.Contains(read)) finds all three.
not … .Contains(…)The choice's name does not have these letters.#book.Where(not Status.Contains(to))
.IsBlank(), .IsNotBlank()No choice is made, or one is.#book.Where(Status.IsBlank())

A choice with a space in its name goes in quotes: #book.Where(Status = "To read"). A one-word choice needs none.

Number

RulePasses whenExample
=The number is this.#book.Where(Pages = 432)
!=, ≠ or <>The number is not this.#book.Where(Pages != 432)
>The number is more than this.#book.Where(Pages > 450)
<The number is less than this.#book.Where(Pages < 500)
>= or ≥The number is this or more (at least).#book.Where(Pages >= 432)
<= or ≤The number is this or less (at most).#book.Where(Pages <= 432)
.Between(…, …)The number is either of them, or between. It is the same as >= one and <= the other, which is how a chip writes it.#book.Where(Pages.Between(100, 300))
.IsBlank(), .IsNotBlank()The field has no number, or has one.#book.Where(Pages.IsBlank())

Date, Edited and Created

RulePasses whenExample
=The day is this day.#task.Where(Due = Tomorrow())
.In(…) this, last or next week, month or yearThe day is in that whole week, month or year (see This, last and next).#task.Where(Due.In(ThisWeek()))
<The day is earlier than this day.#book.Where(Started < Date("next week"))
>The day is later than this day.#book.Where(Started > Today())
<=The day is this day or earlier.#task.Where(Due <= Today())
>=The day is this day or later.#task.Where(Due >= Date(2026, 11, 1))
.Between(…, …)The day is either of them, or between.#task.Where(Due.Between(Date(2026, 11, 1), Date(2026, 11, 5)))
.InLast(a number, a unit)The day is from that long ago up to today.#task.Where(Edited.InLast(3, days))
.InNext(a number, a unit)The day is from today up to that long from now.#task.Where(Due.InNext(2, weeks))
.IsBlank(), .IsNotBlank()The field has no day, or has one.#task.Where(Due.IsNotBlank())

The day can be written in any of these ways:

  • Today(), Tomorrow(), Yesterday()
  • Date(2026, 11, 1): the year, the month and the day
  • Date("…") with words, in any of these ways:
    • 2026-11-01, 11/1, 11/1/2026, Nov 1, 1 Nov, Nov 1 2026
    • a weekday: friday, next friday, this friday, last friday
    • next week, last week: a week from today, or a week ago
    • in 3 days, in 2 weeks, 3 days ago, 2 weeks ago

“Is not” a day is not understood: Due != Today() shows Can’t read.

This, last and next week, month and year

.In(ThisWeek()) means that whole week on the calendar. There are nine: ThisWeek(), LastWeek(), NextWeek(), and the same for Month and Year.

Query, on Thursday, October 29, 2026Lists
#task.Where(Due.In(ThisWeek()))Tasks due Monday, October 26 to Sunday, November 1.
#task.Where(Due.In(NextWeek()))Tasks due November 2 to 8.
#task.Where(Due.In(LastMonth()))Tasks due September 1 to 30.
#task.Where(Due.In(ThisMonth()))Tasks due October 1 to 31.
#book.Where(Started.In(LastYear()))Books started in 2025.
  • A week starts on the day you pick in Settings, Week starts on: Monday, or Sunday. From Sunday, ThisWeek() on October 29 is October 25 to 31.
  • These are whole periods. .InLast(1, months) is different: on October 29, it is September 29 up to today.
  • In a chip, pick is, then one of the nine: This week, Last month, Next year and the rest. Pick more than one for an “or”.
  • New in a query fills the first day: under Due.In(LastMonth()), the new entity is due September 1.

In the last, in the next

.InLast and .InNext take a whole number and one of five units: days, weeks, months, quarters or years. The unit can be singular or plural: .InLast(1, week) is fine.

QueryLists
#task.Where(Edited.InLast(3, days))Tasks changed from 3 days ago up to today.
#task.Where(Due.InNext(2, weeks))Tasks due from today up to 14 days from now.
#book.Where(Started.InLast(6, months))Books started from the same date 6 months ago up to today.
#event.Where(When.InNext(1, quarters))Events from today up to the same date 3 months on.
#trip.Where(When.InLast(2, years))Trips from the same date 2 years ago up to today.

How the days are counted:

  • Today always counts, in both. So .InLast(7, days) is today and the 7 days before it, and .InLast(1, day) is yesterday and today.
  • A week is 7 days. Months, quarters (3 months) and years (12 months) count by the calendar, to the same date: from October 26, .InNext(2, months) runs to December 26.
  • When that month has no such date, it is the month’s last day: from January 31, .InNext(1, month) runs to February 28 (February 29 in a leap year). From February 29, .InNext(1, year) runs to February 28.
  • The number is a whole number, 1 or more. 0, -2 or 2.5 shows Can’t read, and says why.
  • The most is 1,000 years (12,000 months, 365,000 days). More shows Can’t read: that is too far.

The filter on a tag page has the same rules: pick in the last or in the next, type how many, then pick the unit. It starts at 7 days.

A date that repeats

A date that repeats answers a rule on each of its turns, so #routine.Where(When = Today()) lists the routines whose day it is, however long ago they began. A date that doesn't repeat answers by its first day, as above.

  • =, .InLast, .InNext, .In(ThisWeek()) and the rest: a turn falls on that day or in those days.
  • >, >=: a turn comes then. A repeat with no last day always has one.
  • <, <=: it has begun by then.
  • .IsBlank(): never, as it holds a date.
  • Each rule is read on its own. For a stretch of days, use one rule such as .InNext(7, days), not >= with <=, which two different turns can answer.
  • A sort goes by the turn that is due. Agents' queries read the same way.

The builder offers Related(ThisPage()) to start from, and after its . its steps, Where(Similarity >= Close) first. Related(ThisPage()) lists the pages and entities most related to the page the query sits on, up to 30, best first: by links, by tags few others wear, and in the Mac app by meaning too (see Related). Related([[Garden plans]]) lists those of any page.

QueryLists
Related(ThisPage()).Limit(3)The three most related.
Related(ThisPage()).Where(Similarity >= Close)Only the close ones.
Related(ThisPage()).Where(#book)Only the related books.
  • Similarity is one of three words, from least to most: Related, Close and "Very close". Its rule goes in a Where of its own.
  • They come best first. .Sort(Similarity) or .Sort(Similarity, asc) puts the least related first.
  • .Where(#book) is short for .Where(Tags.Contains(#book)) here.
  • After the start, the steps are the same as for Everything.
  • Shown as a Table, it has a Similarity column: each row's word drawn as a bar, as in the Related panel, with no number.

Several tags

Put tags in brackets with or to list the entities of each: (#task or #bug).Where(Status = Doing).

  • Each entity is listed once, with the entities of the tags that build on each.
  • The columns are the first tag's. A filter or a sort on a field reads each entity's own field of that name: a bug's Status for a bug, a task's for a task. A tag with no such field reads as empty.
  • New in the query makes an entity with the first tag.
  • Under the query, Or a tag adds one, and the × beside a tag takes it off.

Leave a tag out

Use .Without and the tag after what to list, or the column Tags.

QueryLists
#task.Without(#routine)Tasks that aren't routines. The same as #task.Where(not Tags.Contains(#routine)).
#task.Without(#routine, #chore).Where(Done = false)Open tasks, with two tags left out.
#task.Where(Tags.Contains(#urgent))Tasks that also wear #urgent: both tags.
Everything.Where(Tags.IsBlank())Pages and entities with no tag at all.
Everything.Where(Tags.IsNotBlank())Only those with a tag.
  • A tag counts with the tags that build on it: when #chore builds on #routine, .Without(#routine) leaves out chores as well.
  • Under a query, + Filter offers Tags: pick the rule, then the tag.
  • When you rename a tag, the query says the new name.
  • New in a query puts on a tag named in Tags: under Tags.Contains(#urgent), the new task wears #urgent as well.

Follow a Select from tag field to a field of the entity it points at, one step, with a .:

QueryLists
#task.Where(Project.Status = Doing)Tasks whose project is Doing.
#task.Where(Project.Owner = Me() and Done = false)Your projects' open tasks.
#task.Where(Project.Name.Contains("garden"))Tasks of any project with garden in its name.
#task.Where(Project.Due.IsBlank())Tasks whose project has no Due, and tasks with no project.
  • The rule is the one for the field behind the link, so it reads as it does on that tag.
  • A field that holds several entities passes when any of them does.
  • A link that can point at any page has only what every entity has behind it: Name, Created, Edited and the like.
  • Its chip says it, with a × to take it off. To change it, use Edit query.
  • When you rename either field, the query says the new name.
  • You can't sort through a link yet.

Sum and average

A query block can add up a Number field over everything it lists, and say the answer beside the count. Each total shows as a chip with the query's filters, such as Sum of Pages, and its × takes it off.

  • To add one, click the icon beside the count, which opens the query's View settings, and pick Total, then Sum of or Average of a Number field. Pick it again to take it off. Total says how many totals the query has.
  • Or write it at the end of the query: #book.Sum(Pages), or #book.Pages.Sum(). The old words, such as #book sum Pages, no longer read.
QuerySays
#book.Sum(Pages)Sum of Pages: 4,320
#book.Where(Status = Read).Average(Pages)Average of Pages: 360
#book.Sum(Pages).Average(Pages)Both.
#task.Sort(Due).Limit(5).Sum(Hours)The sum for those first five only.
  • A total ends the query: only another total comes after it.
  • The total is kept with the query block, as its chips are, not in its words.
  • It reads every entity the query lists, not only the rows on the screen, and changes when they do.
  • An entity with no value is left out of an average. An average with no values says no value.
  • It shows the number in the field's own format.
  • While the query's Search box holds words, the answer isn't shown.
  • Agents get the same answer when they read the query.

For one number in your writing, with no rows, use a formula: #book.Pages.Sum() after ?? in a sentence (see A formula in a block).

One or another

or joins rules of one column: an entity passes when it passes any of them.

QueryLists
#book.Where(Status.In(Reading, "To read"))Books being read, and books to read.
#task.Where(Due.In(ThisWeek()) or Due.In(NextWeek()))Tasks due this week or next week.
#task.Where(Due = Today() or Due.IsBlank())Tasks due today, and tasks with no day.
#book.Where(Author.Contains("Le Guin") or Author.Contains("Tolkien"))Books by either.
#task.Where(Done = false and (Due = Today() or Due.IsBlank()))Open tasks due today or with no day.
  • With other rules beside it, put the or in brackets, as in the last example.
  • An or across two columns, such as Due = Today() or Done = false, can't be read yet.
  • Words in quotes stay whole: Name.Contains("rock or roll").
  • To leave out more than one, use and: Status != Read and Status != "To read". Status != Read or Status != "To read" shows Can’t read.
  • In a chip, pick more than one option of a Select, or more than one of this, last and next: the query gets an .In(…) or an or.
  • New in a query fills the first value that fits: under Status.In(Reading, "To read"), the new book is Reading.

Checkbox

RulePasses whenExample
= trueThe box is ticked.#task.Where(Done = true)
= falseThe box is not ticked.#task.Where(Done = false)

!= true is the same as = false.

Edited by and Created by

RulePasses whenExample
=The person is the one named. Write the name you see, capitals or not.#book.Where([Created by] = "Maya")
!=Anyone else.#task.Where([Edited by] != "Maya")
= Me()The person is you. Whoever reads the query sees their own blocks.#task.Where([Created by] = Me())

An agent is found by the name it shows, too. You can sort by these two: #book.Sort([Edited by]).

Quotes and brackets

  • A value with a space goes in quotes: "…", “…” or '…'. #book.Where(Name = "Pride and Prejudice") finds Pride and Prejudice. Without the quotes it shows Can’t read.
  • A value of one word needs no quotes: Status = Reading.
  • A column with a space, or named like a word of the language such as and, goes in square brackets: [Edited by].
  • A pick in the builder writes the quotes and brackets for you.

Sort

StepOrderExample
.Sort(Column)Lowest or earliest first (A to Z, 1 to 9, old to new). Empty values go last. .Sort(Column, asc) is the same.#task.Sort(Due)
.Sort(Column, desc)Highest or latest first.#book.Sort(Pages, desc)
.ThenBy(Column)After a sort: sorts entities with the same first value by a second column. It takes desc too.#book.Sort(Status).ThenBy(Pages)

A Select sorts in the order of its choices (for Status: To read, Reading, Read), not A to Z. With no sort, the newest entities come first.

Limit

.Limit and a whole number shows only the first so many results: #book.Sort(Pages).Limit(2). The count then says how many of all: 2 of 3. .Limit(0) or .Limit(two) shows Can’t read, and says that Limit takes a whole number.

What you see

  • The results, under the block: one row for each entity, with its tag, its name and up to three of its field values. Click a row to open that entity's own page. Back returns to the query.
  • The count, at the end of the block: how many entities match, then a list icon. With a limit: 2 of 3.
  • Show more: a query without a limit shows 100 results at first. Show more (n) under them shows 100 more.
  • Folded: fold a query block with its arrow and it stops running. It keeps the count it last had.
  • The query block itself is never one of its own results.

Under the block you may also see one of these:

MessageWhat it means
Can’t read: …A part of the query is not understood, and the message says which part or what is missing, such as A ) is missing. No results show until it is fixed, because results that skip part of a query would be wrong. Common causes: a column the tag does not have, a rule that does not fit the column's field Type (#task.Where(Done > 1) says Done is a Checkbox field: this rule doesn’t work on it. Try Done = true or Done = false), a missing value, a missing . before a step (#book Pages > 450), a step Fennick doesn't know, or a value with a space that isn't in quotes.
No tag named #… yet.There is no tag with that name. Check the spelling, or make the tag first.
Nothing matches.The query is understood, and no entity passes it.
Reading…Fennick is still counting the results. It goes away in a moment.
Couldn’t load, retrying…The results could not be read just now. Fennick tries again by itself.

More examples

QueryLists
#task.Where(Done = false).Sort(Due)Open tasks, the soonest first.
#task.Where(Due < Today() and Done = false)Tasks that are late.
#book.Where(Status = Reading and Pages > 300).Sort(Started, desc).Limit(5)Up to 5 long books you are reading, the latest started first.
#task.Where(Edited.InLast(1, weeks)).Sort(Edited, desc)Tasks changed in the last week, the newest change first.
#task.Where(Due.InNext(3, days) and Done = false)Open tasks due in the next 3 days.
#book.Where(Mentions.Contains("October"))Books on, or named on, a day page in October.

Agents ask for entities through the Fennick connector, with a query in the same formula language. See People and agents.