Back arrowAll guides

How to build an upvote system for feature requests in Notion

Pixel art of request rows with an arrow into a glowing card linked to a gold cluster on a dark green background
Talton Figgins•••6 min read

Notion doesn't have a voting feature, so you build one out of two properties and a formula. Add a Person property to your feature request database, let people tag themselves in it to vote, then add a Formula property that counts how many names are in that field. Sort the view by the formula, descending, and you have a ranked backlog. Notion's own guide to this pattern confirms there's no shortcut: the formula they publish is prop("Upvoted by").length(), counting names tagged in a Person property called "Upvoted by."

That's the whole mechanism, and it works. The limit worth stating upfront: a Person property can only hold people who are already members of your Notion workspace. Whoever isn't in the workspace can't be tagged, which means they can't vote, no matter how much they want the feature.

“we need SSO before rollout”
Slack logo“any update on single sign-on?”
Zendesk logo“SSO came up twice on this call”
↓ classified + deduped into
SSO requests
8 accounts asking · quotes kept
filesLinear logoLinear issue, quotes attached
writesNotion logoinsight report in Notion
answersyour agents
The Notion board only ever sees the request that got typed into it. The same ask, said in Slack or on a call, never touches the vote count.

Step 1: build the request database

Start with a plain database. Four properties do the job:

  • Name: the request, written as a sentence, not a title fragment
  • Status: a select property: Under review, Planned, Shipped
  • Asked by: a Created by property, which Notion fills in automatically with whoever created the row
  • Upvoted by: a Person property, empty by default

The Asked by and Upvoted by split matters. Without it, the creator's vote and everyone else's votes look identical, and you lose the ability to tell "one person cares a lot" from "six people asked."

Step 2: write the counting formula

Add a Formula property, and set it to:

prop("Upvoted by").length()

This counts the number of names in the "Upvoted by" property and returns it as a number. The person who filed the request should tag themselves in "Upvoted by" too, so their own request starts at a vote of one instead of zero.

To vote, a teammate opens the row and adds their name to "Upvoted by." That's the entire voting interaction. No button, no separate form, one property edit.

Step 3: sort and filter the view

A vote count with no view discipline around it is just a number nobody looks at. Build one view that:

  • Sorts by the formula property, descending
  • Filters Status to hide Shipped (a shipped item with 40 votes clutters the top of the list for no reason)
  • Groups by Status if you're tracking more than about thirty open requests

This is the view you actually check before a planning meeting. The formula and the properties behind it exist only to make this one sortable list honest.

A rollup version, if requests and votes need to live apart

The Person-property formula above works as long as one database holds everything. Some teams want an audit trail of individual votes, or want to enforce one vote per person somewhere that isn't a simple tag. For that, use two databases instead of one: a Requests database and a Votes database, connected by a Relation property, with a Rollup on Requests that counts the related Votes rows.

It's more setup for the same number displayed at the end, so most teams should start with the single-database formula in Step 2 and only split into a relation and rollup if they specifically need per-vote records, like a timestamp or a note on why someone voted.

One thing a rollup doesn't fix: a Votes database is still a Notion database, and adding a row to it still requires the person doing the voting to have access to that database. The membership ceiling doesn't move just because you added a second table.

Where the workspace-only ceiling shows up

A Notion form looks like the workaround for customers who have no workspace seat, and it solves intake rather than voting. A form connects to a database, and every response it collects lands as its own row. It has no way to find an existing row and edit one of its properties, only to write a new one, so it cannot add a submitter to the Upvoted by field on a request that already exists. Ten customers submitting the same request produce ten rows, not one row with ten votes.

Two Notion mechanics, both true regardless of how the database is built:

Put together, this is a request board that ranks by who works at your company and happens to check it, not by who's actually asking. The requests coming in from support tickets, sales calls, and the Slack channels your customers are already in never reach the Person property at all, because nobody is going to open Notion, find the matching row, and tag themselves in it. Most of them just don't.

Where Modem picks up

A Notion-native vote count and a real signal of customer demand pull apart right here. Modem reads feature requests out of Slack, support tickets, and sales calls directly, matches multiple mentions of the same ask to a single counted topic even when nobody typed the same words twice, and keeps every requester attached to it, whether or not they've ever opened your Notion workspace. The count that matters is "how many separate customers and accounts asked," not "how many teammates remembered to tag themselves."

Modem's Notion integration can write those counted topics back into your workspace as pages and database rows, so the board format survives and only the source of the number changes. Related reading: the six best tools to sync customer feedback to Notion compares the wider field, and why feature voting boards fail covers the failure modes that show up once a board like this has been running for a year, Notion-built or otherwise. Modem is what we sell, so weigh that bias into the comparison. It doesn't undercut the case for the setup above, since a team ranking only its own internal backlog doesn't need anything more than the Person-property formula.

Set this up before your next planning meeting

Add "Upvoted by" as a Person property and one Formula property with prop("Upvoted by").length() to whatever database already holds your feature requests. Sort by it, descending. That's the entire native system, and it will tell your team more about its own backlog by Friday than a quarter of scattered Slack DMs did.