All writing
5 min readDesignNotion

Why page properties are a block, not a panel

Every other tool bolts metadata to the top of the page. Nex makes it a block in the document — and gives up something real to do it.

Open a page in Notion or AFFiNE and there is a strip above the content: tags, dates, who made it, a button to add a property. It is not part of the document. It is a panel attached to the page, backed by columns on a row somewhere.

Nex does it the other way round. Type /properties and you get a block, in the document, wherever you put it. This post is about why — including the thing it costs, which is not small.

Three things you get for free

It goes where it is useful. A properties panel is always at the top, because that is the only place a panel can be. A block is wherever the information belongs: at the top of a spec, certainly, but also halfway down a long note where a status panel actually helps, or at the end of a decision record next to the decision.

It travels with the page, because it is the page. Duplicate a page and the properties come. Export it to markdown and they come out as labelled lines. Save the page as a template and the fields are part of the template. None of that needed a single line of special handling — it works because the block is not special.

Adding a property needs no schema change. The rows live in the block’s attributes, so a new field is a thing you typed, not a migration. There is no property registry to keep in step, no “this property is used by 14 pages” dialog, no orphaned columns.

The thing it costs

Properties in Nex are not queryable. Because they live inside documents, nothing can answer “every page whose Status is Blocked” without reading every document in the account. Notion can answer that question instantly. We cannot answer it at all.

We think that is the right trade today, for a reason that has nothing to do with implementation difficulty: the query is only worth having once the fields are consistent, and fields only become consistent when somebody has decided what they are — which is the schema, arriving through the back door. Most people writing most pages do not want to make that decision, and the tool asking them to is how a notes app becomes an admin job.

It will become the wrong trade. The day there is a database view worth pointing at these, the rows move to a table and this block becomes its editor. That path is deliberately open, and the block was designed knowing it: the rows are already typed, already named, already carrying stable ids.

Dates are not stored in it

The block shows Created and Last edited, and those two are not in the document. They are read live from the page every time the block renders.

This is the kind of detail that sounds pedantic and is not. If the dates were written into the block, they would be a copy — correct at the moment it was inserted and wrong by the next save. You would have a document confidently telling you it was last edited in March while the page it is part of was edited this morning. A fact that the page already knows should be read from the page, every time, not snapshotted into content that has no way of hearing about the change.

The custom fields you type are the opposite: they belong to the document, because you wrote them, and nothing else in the system has an opinion about what Owner or Due or Shipped should say.

And it is searchable

One consequence worth mentioning: because the rows are in the body, the search index reads them. A page whose only mention of a name is in the Owner field is still findable by that name. That fell out of the design rather than being built — which is usually the sign that the design was right.