Track usage
Declare what a profile does in your application — projects created, orders placed, users invited — once, and get a family of properties that rules and scores can read.
Settings → Usage is where you say what a profile does in your application: the things it creates or interacts with. Profiles is for who a profile is — its plan, its company size, its owner's email. Usage is for what it did, and when.
A usage metric is one thing tracked over time. You declare it once — "projects", measured from the projects table by created_at — and Uptend derives a property for each thing you asked to track. Track everything and you get ten:
| Property | Type | What it holds |
|---|---|---|
projects_total | Number | The measure over every matching row, all time. |
projects_first_at | Date | When the earliest matching row happened. |
projects_last_at | Date | When the latest one did — the recency a churn model wants. |
projects_7d, projects_30d, projects_90d | Number | The measure over the last 7, 30 and 90 days. |
projects_prev_7d, projects_prev_30d, …_prev_90d | Number | The same windows one period earlier, so a rule can compare "this month" with "last month". |
projects_weekly | JSON | Thirteen numbers, oldest first: one per week for the last twelve complete weeks plus the current one. A sparkline. |
They are ordinary properties. Rules and scoring models read them by key like any other; the Profiles list can show a metric's total as a column; the profile page shows the metric as one row on its Usage card. They are locked: you can't edit, retype or correct them, and they are removed with the metric.
You don't have to take all ten. What to track in the editor asks what you want to know, and creates only the properties those answers need — see Choose what to track.
Only workspace admins can change usage metrics — the same permission as Profiles. Usage is measured from the same database profiles come from, so the primary source needs a backing table before a metric can be declared.
The two metrics the templates expect
A fresh workspace's scoring models read projects_total and users_total, and they are the two properties the lifecycle rules most often end up asking about (the rules themselves ship unwritten — only you know what activation means). The empty Usage page offers Track projects and Track users for exactly that reason: each opens the editor with the label, key and — when your schema has a projects or users table pointing at the backing table — the path and timestamp column already filled in. Declaring them lights the templates up without renaming anything.
Add a metric
- Go to Settings → Usage and click Add metric (or one of the suggestions).
- Enter a label. The key follows it — "Paid orders" becomes
paid_orders— until you edit the key yourself. The key is the family prefix, so it is capped at 40 characters and uses only lowercase letters, numbers and underscores. - Build the path from the backing table to the rows you want to measure (below).
- Choose the Happened at column: the timestamp on the measured table that says when each row happened. Uptend guesses
created_atwhen the table has one. Only timestamp and date columns are offered. - Choose a measure (below). Count needs nothing else; the others ask for a column.
- Optionally add conditions. Every condition lives on a step, whichever table it reads: Only measure some orders rows… on the measured step narrows what is counted, and a condition on any other step narrows what the path reaches (below).
- Under What to track, keep or drop the properties the metric creates (below).
- Click Preview, check the values and the two verdicts, then Save.
Choose what to track
The What to track section of the editor decides which properties the metric creates. It asks questions rather than listing property names, and each answer shows the keys it produces underneath it. Everything is on by default, so a metric you don't think about gets all ten.
- Total, all time is always tracked — it's the measure itself, and every metric has one.
- When it happened creates
_first_atand_last_at: the timestamps a recency score reads. Turn off either one on its own. - Track it over time opens the rest. Windows picks any of last 7, 30 and 90 days. Compare each window with the one before adds the matching
_prev_property for each window you kept, so a rule can subtract one from the other. Keep a weekly trend adds_weekly, the thirteen-week series behind the sparkline.
Turning a question off removes its properties from the list under The properties this creates, and the save bar counts what's left. A metric that only answers "how many, ever" is one property — and one less thing for your database to compute on every refresh.
Turning something off on a saved metric deletes those properties. The editor names the ones a save would delete before you click Save. Anything scoring or filtering on them stops finding a value, exactly as if you had removed the metric. The numbers already on each profile stay put until the next refresh.
usage-what-to-trackReach the rows through a path
A metric measures rows in some table that belong to the profile. The path says how to get there from the backing table, one step at a time. Each step lists every table the schema's foreign keys can reach from the previous one, in both directions, with the join spelled out:
projects · projects.account_id → accounts.id— projects that point at the account. Many per account; this is what a metric usually counts.users · accounts.owner_id → users.id— the user the account points at. One per account.
One step is the common case: projects on an account. Two steps reach nested ownership — tasks in the account's projects — and three is the most a path can take. A table with no captured foreign key can still be joined: choose Another table — join by hand and pick the two columns yourself.
Each step can carry its own filter — that is the one place conditions live, so which table a condition reads is never a question of which box you typed it into. A filter on an earlier step excludes everything below it: "projects that are not archived" keeps the archived projects' tasks out too. A filter on the measured step narrows what is counted, and a filter on a lookup step leaves out any measured row whose lookup does not match.
Which step is measured
The two directions do different things. A step onto a table that points at the previous one multiplies rows — many projects per account — and a step onto a table the previous one points at adds no rows, just more columns to read. So the step being measured is the last one that multiplies, and every step after it is a lookup: there to filter through, not to count. The builder labels them, and says so under the steps.
This is the shape to reach for when the number you want and the condition you want live on different tables:
- Step 1 —
charges · charges.account_id → accounts.id. Measured. One row per charge. - Step 2 —
payment_intents · charges.payment_intent_id → payment_intents.id. Lookup. One intent per charge.
Sum charges.amount, then put live_mode is true on step 2. The charges whose intent is test-mode drop out, and the total is a sum of charge amounts — not of intent amounts counted once per charge, which is what measuring the last table would have given you.
To measure the lookup's rows instead, you need a path that reaches that table directly — a lookup can never be what is counted, because the join produces one row per row before it. When the table you want to count has no step that reaches it by fan-out, Distinct count over the pointer column gets you the same number: count(distinct charges.payment_intent_id) is how many intents an account has.
A lookup has to match at most one row, so its join column must be the table's primary key or carry a unique index. Uptend refuses to save a lookup that could match twice: it would count some rows more than once and there would be nothing on the screen to tell you.
The same builder fills in the Joined value, First related row and List shapes when you compute a property. One way to reach a related table, everywhere.
Measures
| Measure | Needs | _total and the windows read as | When there are no rows |
|---|---|---|---|
| Count | nothing | how many rows | 0 |
| Sum | a numeric column | the column summed | 0 |
| Average | a numeric column | the column averaged | unknown, not zero |
| Distinct count | any column | how many different values | 0 |
A metric has one measure, and each measure is its own metric. "Orders" is a count over orders; "Revenue" is a sum of orders.amount; "Active seats" is a distinct count of memberships.user_id. Three declarations, three keys, the same family shape — and metrics that share a table, a path and a timestamp column cost your database one pair of statements between them, however many there are.
_first_at and _last_at are about the rows, not the measure: a revenue metric filtered to paid orders still says when the last paid order happened.
Preview and the two verdicts
Preview runs the metric for the same five profiles the mapping preview shows and lists a column for each property it will create — so a metric that declined the 90-day windows has no 90-day column. Nothing is stored. Underneath are two verdicts:
- Cost — the planner's estimate of the statement the refresh will run, graded Light, Moderate or Heavy, exactly as for a computed property.
- Index — whether every join on the path is a lookup. Indexed means each step's join column leads an index in your database (for the measured step, an index on the join column and the timestamp together is ideal, and the card says when one would help). Unindexed means some step walks a whole table, or the planner chose a sequential scan; the card names the table with its row estimate and gives a ready-to-run
CREATE INDEX CONCURRENTLYrecipe.
Both are advice. An unindexed metric still saves — but until the index exists it is recomputed at most daily, whatever refresh cadence the source has, and a run that times out on it reports the recipe with the error. Run the recipe on your database, then Refresh schema on the source: the verdict clears on the next introspection without re-saving anything. The pill in the metric list shows the stored verdict; a metric you have added but not yet saved reads Graded on save.
When metrics are recomputed
Usage metrics ride the primary source's Refresh derived values cadence, the same one that recomputes joined, first-row and list values, because a metric's windows move even when nothing about the profile changed. The line under the metric list states the cadence in force and links to the source's page where it is set; see Sync. Full and incremental reads also compute the family for the rows they touch.
A profile the refresh has not reached since a metric was declared reads Not computed yet on its Usage card until the next run. The Usage page says the same thing above the cadence line — Not computed yet when no refresh has run since the metrics were declared, Changed N minutes ago when profiles still carry the previous values — with a Sync now button that runs the source's refresh right away (the lifecycle recomputes when it finishes) and Computing now while it runs. Recompute now on the Lifecycle page does not compute metrics; it re-evaluates the lifecycle over the values already on the profiles, so a rule that reads projects_total sees nothing until the refresh has landed the family. Once every profile is current the line reads Last computed for every profile N minutes ago.
Use a metric in a score
A saved metric's row offers Use in a score to workspace admins who can edit scoring models. It lists the workspace's models and opens the one you pick — Profile value is usually the one a usage metric belongs in — with the metric's family keys offered as one-click factors. A card under Factors names the metric and lists the keys it actually tracks. Click one to add a factor already pointed at it — a count (_total, _30d, _prev_30d, …) becomes an Activity volume factor through a logarithmic transform, a timestamp (_first_at, _last_at) becomes an Activity recency factor — then adjust the weight and transform, preview, and save. A key a factor already reads is marked added rather than offered twice. The _weekly series is not offered: no factor reads a list.
The option appears once the set is saved, because a metric that is only added has no properties yet. See Score a usage metric for the scoring side.
Edit or remove a metric
Edit on a row opens the same editor. Changing the label relabels the metric's properties; changing the key renames them, and any rule or score that read the old keys stops finding them — the same consequence as removing a property.
The remove button on a row drops the metric from the set; saving applies it. Its properties go with it. The values already stored on profiles are kept, as they are when a property is removed, so declaring the same key again later finds them where they were.
A metric's keys and a hand-declared property can't collide. Declaring a metric whose family would reuse a key that already exists under Profiles is refused with the key named, and so is declaring a property that would take a metric's key.
On the profile page
Each profile's page has a Usage card between its properties and its lifecycle: one row per metric with the total, when the latest row happened, its widest window with the change against the period before, and a sparkline of the last thirteen weeks. The card only shows the columns your metrics actually track, and a metric that skipped one reads a dash. A metric with nothing yet reads None yet.
Below it, Recent activity is where a per-profile narrative — what this profile did most recently, read on demand from your database — will appear. It is not available yet; the section says so rather than showing an empty list.