---
url: https://simpledatalogs.com/docs/guide/feature-discovery.md
description: >-
  Find out whether users discover a feature, how they reach it and whether it
  works for them, with three question widgets and no setup, shown on a real Mac
  screenshot app.
---

# Feature discovery

Every feature raises three questions. A board answers each one with a single widget, picked as a question in plain words:

| Question | In plain words | Widget question |
|---|---|---|
| **Reach** | Did users find it? | What share of users used \_\_\_? |
| **Path** | How did they get there? | How is \_\_\_ split by \_\_\_? |
| **Stick** | Did it work for them? | What share of \_\_\_ had \_\_\_? · Of users who \_\_\_, how many \_\_\_? |

There are no cohorts, no event definitions and no query language. Your app sends plain events with plain properties, and every picker lists only what your environment has actually sent.

"Users" means devices: an app that sends only a device id has one user per device.

## The example: a Mac screenshot app

XKD Screenshotter is a menu bar app for macOS. It sends these events with the [Swift SDK](/sdks/swift):

| Event | Properties | Sent when |
|---|---|---|
| `app_installed` | `source` (`website`, `homebrew`, `setapp`) | The first launch after install |
| `app_launched` | `launch_source`, `cold_start_ms` | Every launch |
| `screenshot_taken` | `type` (`area`, `full_screen`, `window`), `trigger` (`shortcut`, `menu_bar`, `history_panel`), `format`, `duration_ms` | Every capture |
| `screenshot_saved` | `destination` (`clipboard`, `file`, `both`) | A capture is kept |
| `history_panel_opened` | `source` (`menu_button`, `shortcut`, `dock_click`) | The History panel opens |
| `ocr_used` | `source`, `found_text` (`true`, `false`), `chars_bucket` | Text is read from a capture |

```swift
SimpleDataLogs.track("history_panel_opened", properties: ["source": "shortcut"])
SimpleDataLogs.track("ocr_used", properties: ["source": "capture_toolbar", "found_text": true, "chars_bucket": "50_500"])
```

Two habits make every question below possible:

* **Send a `source` (or `trigger`) property** on any feature that can be opened in more than one way. That property is the path.
* **Send buckets, not raw numbers, for splits.** `chars_bucket: "50_500"` splits; a raw character count only works in conditions and averages.

## Reach: did users find it?

On a board, click **Add widget**, then **What share of users used \_\_\_?** and pick `history_panel_opened`.

The card shows the share of active users who opened History in the range, the counts behind it ("874 of 1,902 users") and the change in percentage points against the previous range. Pick `ocr_used` for a second card and you can see at a glance that OCR reaches far fewer users than History.

To watch reach move after a change (a new onboarding tip, a new ad), add the same event under **More shapes** as a **Line** with the measure **Reach**.

## Path: how did they get there?

**How is \_\_\_ split by \_\_\_?** with `history_panel_opened` and `source`, counting **Users**, shows how History is opened: for example 52 % from the menu button, 41 % by shortcut, 7 % from the Dock.

The same question with `screenshot_taken` and `trigger` shows whether people capture with the keyboard shortcut or the menu bar. A property with up to 6 values draws a donut; more values draw a top list.

## Stick: did it work for them?

**What share of \_\_\_ had \_\_\_?** measures one outcome among an event's rows. Pick `ocr_used`, then the condition `found_text` is `true`: the card says what share of OCR attempts found text.

Conditions take a property, a comparison and a value. Text properties offer **is** and **is not** with the values your app has sent; numeric properties add **<**, **≤**, **>** and **≥**. Each list holds up to 3 conditions, and all must hold.

**Of users who \_\_\_, how many \_\_\_?** follows users from one step to the next. With `app_installed` then `screenshot_taken` within 24 hours, the card shows how many new installs took a first screenshot that day. Add up to 4 steps for a funnel, and give any step its own conditions, such as `screenshot_taken` where `trigger` is `shortcut`.

Conditions and conversions read raw events, so a long range may be refused; the card then asks for a shorter one. A conversion counts each user once.

## Suggested boards

**New board** offers up to three boards built from what your environment sent in the range, with no AI build used:

| Board | Holds |
|---|---|
| **Daily use** | Active users, your busiest event, both on one line per day, and the busiest days |
| **Feature reach** | A reach card for each of your 6 busiest feature events (launches, installs, sessions and page views are left out), each beside a donut by its best few-valued property, preferring `source`, `trigger` or `mode` |
| **Conversions** | Up to 4 conversions between events where at least 20 % of the first event's users also sent the second, and the second is the narrower one |

Click one and it is added as a normal board: rename it, edit any widget, or remove what you don't need.

For the screenshot app, Feature reach puts `screenshot_taken` by `trigger`, `history_panel_opened` by `source` and `ocr_used` by `source` side by side with their reach, which answers reach and path for every feature in one click.

## Next

* [Boards](/guide/boards) lists every widget kind, the condition ops and the widget JSON the assistant writes.
* [Swift SDK](/sdks/swift) shows how to send events and properties from a Mac or iOS app.
