Metrics that are just a number

Some numbers don't need a chart or a BI tool. They need to be somewhere you'll see them. Here is how insight cards work in Butters, how to keep them current from cron, and how to tell whether a number belongs on a card, in the feed, or in an analytics tool.

6 min read
Three white metric cards in a row, the middle one larger with a yellow value block and a blue up arrow

Most small teams don't need a BI tool. They need four or five numbers somewhere they'll see them: how many orders are stuck, how much came in yesterday, how deep the job queue is. The usual answer is a spreadsheet someone forgets to update or a dashboard project that never ships.

Butters has a smaller answer. Each project has an Insights tab: a grid of cards, each holding one title, one value and an optional emoji. You set a card with one API call and set it again whenever the number changes. This post covers how cards work, how to keep them current from cron, and how to tell which numbers belong on a card at all.

How a card works

A card has three fields:

  • title, like Orders Processing.
  • value, like 23 or $1,449.
  • icon, an optional emoji of up to 16 characters.

The title is the card's key within the project. Posting a title the project hasn't seen creates a card. Posting it again updates that card in place. You never create a card and then update it by id. You just keep saying what the number is now.

curl -X POST https://app.getbutters.com/api/insight \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $GETBUTTERS_API_KEY" \
  -d '{"project": "q4q8nb18qc2i", "title": "Orders Processing", "value": 23, "icon": "🏭"}'

Or with the CLI :

butters insight --project q4q8nb18qc2i --title "Orders Processing" --value 23 --icon "🏭"

The API answers {"ok": true} and the CLI prints set Orders Processing = 23.

Each card also shows when it was last updated, as a relative time like "3 minutes ago". That timestamp matters more than it looks. It's how you notice that the job feeding a card has quietly stopped running.

The value is text

The value is stored as text, not a number. Send 23 and it's saved as "23". Send "$1,449" and it's shown exactly as written.

That's a feature for display. You can format the number however you like: currency, a percentage, 12 / 40, 3h 20m, or OK. The card shows your string in large type and doesn't try to be clever with it.

It's also the limit. Butters doesn't add values up, compare them, or chart them. There's no history either: an update replaces the old value, so yesterday's number is gone. If you need to see a trend, a card is the wrong place for that number.

Two small things follow from values being text. 0 and an empty string both count as values, so a queue that has drained can report 0 and the card will say so. And with the CLI, single-quote anything with a $ in it, or your shell will try to expand it:

butters insight --project q4q8nb18qc2i --title "24h Sales" --value '$1,449' --icon "☀️"

Keeping cards current

A card is only useful if something updates it. The simplest something is cron.

Say your orders live in Postgres. One line per card, run every five minutes:

*/5 * * * * butters insight --project q4q8nb18qc2i --title "Orders Processing" --value "$(psql -tAc "select count(*) from orders where status = 'processing'")" --icon "🏭"

psql -tA prints just the value, with no headers or padding, which is what the card wants.

A daily sales total, formatted as currency by the query itself:

0 7 * * * butters insight --project q4q8nb18qc2i --title "24h Sales" --value "$(psql -tAc "select to_char(coalesce(sum(total), 0), 'FM\$999,999,990') from orders where created_at > now() - interval '24 hours'")" --icon "☀️"

A few more that work well as cards:

  • Queue depth. Whatever your job runner reports as pending. Useful at a glance, and a number you'd never want as an event per job.
  • Open support tickets. Pull the count from your help desk's API in the same cron job.
  • Days since last incident. Store the date of the last incident somewhere and let a daily job do the arithmetic:
LAST=2026-09-02
DAYS=$(( ( $(date +%s) - $(date -d "$LAST" +%s) ) / 86400 ))
butters insight --project q4q8nb18qc2i --title "Days Since Incident" --value "$DAYS" --icon "🧯"

(That's GNU date. On macOS use date -j -f %F "$LAST" +%s.)

If your jobs already report their runs to Butters, which is covered in Deploys, cron jobs, and the things that fail quietly , the card update can go at the end of the same script. The job records that it ran, then records what it found.

A few things to know

Cards are sorted by title, alphabetically. There's no other ordering. If you want Revenue above Queue Depth, name them so the alphabet agrees, or prefix them.

The page doesn't refresh itself. The feed streams new events live, but the Insights tab shows values as of when you loaded it. Reload to see the latest.

Renaming a card makes a new one. Because the title is the key, changing Orders Processing to Orders In Progress in your cron job leaves the old card behind with its last value. Delete it from the dashboard with the ✕ on the card.

Deleting is a dashboard job for now. The API can delete a card, but only by id, and nothing in the API or CLI hands you that id. So in practice cards are removed from the Insights tab, not from scripts. The CLI has no delete command.

Card, event, or analytics tool?

Every number you care about fits one of three places, and picking the wrong one is how feeds get noisy and dashboards go stale.

It's an event when something happened at a moment and someone might care that it happened. An order was placed. A payment failed. A deploy finished. Events have a time, a category, and can notify. What to track first covers which ones to start with.

It's a card when it's a state, not a happening. Nobody wants a feed entry every time the queue grows by one. They want to glance at the queue and see 14. If the question is "what is it right now?", that's a card.

It's an analytics tool's job when you need history, breakdowns or comparisons. Conversion by channel, revenue by week, retention by cohort. Cards hold one current value with no past, so anything you'd want to put on a chart belongs somewhere built for charts.

Some numbers are both. A failed payment is an event, and "failed payments today" is a card. That's fine. The event tells you each time it happens. The card tells you whether today is a bad day.

The rule

A card earns its place if you'd check it at least once a day, and you'd act if it looked wrong.

Orders stuck in processing: you'd check, and you'd go look if it said 200. Days since incident: maybe not action, but it's the number your team actually talks about, so it earns a spot. Total registered users since launch: you'd glance at it once and never again. Leave that one off.

Start with three cards, each fed by one cron line. Watch which ones you actually look at for a week. The ones you skip can go.