# Declare features

> Measure each feature of your iOS app as a funnel — discovery, adoption, success — by declaring its events and roles in the Union SDK.

Source: https://useunion.dev/docs/features · Updated 2026-10-23

A feature in Union is a funnel built from events your code declares: people who **saw** it, people who **started** it, people who **finished** it. Declaring it in code is a definition, not a suggestion — a build from TestFlight or the App Store creates the feature in the panel.

## Declare a feature

```swift
let checkout = Union.feature("checkout")
checkout.screen("Checkout")                                   // discovery
checkout.start("checkout_started")                            // start
checkout.use("shipping_selected")                             // use
checkout.success("order_placed", properties: ["items": 3])    // success
checkout.failure("payment_failed", properties: ["reason": "card_declined"]) // failure
```

Define each feature key once (a constant or a static handle) and reuse it — a typo creates a second feature.

## Roles

| Role | Meaning |
| --- | --- |
| discovery | Saw that the feature exists and has not tried it yet. `feature.screen(…)` sets it for you. |
| start | Made an attempt whose outcome is not known yet. The funnel’s denominator — without it there is no funnel. |
| use | Working inside the feature; may happen many times per attempt. Counted towards adoption, not a step of its own. |
| success | The feature gave the person what they came for. Once per attempt. |
| failure | The attempt failed from the person’s point of view: permission denied, no results, a save that did not happen. |

The funnel in the panel has three steps — **Discovery → Adoption → Success** — where adoption counts everyone who started or used the feature.

## Rules the server enforces

- **One event name is one (feature, role) pair** everywhere in the code. The same name as `success` in one place and `failure` in another is a conflict the panel shows and never resolves for you.
- Giving up is its own event name (`onboarding_abandoned`), not a property on the completed event.
- A feature is something a person can fail to adopt. A core surface everyone touches on day one (`home`, `items`) reports 100% adoption and says nothing — send those events without a feature.
- Declare only what you instrument. A declared feature nothing emits reads as zero adoption.

> **Development builds do not create features**
> A DEBUG build only pre-fills the feature editor, so experimenting locally never adds features to the panel. Declarations from TestFlight and App Store builds do.

## Changing a feature later

The SDK only ever adds: a new event or role in a later build joins the feature, and nothing you set in the panel is removed or overwritten. A feature you created by hand with the same name is adopted instead of duplicated. When the panel’s definition changes, history is recomputed from the raw events.

## Suggestions

Events you did not declare are grouped by name (`comment_opened`, `comment_posted` → `comment`) into suggestions. Nothing is created until you approve one under **Features → Review**.
