# Welcome to Setgreet Docs
Source: https://www.setgreet.com/docs
> Learn how to design, ship, and measure native mobile flows with Setgreet.
# Welcome to Setgreet Docs
Setgreet is **the AI growth team for your mobile app**. Describe what you want to fix, and Setgreet designs, ships, and measures it as real native screens inside your iOS, Android, React Native, and Flutter app, live in minutes without an app release.
Use Setgreet to build onboarding, paywalls, feature announcements, in-app surveys, NPS prompts, upgrade nudges, and any other flow your users should see at the right moment.
## How it works
1. **Describe or design** the flow. Ask the AI in plain language, or build it screen by screen in the visual flow builder.
2. **Target the right users** with attribute- and event-based trigger rules.
3. **Publish instantly.** The mobile SDK renders flows natively on every platform, no app release required.
4. **Measure results** with built-in analytics on views, completions, and component interactions.
## Key sections
| Section | What you will find |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| [Getting Started](/docs/getting-started) | Create an account, register your app, install the SDK, and publish your first flow. |
| [Flows](/docs/flows) | Deep dive into the flow builder, screens, triggers, branching, and the publish lifecycle. |
| [Components](/docs/components) | Reference for every component you can place on a screen -- text, buttons, inputs, feedback widgets, and more. |
| [Personalization](/docs/personalization) | Variable bindings, user attributes, and dynamic content. |
| [Audience & Targeting](/docs/audience) | Trigger conditions, user segments, and event-based targeting. |
| [Experiments](/docs/experiments) | A/B testing and experimentation for flows. |
| [Analytics](/docs/analytics) | Flow-level and component-level analytics, funnel tracking, and exports. |
| [Integrations](/docs/integrations) | Connect Setgreet to your analytics stack and third-party tools. |
| [SDK Reference](/docs/sdk) | Platform-specific SDK guides for iOS, Android, React Native, and Flutter. |
| [MCP Server](/docs/mcp) | Connect Claude, ChatGPT, Cursor, and any MCP-compatible AI agent to your Setgreet platform. |
| [Team & Billing](/docs/team) | Organization management, roles, and subscription plans. |
New to Setgreet? Start with the [Getting Started](/docs/getting-started) guide to go from zero to a published flow in
under 30 minutes.
---
# Audience
Source: https://www.setgreet.com/docs/audience
> Understand, segment, and target your app's users.
# Audience
The Audience section is where you understand who your users are and define which groups should see specific flows. Every end user who interacts with your app through the Setgreet SDK becomes part of your audience automatically.
## End users
An end user is anyone who opens your app with the Setgreet SDK installed. Users are identified in two ways:
- **Anonymous users** -- the SDK generates a unique anonymous ID for every device. Flows can target anonymous users, but you cannot attach custom attributes until the user is identified.
- **Identified users** -- when your app calls the SDK `identifyUser` method with a user ID and optional attributes, the user becomes a known individual you can target with precision.
Once identified, users carry [attributes](/docs/audience/user-attributes) like `plan`, `signupDate`, or `completedOnboarding` that you define and send from your app.
## Segments
[Segments](/docs/audience/segments) let you group users by shared characteristics. A segment is a reusable set of conditions -- for example, "users on the Pro plan who signed up in the last 30 days." You build segments with AND/OR logic using the condition builder in the dashboard.
Segments are used in [flow triggers](/docs/flows) to control which users see a flow, so you can ship different experiences to different audiences without writing conditional logic in your app.
## How targeting works
When the SDK checks whether a flow should display, it evaluates the flow's trigger conditions against the current user's attributes and events. This evaluation happens locally on the device, so flows appear instantly without network round-trips.
| Concept | Purpose |
| ------------------------------------------------- | ----------------------------------------------------------------------- |
| [User Attributes](/docs/audience/user-attributes) | Key-value data about each user, used for personalization and targeting. |
| [Segments](/docs/audience/segments) | Reusable groups of users defined by attribute and behavior conditions. |
User attributes and segments are only as useful as the data your app sends. Make sure to call `identifyUser` with
relevant attributes after login and whenever key user properties change.
---
# Segments
Source: https://www.setgreet.com/docs/audience/segments
> Create user segments for targeted flow delivery.
# Segments
A segment is a reusable definition of a user group based on attribute conditions. Instead of repeating the same targeting rules across multiple flows, define a segment once and reference it wherever you need it.
## What segments are
A segment answers the question: "Which users match these conditions?" For example:
- **Power users** -- `loginCount` greater than `50` AND `isPremium` equals `true`
- **New free users** -- `plan` equals `"free"` AND `signupDate` is within the last 7 days
- **Enterprise accounts** -- `plan` equals `"enterprise"`
Segments evaluate dynamically. A user who did not match yesterday might match today if their attributes change.
Segments are scoped to an app. You create them for a specific app and they are only available in flows belonging to
that app.
## Creating a segment
1. Go to **Audience** in the sidebar.
2. Make sure the **Segments** view is selected.
3. Click **Create Segment**.
4. Give the segment a descriptive name.
5. Add one or more conditions.
### The condition builder
Segment conditions are combined with **AND** logic -- a user must match **every** condition in the segment to be included. Segments do not support OR logic or nested groups internally. To express OR logic, create multiple segments and combine them at the flow trigger level, which does support AND/OR grouping.
Each condition consists of three parts:
1. **Attribute** -- the user attribute key to evaluate.
2. **Operator** -- how to compare the attribute value.
3. **Value** -- the target value to compare against.
### Available operators
| Operator | Description |
| -------------------- | --------------------------------------------- |
| `equals` | Exact match. |
| `notEquals` | Does not match. |
| `contains` | Value contains the substring. |
| `notContains` | Value does not contain the substring. |
| `startsWith` | Value starts with the substring. |
| `endsWith` | Value ends with the substring. |
| `greaterThan` | Value is greater than the target. |
| `lessThan` | Value is less than the target. |
| `greaterThanOrEqual` | Value is greater than or equal to the target. |
| `lessThanOrEqual` | Value is less than or equal to the target. |
## Using segments in flow triggers
When configuring a flow's trigger conditions, you can reference a segment instead of building conditions from scratch:
1. Open your flow in the editor.
2. Open the trigger editor (via the Publish modal -- see [Flow Triggers](/docs/flows/triggers)).
3. Add a segment reference as a trigger item.
4. Choose the segment you want to target.
The flow will only display to users who match the segment at evaluation time. You can combine segment references with other trigger conditions and use AND/OR grouping at the flow trigger level.
## Editing segments
To edit a segment:
1. Go to **Audience** in the sidebar.
2. Open the **Segments** view.
3. Click the segment you want to edit.
4. Modify the conditions and save.
Editing a segment affects every flow that references it. Review which flows use a segment before making changes.
## Deleting segments
You can delete a segment from the segment list. Review which flows reference the segment before deleting it so no flow ends up with a broken targeting rule.
## Best practices
- **Name segments descriptively.** "Premium users on iOS" is more useful than "Segment 1."
- **Keep segments focused.** A segment with 10 conditions is hard to reason about. Break complex targeting into multiple simpler segments and combine them at the flow trigger level when needed.
- **Audit segment usage.** Periodically check which flows reference each segment to avoid stale definitions that no longer match any active flow.
---
# User Attributes
Source: https://www.setgreet.com/docs/audience/user-attributes
> Define and use custom user attributes for targeting and personalization.
# User Attributes
User attributes are key-value pairs that describe your end users. They power two core capabilities in Setgreet: **targeting** (deciding which users see a flow) and **personalization** (inserting user-specific content into screens).
## What attributes look like
An attribute is a named property with a typed value. For example:
| Attribute key | Type | Example value |
| ------------- | ------- | ---------------------- |
| `email` | String | `jane@acme.com` |
| `plan` | String | `pro` |
| `loginCount` | Number | `42` |
| `isPremium` | Boolean | `true` |
| `signupDate` | Date | `2026-01-15T00:00:00Z` |
You decide which attributes matter for your product. There is no fixed schema -- define whatever properties help you target and personalize flows.
## How attributes are set
Attributes reach Setgreet through two paths:
### 1. SDK identify call
The primary method. When your app identifies a user, you pass attributes alongside the user ID:
```swift
// iOS example
Setgreet.identifyUser(userId: "user_123", attributes: [
"plan": "pro",
"loginCount": 42,
"isPremium": true
])
```
```kotlin
// Android example
Setgreet.identifyUser("user_123", mapOf(
"plan" to "pro",
"loginCount" to 42,
"isPremium" to true
))
```
Call `identifyUser` after login, after signup, and whenever a relevant attribute changes (for example, when a user upgrades their plan).
### 2. Branch node responses
A [Branch node](/docs/flows/branching) with **Save to Attribute** enabled saves the user's response as a user attribute. This works only for branchable components (RadioGroup, NPS, Rating, Dropdown, Checkbox, Button). See [Form Data Collection](/docs/personalization/form-data) for details.
## Defining custom attributes
You can view and manage the attributes Setgreet knows about from the app edit screen:
1. Go to **Apps** in the sidebar.
2. Click the **edit** icon on your app to open the edit modal.
3. Open the **SDK Data** tab.
Here you can:
- See all attributes that have been received from the SDK.
- Manually define custom attributes with their expected types, before any SDK calls are made.
Each attribute has a key and an explicit type. When you define a custom attribute in the dashboard, you set the type yourself -- the type is not inferred from incoming data.
### Supported types
| Type | Description | Example |
| ----------- | ------------------------------ | ------------------------- |
| **String** | Free text or enum-like values. | `"enterprise"`, `"en-US"` |
| **Number** | Integer or decimal values. | `7`, `3.14` |
| **Boolean** | True or false. | `true`, `false` |
| **Date** | ISO 8601 date string. | `"2026-06-01T12:00:00Z"` |
## Using attributes in flows
### Variable bindings
Insert attribute values directly into screen content. For example, a text component can display `Hello, {{firstName}}!` and the SDK replaces `{{firstName}}` with the current user's value at render time.
### Trigger conditions
Use attributes in flow trigger rules to control who sees a flow. For example:
- Show an upgrade prompt only when `plan` equals `"free"`.
- Show a welcome-back flow when `loginCount` is greater than `1`.
- Show a feature tour only when `completedOnboarding` equals `false`.
### Segments
Combine multiple attribute conditions into a [segment](/docs/audience/segments) for reuse across flows.
Attribute keys are case-sensitive. `Plan` and `plan` are treated as different attributes. Establish a naming
convention early and stick to it.
## Best practices
- **Keep attributes flat.** The SDK accepts top-level key-value pairs, not nested objects.
- **Use consistent types.** Send each attribute with the same type every time so targeting conditions behave predictably.
- **Send attributes early.** Call `identifyUser` as soon as you have user data available -- before the SDK evaluates any flow triggers.
- **Avoid PII in attribute keys.** While values can contain personal data (like email), keep your key names generic. This makes it easier to manage data privacy.
---
# Billing
Source: https://www.setgreet.com/docs/billing
> Understand Setgreet's usage-based billing model.
# Billing
Setgreet uses a **Monthly Active User (MAU)** billing model. You pay based on how many unique end users interact with your app during each billing period -- not per flow, not per screen, and not per event.
## What counts as an MAU
An MAU is a unique end user who generates at least one SDK request during a billing period. Specifically:
- Each unique `userId` (set via the SDK `identifyUser` call) counts as one MAU per app per billing period.
- If a user is not identified, their anonymous device ID counts as the MAU identifier.
- A user who interacts with your app on multiple days within the same billing period still counts as **one** MAU for that app.
- Your organization's **MAU limit applies to the total across all apps**. If you have multiple apps and the same user is active in two of them, they contribute 2 to your organization's MAU total.
### What generates an SDK request
Any SDK interaction counts: flow displays, event tracking, user identification, and flow fetches. If the SDK communicates with the Setgreet API on behalf of a user, that user is counted.
## Anniversary-based billing
Setgreet uses **anniversary-based billing periods**, not calendar months. Your billing period starts on the day you subscribed and renews on the same date each month.
For example, if you subscribed on January 15, your billing periods are:
- January 15 -- February 14
- February 15 -- March 14
- March 15 -- April 14
This means your MAU count resets on your subscription anniversary date, not on the first of each month.
You can view your current billing period, MAU usage, and next renewal date in **Settings > Billing** on the dashboard.
## Plans overview
Setgreet offers four plans to match your scale:
| Plan | Price | MAU included | Team members |
| ---------- | ------- | ------------ | ------------ |
| **Free** | $0 | 5,000 | 1 |
| **Launch** | $49/mo | 25,000 | 3 |
| **Growth** | $299/mo | 100,000 | 10 |
| **Scale** | $799/mo | 250,000 base | Unlimited |
Every new workspace starts with about 14 days of the paid features unlocked, then drops to the permanent free plan.
See [Plans & Usage](/docs/billing/plans-and-usage) for a full comparison of features, limits, and overage pricing.
---
# Plans & Usage
Source: https://www.setgreet.com/docs/billing/plans-and-usage
> Compare plans and understand usage limits.
# Plans & Usage
Setgreet offers four plans designed to grow with your product. All plans include the core flow builder, native SDK rendering, MCP access, and flow analytics. Higher tiers unlock additional features, higher usage limits, and more team capacity.
## Plan comparison
| Feature | Free | Launch ($49/mo) | Growth ($299/mo) | Scale ($799/mo) |
| -------------------------------------------------- | -------- | --------------- | ---------------- | ---------------- |
| **MAU included** | 5,000 | 25,000 | 100,000 | 250,000 base |
| **Team members** | 1 | 3 | 10 | Unlimited |
| **Flow builder** | Yes | Yes | Yes | Yes |
| **Native SDK rendering** | Yes | Yes | Yes | Yes |
| **Analytics (incl. path, component, time-series)** | Yes | Yes | Yes | Yes |
| **MCP server access** | Yes | Yes | Yes | Yes |
| **A/B testing (experiments)** | -- | Yes | Yes | Yes |
| **Realtime collaboration** | -- | Yes | Yes | Yes |
| **Integrations** | -- | Yes | Yes | Yes |
| **Custom integrations** | -- | -- | -- | Yes |
| **SLA** | -- | -- | -- | Yes |
| **Dedicated CSM** | -- | -- | -- | Yes |
| **Overage pricing** | Hard cap | Hard cap | Hard cap | $2 per 1,000 MAU |
Every new workspace starts with about 14 days of the paid features unlocked -- experiments, segmentation, conversion
goals, integrations, and up to 3 seats with live collaboration. When it ends you drop to the permanent free plan. No
card, no charge, and your MAU allowance is the free one throughout, so there is never a surprise bill.
## Plan details
### Free
The free plan is for individuals and apps before real revenue. It is free forever, not a trial. It includes:
- **5,000 MAU** per billing period.
- **1 team member** (the Owner).
- Full access to the flow builder with all components.
- Native SDK rendering on all platforms.
- Full analytics -- views, completions and dismiss rate, plus path analysis,
component-level tracking and time-series. Seeing whether a flow worked is part
of building it, so none of it is gated.
- MCP server access.
- **5 one-time AI credits** (these do not reset -- top-up packs are available on every plan).
The free plan has a **hard cap** at 5,000 MAU.
### Launch ($49/month)
Launch is for apps starting to earn and ready to iterate. It includes everything in Free, plus:
- **25,000 MAU** per billing period.
- **3 team members**, with realtime collaboration in the editor.
- **A/B testing** -- run experiments on your flows to find the best-performing variants.
- **Audience segmentation** and **conversion goal tracking** -- target flows by user
attributes, and define what counts as success.
- **Integrations** -- forward events to Segment, Amplitude, Mixpanel, and webhooks.
- **10 AI credits per month**, plus AI auto-translate and image generation.
The Launch plan has a **hard cap** at 25,000 MAU.
Launch is also available on an annual plan at $37/month (25% discount, billed yearly).
### Growth ($299/month)
The Growth plan is for product teams shipping production apps. It includes everything in Launch, plus:
- **100,000 MAU** per billing period.
- **10 team members**.
- **25 AI credits per month**.
- Priority email and live chat support.
The Growth plan has a **hard cap** at 100,000 MAU.
Growth is also available on an annual plan at $224/month (25% discount, billed yearly).
### Scale ($799/month)
The Scale plan is for organizations with large user bases and advanced needs. It includes everything in Growth, plus:
- **250,000 MAU base**, then **$2 per additional 1,000 MAU** (no hard cap).
- **Unlimited team members**.
- **100 AI credits per month**.
- **SLA** -- guaranteed uptime and response time commitments.
- **Dedicated Customer Success Manager** -- a named contact for onboarding, strategy, and support.
- **Custom integrations** -- work with the Setgreet team to build integrations tailored to your stack.
Scale is billed monthly only -- annual billing is not available on this plan. Above roughly 1M MAU, pricing moves to a
custom agreement.
## Usage tracking
Monitor your MAU usage in real time from **Settings > Billing** in the dashboard. The usage panel shows:
- **Current MAU** -- the number of unique users counted so far in the current billing period.
- **MAU limit** -- your plan's included MAU.
- **Usage percentage** -- a progress bar showing how close you are to your limit.
- **Billing period** -- the start and end dates of your current period.
Usage data updates in near real-time. There may be a brief delay between a user's first SDK request and their
appearance in the usage count.
## Usage limits and grace periods
When your MAU usage reaches your plan's limit:
### Free, Launch and Growth plans
These plans include a **7-day grace period** with a **10% usage buffer** after hitting the MAU cap. Practically, this means:
- Free can serve up to **5,500 MAU** during the 7-day grace period (5,000 × 1.1).
- Launch can serve up to **27,500 MAU** during the 7-day grace period (25,000 × 1.1).
- Growth can serve up to **110,000 MAU** during the 7-day grace period (100,000 × 1.1).
- A warning appears in the dashboard at 80% of your limit, and again when the grace period starts.
Once either the 7 days or the 10% buffer is exhausted, the SDK stops displaying flows to new users who have not yet been counted. Users already counted in the current period continue to see flows normally until the next billing cycle.
### Scale plan
The Scale plan has no hard cap. Usage beyond the 250,000 MAU base is billed at a single flat rate:
| Usage range | Rate |
| --------------- | ------------------------------- |
| **0 – 250,000** | Included in the base $799/month |
| **250,001+** | $2 per additional 1,000 MAU |
Overage charges are calculated at the end of each billing period and added to your next invoice. There is no 7-day
grace period on Scale -- service never pauses, and usage over 250,000 is billed directly. Above roughly 1M MAU,
[contact us](/contact) for custom pricing.
## Upgrading your plan
1. Go to **Settings > Billing**.
2. Click **Change Plan**.
3. Select the new plan.
4. Confirm and complete payment.
Upgrades take effect immediately. Your new MAU limit and features are available right away. The price is prorated for the remainder of your current billing period.
## Downgrading your plan
Downgrades take effect at the end of your current billing period. You keep your current plan's features and limits until the renewal date, at which point the lower plan activates.
If your current MAU usage exceeds the lower plan's limit, the SDK will enforce the new limit after the downgrade takes
effect. Consider timing your downgrade for a period when your usage is naturally lower.
## FAQ
**Does the free plan expire?**
No. The free plan is free forever. The 14 days of unlocked paid features expire; the plan does not. You only pay if you upgrade.
**What happens if I exceed my MAU limit mid-month?**
On Free, Launch and Growth, a 7-day grace period with a 10% usage buffer kicks in and service pauses if it runs out -- you are never billed for the excess. On Scale, overage is billed at the end of the period at $2 per additional 1,000 MAU.
**Can I switch plans mid-billing period?**
Yes. Upgrades are immediate and prorated. Downgrades take effect at the next renewal.
**How is MAU counted across multiple apps?**
MAU is deduped per `(app, user)` pair and then summed at the organization level against your plan's MAU limit. A user active in two apps contributes 2 MAU to your organization total.
**Is there an annual plan?**
Yes for Launch ($37/mo equivalent) and Growth ($224/mo equivalent), both a 25% discount. Scale is monthly only because of the usage-based overage billing cycle.
---
# Your first 14 days
Source: https://www.setgreet.com/docs/billing/reverse-trial
> What is unlocked when you create a workspace, and what changes when it ends.
# Your first 14 days
Every new Setgreet workspace starts with the paid features unlocked for about 14 days. When that window closes, the
workspace stays on the free plan forever. Nothing is charged, and no credit card is involved at any point.
This is not a trial of the free plan. The free plan is permanent. The 14 days are a trial of the **paid** features on
top of it.
## What is unlocked
For the first 14 days your workspace has:
- **A/B experiments** -- split traffic across flow variants and measure the winner.
- **Audience segmentation** -- target flows by user attributes and events.
- **Conversion goal tracking**.
- **Integrations** -- webhooks, Segment, Amplitude, Mixpanel.
- **3 team members** with realtime collaboration in the editor.
## What is not
Three things stay on the free plan's terms throughout -- two limits, and one
that needs no trial at all:
- **Your MAU allowance is 5,000**, the free plan's cap. It is deliberately not raised. Building an audience against a
temporarily larger cap and then losing it on day 15 would pause service for real users, which is a far worse outcome
than a smaller number up front.
- **AI credits stay at the free plan's 5 one-time credits.** AI auto-translate and image generation draw from that same
small pool, so they remain paid-plan features rather than something that dead-ends after one use. Top-up packs are
purchasable on any plan if you want more.
- **Analytics is not part of the trial, because it is free forever.** Path analysis, component-level tracking and
time-series are on every plan including the free one. Seeing whether your flow worked is part of building it, so
there is nothing here to lose on day 15.
## What happens on day 15
Your workspace drops to the free plan. Concretely:
- Flows, apps, screens, analytics history and SDK integration are **untouched**. Everything you built keeps working.
- Creating or starting an A/B experiment is no longer available. **Experiments that are already running can still be
paused or completed** -- you are never stranded mid-test.
- Segments and integrations become read-only paths in the dashboard.
- **Analytics is unaffected** -- it is free on every plan, so your dashboards keep working exactly as they did.
- Team seats return to 1. Existing members are not removed.
You get an email about three days before the window closes, and a banner in the dashboard counts down.
## Extending or upgrading
Upgrade any time from **Settings > Billing**. Upgrading before the window closes is seamless -- nothing turns off in
between. See [Plans & Usage](/docs/billing/plans-and-usage) for what each plan includes.
---
# Build with AI
Source: https://www.setgreet.com/docs/build-with-ai
> Describe a flow in plain language and let the assistant draft editable native screens you can refine.
# Build with AI
Build with AI is an assistant that sits on top of the visual flow builder. Describe the flow you want in plain language, and the assistant drafts real native screens you can open in the editor. It can also edit an existing flow when you ask for changes in chat.
Think of it as an accelerator, not a black box. The assistant gives you a head start on structure, copy, and layout, and you stay in control with the full drag-and-drop editor for everything that follows. Nothing the assistant creates goes live until you publish it yourself.
## What you can do
| Capability | What it does |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **Create a flow with AI** | Describe your app and your goal. The assistant asks a few questions, proposes a plan, and drafts a flow once you confirm. |
| **Edit a flow with AI** | Open an existing flow and ask for changes in plain language. The assistant edits the draft and the canvas updates. |
| **Refine in the editor** | Take any AI draft and adjust screens, copy, components, targeting, and styling with the same tools you use everywhere. |
## Creating a flow with AI
Use this when you want to go from an idea to a working draft without building every screen by hand.
1. **Start a new flow.** From the Flows dashboard, click **New Flow**.
2. **Choose Build with AI.** Pick the **Build with AI** option instead of starting from a blank flow or a template.
3. **Describe your app and your goal.** Tell the assistant what your app does and what the flow should accomplish. For example, "An onboarding flow for my language-learning app that ends on a paywall."
4. **Answer a few questions.** The assistant asks a short set of intake questions to fill in the details it needs, such as your audience, the key value to highlight, and where the flow should end.
5. **Review the proposed plan.** Before anything is generated, the assistant proposes a plan: the screens it intends to create and the order they will appear in. Read it over.
6. **Confirm to generate.** When the plan looks right, confirm it. The assistant generates a draft flow with real, editable native screens. (Generating a flow uses an AI credit. See [Credits](#credits) below.)
7. **Refine in the editor.** The draft opens in the flow builder. Adjust copy, swap components, change styling, set targeting, and rearrange screens until it is exactly what you want.
The plan-then-confirm step means nothing is generated until you approve the proposed screens. If the plan is not quite
right, tell the assistant what to change before you confirm.
## Editing a flow with AI
You do not have to generate a flow from scratch to use the assistant. Any flow can be edited by chat, including flows you built by hand, imported from Figma, or started from a template.
1. **Open a flow.** Open any existing flow in the flow builder.
2. **Switch on Build with AI.** Open the Build with AI panel for that flow.
3. **Ask for the change you want.** Describe the edit in plain language. The assistant updates the draft directly and the canvas reflects the change so you can see the result right away.
Some example prompts:
| Prompt | What the assistant does |
| --------------------------------------------- | ---------------------------------------------------- |
| "Add a permission screen before the paywall." | Inserts a new screen at the right point in the flow. |
| "Reorder these steps." | Rearranges the screens into the order you describe. |
| "Change the paywall to a free-trial layout." | Reworks an existing screen to a different layout. |
After an AI edit, the result is still a draft. Review it, refine it in the editor, and publish when you are ready.
Editing a flow by chat does not spend a generation credit. Only generating a new flow from a confirmed plan uses an AI
credit.
## Credits
Generating a flow with AI uses **AI credits**. Editing an existing flow by chat does not.
Every plan includes a monthly allotment of credits, and you can buy top-ups if you need more. Your current balance is visible in two places:
- In the **flow editor** while you work with the assistant.
- In **Settings -> Billing**, alongside the rest of your plan usage.
For the exact number of credits included with your plan and your current balance, check **Settings -> Billing**. The
balance there is the source of truth for your workspace.
## Tips for better results
- **Lead with your app, audience, and goal.** The more the assistant knows up front about what your app does, who it serves, and what the flow should achieve, the closer the first draft lands. Mention these in your opening description rather than waiting to be asked.
- **Generate first, then refine.** Treat the generated flow as a strong starting point. Fine-tuning copy, swapping components, and adjusting styling are quick in the editor and do not require regenerating.
- **Use edit-by-chat for small tweaks.** Reach for the chat to add a screen, reorder steps, or change a layout. It is faster than rebuilding by hand, and it does not spend a generation credit.
- **Read the plan before confirming.** The proposed plan is your chance to course-correct cheaply. Adjust it before you generate rather than regenerating afterward.
## What's next
- [Flow Builder](/docs/flows/flow-builder) -- refine an AI draft with the full visual editor.
- [Publishing](/docs/flows/publishing) -- take a draft live once you are happy with it.
- [Personalization](/docs/personalization) -- make the flow dynamic with user data and multiple languages.
- [Audience & Targeting](/docs/audience) -- decide who sees the flow and when.
---
# Animations
Source: https://www.setgreet.com/docs/components/animations
> Add enter, exit, hover, and loop animations to your components.
# Animations
Animations bring your in-app experiences to life. Setgreet supports a full animation system with enter, exit, hover, tap, and loop animations, all rendered natively on iOS and Android. You can apply preset animations to any component or use Lottie JSON for complex custom motion graphics.
## Enter Animations
Enter animations play when a component first appears on screen. They control how elements transition from invisible to visible.
### Presets
| Preset | Description |
| ------------ | ---------------------------------------------------- |
| `fade` | Fades in from transparent to opaque. |
| `fadeIn` | Alias for fade. |
| `slideUp` | Slides in from below the component's final position. |
| `slideDown` | Slides in from above. |
| `slideLeft` | Slides in from the right. |
| `slideRight` | Slides in from the left. |
| `scale` | Scales up from a smaller size. |
| `scaleIn` | Alias for scale. |
| `bounce` | Enters with a bouncing effect. |
| `pulse` | Fades in with a pulsing size change. |
| `shake` | Enters with a horizontal shake. |
| `flip` | Flips in along the Y axis. |
| `rotate` | Rotates in from a turned angle. |
| `none` | No animation. The component appears instantly. |
### Triggers
Enter animations can be triggered by different events:
| Trigger | Description |
| ----------- | --------------------------------------------------------------------------------- |
| `onMount` | Plays immediately when the screen loads. This is the default. |
| `onVisible` | Plays when the component scrolls into the viewport. Useful in scrollable screens. |
### Configuration
| Property | Type | Default | Description |
| -------- | ------ | ------- | ---------------------------------------------------------------------------------- |
| Preset | select | none | The animation preset to apply. |
| Duration | number | 300ms | How long the animation takes to complete. |
| Delay | number | 0ms | Time to wait before the animation starts. Use this to stagger multiple components. |
| Easing | select | ease | The timing curve (see Easing section below). |
Stagger multiple enter animations by setting increasing delay values on each component. For example, a title with 0ms
delay, a subtitle with 100ms, and a button with 200ms creates a cascading entrance effect.
---
## Exit Animations
Exit animations play when a component is removed from the screen, typically during a screen transition. They control how elements leave the viewport.
### Presets
Exit animations use the same preset library as enter animations. Common pairings:
| Enter | Exit | Effect |
| ----------- | ------------ | ------------------------------------------ |
| `fadeIn` | `fadeOut` | Gentle fade transition. |
| `slideUp` | `slideDown` | Content slides up to enter, down to leave. |
| `scaleIn` | `scaleOut` | Zooms in to appear, shrinks to disappear. |
| `slideLeft` | `slideRight` | Horizontal slide transitions. |
### Configuration
Exit animations share the same duration, delay, and easing properties as enter animations.
---
## Hover Animations
Hover animations provide visual feedback when a user hovers over a component (on devices that support hover, such as iPads with a pointer or desktop web previews).
### Properties
| Property | Type | Default | Description |
| -------- | ------ | ------- | ------------------------------ |
| Preset | select | none | The animation preset. |
| Scale | number | 1.05 | Scale factor applied on hover. |
| Opacity | number | 1 | Opacity applied on hover. |
| Duration | number | 300ms | Transition duration. |
| Easing | select | ease | Timing curve. |
---
## Tap Animations
Tap animations play when a user taps on a component, providing tactile feedback. They are most commonly used on buttons and interactive elements.
### Properties
| Property | Type | Default | Description |
| -------- | ------ | ------- | ---------------------------------------------------------------- |
| Preset | select | none | The animation preset. |
| Scale | number | 0.95 | Scale factor on tap (values below 1 create a "press in" effect). |
| Opacity | number | 1 | Opacity on tap. |
| Duration | number | 300ms | Animation duration. |
| Easing | select | ease | Timing curve. |
---
## Loop Animations
Loop animations run continuously, repeating for a set number of iterations or indefinitely. They are ideal for drawing attention to important elements.
### Properties
| Property | Type | Default | Description |
| ---------- | ------ | ------- | ------------------------------------------------------------------------------- |
| Preset | select | none | The animation preset. `pulse` and `bounce` are the most common loop animations. |
| Duration | number | 300ms | Duration of one animation cycle. |
| Iterations | number | -1 | Number of repetitions. -1 means infinite. |
| Direction | select | normal | Playback direction: `normal`, `reverse`, `alternate` (forward then backward). |
| Easing | select | ease | Timing curve. |
### Common patterns
- **Pulsing badge**: apply a `pulse` loop to a "NEW" badge to draw the eye
- **Bouncing arrow**: a `bounce` loop on a down-arrow icon to indicate scrollable content
- **Attention button**: a subtle `pulse` on a CTA button to encourage taps
---
## Easing
Easing controls the acceleration curve of an animation, whether it starts slow, ends slow, or maintains constant speed.
| Easing | Description |
| ----------- | ----------------------------------------------------------------------------------------------- |
| `linear` | Constant speed from start to finish. Mechanical feel. |
| `ease` | Starts slow, accelerates, then decelerates. The most natural-feeling default. |
| `easeIn` | Starts slow, accelerates toward the end. Good for exit animations. |
| `easeOut` | Starts fast, decelerates toward the end. Good for enter animations. |
| `easeInOut` | Starts and ends slow with acceleration in the middle. Smooth and polished. |
| `spring` | Physics-based spring dynamics with natural overshoot and settle. Creates the most organic feel. |
---
## Duration and Delay
### Duration
Duration controls how long an animation takes to complete, in milliseconds. The default is 300ms.
| Range | Feel |
| --------- | ----------------------------------------------------------------------- |
| 100-200ms | Snappy, immediate. Good for tap feedback and micro-interactions. |
| 200-400ms | Balanced. The sweet spot for most enter/exit animations. |
| 400-800ms | Deliberate, attention-drawing. Good for hero elements and celebrations. |
| 800ms+ | Dramatic. Use sparingly for special moments. |
### Delay
Delay sets how long to wait before an animation begins, in milliseconds. The default is 0 (immediate).
Delay is most useful for **staggering**: creating a cascading sequence where components animate in one after another. Set incrementing delay values on sibling components to achieve this effect.
---
## Lottie Animations
For animations that go beyond what presets offer, the [Lottie component](/docs/components/media#lottie) renders JSON-based animations created in After Effects and exported via Bodymovin.
Lottie animations offer:
- Frame-by-frame control with start and end frame properties
- Adjustable playback speed
- Loop and autoplay configuration
- Resolution-independent rendering at any size
Use Lottie when you need branded illustrations, complex multi-element animations, or animations that match a specific design specification.
---
# Display Components
Source: https://www.setgreet.com/docs/components/display
> Add badges, tooltips, cards, and other display elements.
# Display Components
Display components provide supporting visual elements that enhance your screens without requiring user interaction. Use them to add labels, contextual hints, embedded web content, visual grouping, and spacing.
## Badge
A compact label element for highlighting status, categories, or tags. Badges are commonly used to draw attention to new features, labels, or metadata.
### Properties
| Property | Type | Description |
| ---------------- | ------- | ----------------------------------------------------- |
| Text | text | The badge label text. Localizable. |
| Background Color | color | Badge fill color. |
| Text Color | color | Label text color. |
| Font Size | number | Label font size. |
| Font Weight | select | Label font weight. |
| Border Radius | number | Corner rounding. Default: fully rounded (pill shape). |
| Padding | spacing | Internal spacing. |
| Border Width | number | Optional border stroke width. |
| Border Color | color | Border stroke color. |
### Use cases
- "NEW" or "BETA" labels on feature announcements
- Category tags in onboarding topic selectors
- Status indicators ("Pro", "Free Trial", "Recommended")
---
## Tooltip
A contextual hint component that displays explanatory text near a target element. Tooltips help users understand what a feature does or why something is being shown.
### Properties
| Property | Type | Description |
| ---------------- | ------- | ------------------------------------------------- |
| Text | text | Tooltip content text. Localizable. |
| Background Color | color | Tooltip background. |
| Text Color | color | Tooltip text color. |
| Arrow Position | select | Where the arrow points: top, bottom, left, right. |
| Font Size | number | Text size. |
| Border Radius | number | Corner rounding. |
| Max Width | number | Maximum tooltip width before text wraps. |
| Padding | spacing | Internal spacing. |
---
## WebView
An embedded web content component that renders a URL within your flow screen. WebView is useful for displaying terms of service, privacy policies, external forms, or any web-based content that does not need to be rebuilt natively.
### Properties
| Property | Type | Description |
| --------------- | ------- | --------------------------------------------------- |
| URL | url | The web page to load. |
| Width | size | Component width. |
| Height | size | Component height. |
| Allow Scrolling | boolean | Whether the embedded page can scroll independently. |
| Show Loading | boolean | Display a loading indicator while the page loads. |
WebView content is rendered inside a platform webview, unlike all other Setgreet components which are fully native.
Use WebView sparingly and only when native components cannot achieve the desired result.
---
## Card
A container component with visual styling (background, border, and shadow) that groups related content together. Cards provide a clear visual boundary around a set of components.
### Properties
| Property | Type | Description |
| ---------------- | ------- | ------------------------------------------------------- |
| Background Color | color | Card fill color. |
| Border Radius | number | Corner rounding. |
| Border Width | number | Border stroke width. |
| Border Color | color | Border stroke color. |
| Shadow | shadow | Box shadow configuration (offset, blur, spread, color). |
| Padding | spacing | Internal spacing around child components. |
| Margin | spacing | External spacing. |
| Width | size | Card width. |
Cards can contain any other components as children, making them a versatile way to visually group text, images, buttons, and input elements.
---
## Divider
A horizontal line that visually separates sections of content within a screen.
### Properties
| Property | Type | Description |
| --------- | ------- | ------------------------------------ |
| Color | color | Line color. Default: light gray. |
| Thickness | number | Line height in pixels. Default: 1. |
| Width | size | Line width. Default: 100%. |
| Margin | spacing | Spacing above and below the divider. |
| Opacity | number | Line transparency (0 to 1). |
---
## Spacer
A flexible spacing element that creates vertical or horizontal gaps between components. Spacers are essential for controlling layout when you need precise spacing that padding and margin alone cannot achieve.
### Properties
| Property | Type | Description |
| ---------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
| Height | size | Fixed height in pixels, or "auto". |
| Fill Space | boolean | When enabled, the spacer expands to fill available space. Useful for pushing content to the bottom of the screen. |
### Layout behavior
A spacer with `fillSpace` enabled expands to push subsequent components toward the bottom of the screen. This is how you create layouts where buttons are pinned to the bottom.
---
# Feedback Components
Source: https://www.setgreet.com/docs/components/feedback
> Collect user feedback with ratings, NPS scores, and emoji reactions.
# Feedback Components
Feedback components are purpose-built for collecting user sentiment. Each component captures a specific type of response (star ratings, NPS scores, emoji reactions, or binary thumbs up/down) and automatically tracks the value in your flow analytics.
All feedback values are recorded as analytics events with the component type and submitted value. You can view
aggregated feedback data in the flow analytics dashboard and forward events to external tools like Amplitude,
Mixpanel, or Segment.
## Rating
A star-based rating component for collecting satisfaction scores on a 1-to-5 scale. Stars are tappable and fill progressively as the user selects a value.
### Properties
| Property | Type | Description |
| -------------- | ------- | ------------------------------------------------ |
| Variable Name | text | Key used to store the rating value (1-5). |
| Max Stars | number | Number of stars to display. Default: 5. |
| Default Value | number | Pre-selected rating (0 for none). |
| Star Size | number | Size of each star in pixels. |
| Active Color | color | Color of filled (selected) stars. Default: gold. |
| Inactive Color | color | Color of empty (unselected) stars. |
| Allow Half | boolean | Enable half-star selections. |
| Required | boolean | Whether a rating is required to proceed. |
| Label | text | Optional label displayed above the stars. |
### Branching
Rating values can drive conditional branching in your flow graph. For example:
- Ratings of 4-5 lead to a "Thank you" screen with an app store review prompt
- Ratings of 1-3 lead to a follow-up screen asking what went wrong
This pattern lets you route happy users toward reviews and dissatisfied users toward support, all within a single flow.
### Analytics
Each rating submission is tracked as a `component_rating` event with the selected value. In the analytics dashboard, you can see the distribution of ratings across all users who encountered the flow.
---
## NPS
A Net Promoter Score component that presents a 0-to-10 scale for measuring user loyalty. The NPS component renders as a horizontal row of numbered buttons, following the standard NPS survey format.
### Properties
| Property | Type | Description |
| -------------- | ------- | ----------------------------------------------------------------------- |
| Variable Name | text | Key used to store the NPS score (0-10). |
| Required | boolean | Whether a score is required. |
| Low Label | text | Label at the left end of the scale (e.g., "Not likely"). Localizable. |
| High Label | text | Label at the right end of the scale (e.g., "Very likely"). Localizable. |
| Active Color | color | Color of the selected score button. |
| Inactive Color | color | Color of unselected score buttons. |
| Text Color | color | Number text color. |
| Size | select | Button size: small, medium, large. |
### Branching
NPS scores naturally segment into three groups, and you can branch your flow accordingly:
- **Promoters (9-10)**: route to a referral or review prompt
- **Passives (7-8)**: route to a "what could be better?" follow-up
- **Detractors (0-6)**: route to a detailed feedback form or support offer
Create a branch node in the flow graph and map score ranges to different target screens.
### Analytics
NPS submissions are tracked as `component_nps` events. The analytics dashboard calculates your overall NPS score (% Promoters minus % Detractors) and shows the score distribution over time.
---
## Emoji Feedback
An emoji-based feedback component that lets users express their sentiment by selecting from a row of emoji faces, from very unhappy to very happy.
### Properties
| Property | Type | Description |
| ------------------- | ------- | --------------------------------------------------------------------------------------- |
| Variable Name | text | Key used to store the selected emoji value. |
| Options | array | The set of emoji options with values and labels. Defaults to a 5-point sentiment scale. |
| Required | boolean | Whether a selection is required. |
| Size | number | Emoji display size in pixels. |
| Show Labels | boolean | Display text labels beneath each emoji. |
| Label Color | color | Text color for labels. |
| Selected Background | color | Background highlight for the selected emoji. |
### Use cases
- Quick sentiment check after a feature interaction
- Post-support satisfaction measurement
- Lightweight alternative to star ratings for casual contexts
---
## Thumbs
A binary feedback component presenting thumbs-up and thumbs-down buttons. This is the simplest feedback mechanism, ideal when you need a quick positive/negative signal.
### Properties
| Property | Type | Description |
| ------------------- | ------- | ------------------------------------------------ |
| Variable Name | text | Key used to store the response ("up" or "down"). |
| Required | boolean | Whether a selection is required. |
| Up Color | color | Thumbs-up icon color. |
| Down Color | color | Thumbs-down icon color. |
| Size | number | Icon size in pixels. |
| Show Labels | boolean | Display "Yes"/"No" labels beneath the icons. |
| Up Label | text | Custom label for thumbs up. Localizable. |
| Down Label | text | Custom label for thumbs down. Localizable. |
| Selected Background | color | Background highlight for the selected thumb. |
### Use cases
- "Was this helpful?" after a tooltip or announcement
- Quick feedback on a new feature
- Binary qualification questions ("Are you interested in X?")
### Branching
Like other feedback components, thumbs responses can drive conditional branching. Route thumbs-up users to one screen and thumbs-down users to another for targeted follow-up.
---
# Components
Source: https://www.setgreet.com/docs/components
> Reference for every native component available for building native flows.
# Components
Setgreet provides a full library of native UI components for building in-app experiences. Every component is rendered natively on iOS and Android, no webviews, no performance compromises. What you configure in the dashboard is exactly what your users see on their devices.
## Component categories
Components are organized into categories based on their purpose:
| Category | Components | Purpose |
| --------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------- |
| [Text & Buttons](/docs/components/text-and-buttons) | Text, Button | Core content and interaction elements |
| [Media](/docs/components/media) | Image, Video, Lottie, Carousel | Visual and animated content |
| [Input](/docs/components/input) | TextInput, Dropdown, Checkbox, RadioGroup, MultiSelect, Slider, DatePicker | Collect user responses |
| [Feedback](/docs/components/feedback) | Rating, NPS, Emoji Feedback, Thumbs | Gather user sentiment |
| [Display](/docs/components/display) | Badge, Tooltip, WebView, Card, Divider, Spacer | Supporting visual elements |
| [Metrics](/docs/components/metrics) | Countdown Timer, Progress Bar, Progress Indicator | Track progress and create urgency |
| [Layout](/docs/components/layout) | Container, Stack, Presentation Styles | Structure and present screens |
## How components work
Every flow in Setgreet is made up of **screens**, and every screen is made up of **components**. You build screens by adding components from the component toolbox and configuring their properties in the properties panel.
### Adding components
Select a component from the toolbox on the left side of the flow builder. The component is added to the currently selected screen. Drag components to reorder them within the screen, or nest them inside container components to create complex layouts.
### Configuring properties
Each component has a set of configurable properties organized into sections:
- **Content**: the data the component displays (text, image URL, options list)
- **Layout**: width, height, padding, margin
- **Appearance**: colors, fonts, borders, shadows, border radius
Properties are edited in the right-side panel when a component is selected.
## Flexbox layout system
Setgreet uses a **flexbox layout model** for all component positioning. Components flow vertically by default within a screen, and you control their arrangement using standard flexbox properties on container components:
- **Direction**: row or column
- **Alignment**: how children align on the cross axis (start, center, end, stretch)
- **Justify**: how children distribute on the main axis (start, center, end, space-between, space-around)
- **Gap**: spacing between child components
- **Padding**: internal spacing within the container
This approach ensures layouts adapt naturally to different screen sizes without manual positioning.
Setgreet does not use absolute positioning for component layout. All components are arranged using flexbox, which
guarantees consistent rendering across device sizes and orientations.
## Native rendering
Components are rendered using native platform UI elements: SwiftUI on iOS and Jetpack Compose on Android. This means:
- Smooth 60fps animations and transitions
- Consistent behavior with the rest of your app
- No web rendering overhead
- Full support for platform accessibility features
## Variable bindings
Most component properties support [variable bindings](/docs/personalization/variable-bindings), letting you display dynamic content based on user attributes or event data. For example, a text component can greet users by name using `{{user.firstName}}`.
## Animations
Components can be configured with [enter, exit, hover, tap, and loop animations](/docs/components/animations) to create engaging, polished experiences.
---
# Input Components
Source: https://www.setgreet.com/docs/components/input
> Collect user input with text fields, dropdowns, checkboxes, and more.
# Input Components
Input components let you collect information from users directly within your flows. Every input component supports a `variableName` property that determines how the collected value is stored and referenced, both within the current flow and as a user attribute for future targeting.
All input values collected through these components are available in your [flow
analytics](/docs/personalization/form-data) and can be saved as user attributes for segmentation and targeting.
## TextInput
A text field for collecting free-form text responses. Supports single-line and multi-line modes.
### Properties
| Property | Type | Description |
| ---------------- | ------- | --------------------------------------------------------- |
| Placeholder | text | Ghost text shown when the field is empty. |
| Variable Name | text | Key used to store the entered value. |
| Multiline | boolean | Enable multi-line input (textarea). Default: false. |
| Max Length | number | Maximum character count. |
| Required | boolean | Whether the field must be filled before proceeding. |
| Keyboard Type | select | Input keyboard hint: default, email, numeric, phone, url. |
| Font Size | number | Text size within the field. |
| Text Color | color | Input text color. |
| Background Color | color | Field background color. |
| Border Radius | number | Corner rounding. |
| Border Color | color | Field border color. |
| Padding | spacing | Internal spacing. |
### Use cases
- Collecting a user's name during onboarding
- Open-ended feedback after a feature interaction
- Email address capture for lead generation
---
## Dropdown
A selection component that presents a list of options in a native dropdown/picker interface.
### Properties
| Property | Type | Description |
| ---------------- | ------- | ---------------------------------------------------------------- |
| Options | array | List of selectable options, each with a value and display label. |
| Placeholder | text | Text shown when no option is selected. |
| Variable Name | text | Key used to store the selected value. |
| Required | boolean | Whether a selection is required. |
| Background Color | color | Dropdown background. |
| Text Color | color | Option text color. |
| Border Radius | number | Corner rounding. |
Option labels are localizable. You can provide translated labels for each language your flow supports.
---
## Checkbox
A multi-selection component where users can toggle one or more options on or off.
### Properties
| Property | Type | Description |
| ---------------- | ------- | ----------------------------------------------- |
| Options | array | List of checkbox options with value and label. |
| Variable Name | text | Key used to store the array of selected values. |
| Required | boolean | Whether at least one option must be selected. |
| Min Selections | number | Minimum number of required selections. |
| Max Selections | number | Maximum allowed selections. |
| Check Color | color | Color of the checked indicator. |
| Label Color | color | Text color for option labels. |
| Layout Direction | select | Vertical or horizontal arrangement. |
### Use cases
- Multi-topic interest selection during onboarding
- Feature preference gathering
- Terms and conditions acknowledgment
---
## RadioGroup
A single-selection component that presents a list of mutually exclusive options. RadioGroup is one of the most powerful input components because it supports **conditional branching**: each option can route the user to a different screen.
### Properties
| Property | Type | Description |
| ---------------- | ------- | ------------------------------------------- |
| Options | array | List of radio options with value and label. |
| Variable Name | text | Key used to store the selected value. |
| Required | boolean | Whether a selection is required. |
| Selected Color | color | Color of the selected radio indicator. |
| Label Color | color | Text color for option labels. |
| Layout Direction | select | Vertical or horizontal arrangement. |
### Branching
RadioGroup selections can drive the flow path. In the flow graph editor, create a **branch node** connected to the screen containing the RadioGroup. Map each option value to a target screen:
- Option "Beginner" leads to Screen A (introductory content)
- Option "Advanced" leads to Screen B (power-user features)
- Option "Just browsing" leads to Screen C (quick tour)
This creates genuinely personalized journeys without requiring separate flows.
---
## Segmented Control
A single-select control with two layouts. The default **segmented** layout is a compact tab strip; the **stacked** layout renders each option as a full-width card with an icon, title, and description, ideal for goal pickers and onboarding choices.
### Properties
| Property | Type | Description |
| ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Options | text | One option per line as `value\|label`, or `value\|label\|description\|icon` for stacked cards. |
| Layout | select | Segmented (tab strip) or Stacked (full-width cards). |
| Save to Attribute | text | User attribute that stores the selected value. |
| Default Value | text | Value of the initially selected option. Leave empty for no pre-selection. |
| Required | boolean | Block the screen's Continue button until an option is selected. Only applies to the stacked layout (a tab strip always has one active tab). |
| Selected Color | color | Selected segment color (segmented layout). |
| Track Color | color | Background track color (segmented layout). |
| Text Color | color | Option label color. |
The stacked layout adds card styling: Card Background, Card Border, Selected Card Background, and Selected Card Border.
### Use cases
- "What do you want to do?" goal selection
- Plan or billing-period toggles (Monthly / Yearly)
- Unit or preference switches (lb / kg)
---
## MultiSelect
A chip-style multi-selection component where users pick from a set of options displayed as tappable chips or tags.
### Properties
| Property | Type | Description |
| ------------------------ | ------- | ------------------------------------------------ |
| Options | array | List of selectable options with value and label. |
| Variable Name | text | Key used to store the array of selected values. |
| Required | boolean | Whether at least one chip must be selected. |
| Min Selections | number | Minimum selections required. |
| Max Selections | number | Maximum selections allowed. |
| Chip Background | color | Default chip background color. |
| Chip Selected Background | color | Background color when selected. |
| Chip Text Color | color | Label color. |
| Chip Border Radius | number | Corner rounding for chips. |
| Gap | number | Spacing between chips. |
### Use cases
- "What are you interested in?" topic selectors
- Feature discovery preferences
- Multi-category filtering
---
## Slider
A numeric range slider for collecting a value within a defined minimum and maximum.
### Properties
| Property | Type | Description |
| ------------------ | ------- | ----------------------------------------------- |
| Variable Name | text | Key used to store the selected value. |
| Min Value | number | Minimum slider value. Default: 0. |
| Max Value | number | Maximum slider value. Default: 100. |
| Step | number | Increment step size. Default: 1. |
| Default Value | number | Initial slider position. |
| Show Value | boolean | Display the current value alongside the slider. |
| Track Color | color | Color of the slider track. |
| Active Track Color | color | Color of the filled portion of the track. |
| Thumb Color | color | Color of the draggable thumb. |
### Use cases
- Budget or price range selection
- Satisfaction level measurement
- Frequency or intensity preferences
---
## Stepper
A discrete number stepper: a label and a minus / value / plus control. The minus and plus buttons step the value by a fixed amount between a minimum and maximum, and each disables at its bound. Use it when a precise whole number matters more than a quick drag, such as age, height, or quantity.
### Properties
| Property | Type | Description |
| ----------------- | ------- | -------------------------------------------------- |
| Label | text | Text shown to the left of the control. |
| Unit | text | Suffix appended after the value (e.g. yr, lb, kg). |
| Min | number | Minimum value. Default: 0. |
| Max | number | Maximum value. Default: 100. |
| Step | number | Increment per tap. Default: 1. |
| Default Value | number | Initial value, clamped into the min / max range. |
| Save to Attribute | text | User attribute that stores the selected value. |
| Disabled | boolean | Render the control as read-only. |
| Text Color | color | Label and value color. |
| Button Background | color | Background of the minus / plus buttons. |
| Button Icon Color | color | Color of the minus / plus glyphs. |
### Use cases
- Age, height, or weight during onboarding
- Quantity selection
- Goal targets that need an exact number
---
## DatePicker
A date selection component that opens the native platform date picker.
### Properties
| Property | Type | Description |
| ---------------- | ------ | ---------------------------------------------- |
| Variable Name | text | Key used to store the selected date. |
| Placeholder | text | Text shown before a date is selected. |
| Min Date | text | Earliest selectable date. |
| Max Date | text | Latest selectable date. |
| Date Format | select | Display format (e.g., MM/DD/YYYY, DD.MM.YYYY). |
| Background Color | color | Picker background. |
| Text Color | color | Date text color. |
| Border Radius | number | Corner rounding. |
### Use cases
- Birthday collection during onboarding
- Event scheduling within flows
- Subscription or trial date preferences
---
# Layout & Presentation
Source: https://www.setgreet.com/docs/components/layout
> Control layout structure and how flows appear on screen.
# Layout & Presentation
Layout components control how child components are arranged within a screen, and presentation styles determine how the entire screen appears on the user's device. Together, they give you full control over the structure and appearance of every in-app experience.
## Container
The Container is the fundamental layout component. It groups child components together and arranges them using flexbox properties. Every screen has an implicit root container, and you can nest additional containers to create complex layouts.
### Properties
| Property | Type | Description |
| ---------------- | ------- | -------------------------------------------------------------------------------------- |
| Direction | select | **Column** (vertical, default) or **Row** (horizontal). |
| Justify Content | select | Main-axis distribution: start, center, end, space-between, space-around, space-evenly. |
| Align Items | select | Cross-axis alignment: start, center, end, stretch, baseline. |
| Gap | number | Spacing between child components in pixels. |
| Padding | spacing | Internal spacing (top, right, bottom, left). |
| Margin | spacing | External spacing. |
| Width | size | Container width. Pixel value, percentage, or "auto". |
| Height | size | Container height. |
| Background Color | color | Container fill color. |
| Border Radius | number | Corner rounding. |
| Border Width | number | Border stroke width. |
| Border Color | color | Border stroke color. |
| Overflow | select | How overflowing content is handled: visible, hidden, scroll. |
| Flex Wrap | select | Whether children wrap to the next line: nowrap, wrap. |
### Common layout patterns
**Header with pinned footer:**
Use a column container for the full screen. Place content at the top, add a spacer with `fillSpace` enabled, then place buttons at the bottom. The spacer expands to push the buttons to the bottom edge.
**Horizontal button row:**
Wrap two or more buttons in a row container with `gap` spacing. Set each button to equal width for a balanced layout, or let them size to their content.
**Centered content block:**
Set a container's `justifyContent` to "center" and `alignItems` to "center" to perfectly center its children both vertically and horizontally.
---
## Stack
The Stack component is a simplified container optimized for common stacking patterns. It arranges children in a single direction with consistent spacing.
### Properties
| Property | Type | Description |
| ----------- | ------- | ------------------------------------------ |
| Direction | select | Vertical or horizontal. Default: vertical. |
| Gap | number | Spacing between children. |
| Align Items | select | Cross-axis alignment. |
| Padding | spacing | Internal spacing. |
| Width | size | Stack width. |
Stack is a convenience wrapper around Container with fewer configuration options, making it faster to set up for simple vertical or horizontal layouts.
---
## Presentation Styles
Presentation style determines how a screen is displayed on the user's device. Each screen in a flow can have its own presentation style.
### Fullscreen
The default presentation style. The screen takes over the entire device display, covering your app's UI completely. Fullscreen is appropriate for:
- Onboarding flows that require full attention
- Feature announcements with rich media
- Surveys and feedback forms
- Permission request pre-explanations
### Bottom Sheet
The screen slides up from the bottom of the device as a sheet overlay. Your app's content remains visible behind the sheet, dimmed by a configurable overlay. Bottom sheets are appropriate for:
- Quick prompts that do not need full-screen real estate
- Contextual actions related to what the user was doing
- Non-blocking announcements
- Lightweight surveys (NPS, thumbs up/down)
#### Bottom sheet configuration
| Property | Default | Description |
| ---------------------- | ------------ | ----------------------------------------------------------------------------------------------- |
| Initial Height | medium (50%) | How tall the sheet appears: **small** (30%), **medium** (50%), **large** (75%), **full** (95%). |
| Corner Radius | 0 | Rounding of the top-left and top-right corners. |
| Show Handle | true | Display a drag indicator at the top of the sheet. |
| Handle Color | #9CA3AF | Color of the drag indicator. |
| Background Color | #FFFFFF | Sheet background fill. |
| Show Overlay | true | Dim the content behind the sheet. |
| Overlay Color | #000000 | Scrim/overlay color. |
| Overlay Opacity | 0.32 | Scrim transparency (0 = invisible, 1 = opaque). |
| Draggable | true | Allow the user to drag the sheet up and down. |
| Dismiss on Overlay Tap | true | Close the sheet when the user taps outside it. |
| Dismiss on Swipe Down | true | Close the sheet when the user swipes it down. |
| Animation Duration | 300ms | How long the slide-up/slide-down animation takes. |
Bottom sheet configuration is per-screen. You can mix presentation styles within a single flow. For example, start
with a fullscreen onboarding screen, then show a bottom sheet survey at the end.
---
# Media
Source: https://www.setgreet.com/docs/components/media
> Add images, videos, Lottie animations, and carousels to your flows.
# Media
Media components bring visual richness to your in-app experiences. Display images, play videos, render Lottie animations, or build swipeable carousels, all rendered natively on each platform.
## Image
The Image component displays static images on a screen. You can upload images directly to Setgreet or reference them by URL.
### Properties
| Property | Type | Description |
| ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Source | image | Upload an image or provide a URL. |
| Content Mode | select | How the image fills its frame: **Fit** (preserves aspect ratio, may letterbox), **Fill** (stretches to fill), **Cover** (crops to fill while preserving aspect ratio). |
| Width | size | Component width. Pixel value, percentage, or "auto". |
| Height | size | Component height. Pixel value, percentage, or "auto". |
| Aspect Ratio | number | Fixed width-to-height ratio (e.g., 16/9). When set, the image scales proportionally. |
| Border Radius | number | Corner rounding in pixels. |
| Padding | spacing | Internal spacing around the image. |
| Margin | spacing | External spacing around the component. |
| Opacity | number | Transparency level (0 to 1). |
### Variable bindings
The image source URL supports [variable bindings](/docs/personalization/variable-bindings). This lets you display user-specific images, for example, a profile photo loaded from a user attribute.
Uploaded images are stored on Setgreet's CDN and served with optimized caching. For dynamic images, use a URL source
with variable bindings.
---
## Video
The Video component embeds a native video player within your flow screens. Use it for product walkthroughs, onboarding tutorials, or promotional content.
### Properties
| Property | Type | Description |
| ------------- | ------- | ------------------------------------------------------------------- |
| Source URL | url | URL to the video file (MP4 recommended). |
| Autoplay | boolean | Start playing automatically when the screen appears. Default: true. |
| Loop | boolean | Restart the video when it reaches the end. Default: false. |
| Mute | boolean | Play without audio. Default: false. |
| Show Controls | boolean | Display native playback controls (play/pause, seek). Default: true. |
| Width | size | Component width. |
| Height | size | Component height. |
| Aspect Ratio | number | Fixed width-to-height ratio. |
| Border Radius | number | Corner rounding. |
| Poster Image | image | Thumbnail displayed before the video plays. |
### Analytics
Video interactions are automatically tracked: play, pause, completion, and watch duration. These events appear in your flow analytics and can be forwarded to external analytics tools.
---
## Lottie
The Lottie component renders JSON-based animations created with After Effects and exported via Bodymovin. Lottie animations are lightweight, resolution-independent, and render smoothly on both iOS and Android.
### Properties
| Property | Type | Description |
| ----------- | ------- | ------------------------------------------------------------------------------------------------ |
| Source | url | URL to the Lottie JSON file. |
| Autoplay | boolean | Start the animation immediately. Default: true. |
| Loop | boolean | Repeat the animation continuously. Default: true. |
| Speed | number | Playback speed multiplier. Default: 1. Values above 1 speed up, below 1 slow down. |
| Start Frame | number | Frame number to begin playback from. |
| End Frame | number | Frame number to stop playback at. |
| Width | size | Pixel value, percentage, or **Fill** (100% of the available width). |
| Height | size | Pixel value, percentage, or **Auto** to derive the height from the animation's own aspect ratio. |
By default a Lottie fills the available width and sizes its height automatically
from the animation's intrinsic aspect ratio, so it stays proportional on every
screen size. Set a fixed pixel width or height to override this.
### When to use Lottie
Lottie is ideal for:
- Animated illustrations during onboarding
- Success/celebration animations after completing a task
- Loading or progress indicators with custom branding
- Attention-grabbing decorative elements
Lottie JSON files should be hosted on a CDN or uploaded to Setgreet. Keep file sizes reasonable (under 500KB) for
optimal performance on mobile devices.
---
## Carousel
The Carousel component creates a horizontally swipeable set of cards. Each card can contain its own content (images, text, buttons), making carousels effective for feature tours, product showcases, or multi-step introductions within a single screen.
### Properties
| Property | Type | Description |
| ------------------------ | ------- | ------------------------------------------------------------------------ |
| Items | array | The list of carousel cards. Each card has its own content configuration. |
| Autoplay | boolean | Automatically advance through cards. Default: false. |
| Autoplay Interval | number | Seconds between auto-advances. Default: 3. |
| Show Indicators | boolean | Display dot indicators below the carousel. Default: true. |
| Indicator Color | color | Active indicator dot color. |
| Indicator Inactive Color | color | Inactive indicator dot color. |
| Loop | boolean | Return to the first card after the last. Default: true. |
| Width | size | Carousel width. |
| Height | size | Carousel height. |
| Gap | number | Spacing between cards in pixels. |
### Analytics
Carousel swipe interactions are tracked automatically. You can see which cards users viewed, how far they swiped, and which card they stopped on in your flow analytics.
---
# Metric Components
Source: https://www.setgreet.com/docs/components/metrics
> Add countdown timers, progress bars, and step indicators.
# Metric Components
Metric components communicate progress, urgency, and status to users. Use them to drive action with countdown timers, show how far along a user is with progress bars, or indicate which step of a multi-screen flow they are on.
## Countdown Timer
A timer that counts down to a target time, creating urgency for time-sensitive promotions, limited offers, or onboarding deadlines.
### Properties
| Property | Type | Description |
| ---------------- | ------- | --------------------------------------------------------------------- |
| Target Date | text | The date/time the countdown reaches zero. |
| Duration | number | Alternative to target date: countdown from a fixed number of seconds. |
| Format | select | Display format: days/hours/minutes/seconds, or a subset. |
| Show Labels | boolean | Display unit labels (e.g., "hours", "min", "sec"). |
| Label Text | text | Custom unit labels. Localizable. |
| Separator | text | Character between time units (e.g., ":" or " "). |
| Font Size | number | Digit font size. |
| Font Family | font | Typeface for the timer. |
| Font Weight | select | Digit font weight. |
| Text Color | color | Timer text color. |
| Background Color | color | Background behind each digit group. |
| Border Radius | number | Corner rounding for digit backgrounds. |
| Padding | spacing | Internal spacing. |
| Completed Text | text | Text shown when the timer reaches zero. Localizable. |
### Use cases
- Limited-time discount offers
- Trial expiration reminders
- Event or launch countdowns
- Session timeouts with visual urgency
Countdown timers continue to tick in real time on the device. If the user backgrounds and returns to your app, the
timer syncs to the correct remaining time.
---
## Progress Bar
A horizontal bar that fills to indicate progress toward a goal. Progress bars are effective for showing completion status, usage levels, or step advancement.
### Properties
| Property | Type | Description |
| ------------- | ------- | ------------------------------------------------- |
| Value | number | Current progress value. |
| Max Value | number | Maximum value (100% fill). Default: 100. |
| Show Label | boolean | Display the percentage or value text. |
| Label Format | select | "percentage" or "value" (e.g., "75%" vs "15/20"). |
| Height | number | Bar height in pixels. |
| Track Color | color | Background track color (unfilled portion). |
| Fill Color | color | Filled portion color. |
| Border Radius | number | Corner rounding for the bar. |
| Animated | boolean | Animate the fill when the value changes. |
| Width | size | Bar width. Default: 100%. |
### Variable bindings
The progress value supports [variable bindings](/docs/personalization/variable-bindings), so you can display real user data:
- Profile completion percentage from a user attribute
- Usage quota consumed this billing period
- Onboarding steps completed
---
## Progress Indicator
A step/page indicator that shows which screen the user is on within a multi-screen flow. This component is essential for onboarding flows and surveys where users need to understand how many steps remain.
### Properties
| Property | Type | Description |
| --------------- | ------ | ---------------------------------------------- |
| Total Steps | number | Total number of steps/screens in the sequence. |
| Current Step | number | The currently active step (1-based). |
| Style | select | Visual style: dots, bars, numbers. |
| Active Color | color | Color of the current step indicator. |
| Inactive Color | color | Color of remaining step indicators. |
| Completed Color | color | Color of completed step indicators. |
| Size | number | Indicator element size. |
| Gap | number | Spacing between indicator elements. |
### Automatic step tracking
When placed in a flow, the progress indicator can automatically track the current screen position. The `currentStep` value updates as users navigate between screens, so you do not need to manually set it for each screen.
### Use cases
- Onboarding flow step tracker (e.g., "Step 2 of 5")
- Survey progress indication
- Multi-page form completion status
---
# Styling Components
Source: https://www.setgreet.com/docs/components/styling
> Customize the visual appearance of every component.
# Styling Components
Every component in Setgreet supports a comprehensive set of styling properties. You configure these in the properties panel when a component is selected in the flow builder. Styles are applied per-component, giving you precise control over the visual appearance of every element.
## Colors
Color properties accept hex values (e.g., `#FF5733`) or rgba values (e.g., `rgba(255, 87, 51, 0.8)`) for transparency control.
| Property | Available on | Description |
| ---------------- | --------------------------- | ---------------------------------------- |
| Background Color | Most components | Fill color behind the component content. |
| Text Color | Text, Button, Badge, inputs | Color of text content. |
| Border Color | Most components | Stroke color of the component border. |
| Active Color | Rating, NPS, Progress | Color for the active/selected state. |
| Inactive Color | Rating, NPS, Progress | Color for the inactive/unselected state. |
### Opacity
All components support an **opacity** property (0 to 1) that controls overall transparency. An opacity of 0 makes the component fully transparent, while 1 is fully opaque. This is independent of any alpha channel in color values.
---
## Typography
Text styling properties are available on any component that displays text: Text, Button, Badge, input labels, and more.
| Property | Type | Description |
| --------------- | ----------- | ------------------------------------------------------------------------------------------------------ |
| Font Family | font picker | Select from available typefaces. Default: Inter. The font picker includes common web and system fonts. |
| Font Size | number | Size in points. Range: 8-128. |
| Font Weight | select | Thin, Extra Light, Light, Regular, Medium, Semi Bold, Bold, Extra Bold, Black. |
| Line Height | number | Multiplier that controls vertical spacing between lines. |
| Letter Spacing | number | Additional horizontal spacing between characters in pixels. |
| Text Alignment | select | Left, center, or right alignment of text within its container. |
| Text Decoration | select | None, underline, strikethrough. |
| Text Transform | select | None, uppercase, lowercase, capitalize. |
Fonts are rendered using the native platform type system. If a specified font is not available on a device, the
platform falls back to the default system font.
---
## Borders
Border properties create visual outlines around components.
| Property | Type | Description |
| ------------- | ------ | ---------------------------------------------------------------------- |
| Border Width | number | Stroke width in pixels. Set to 0 for no border. |
| Border Color | color | Stroke color (hex or rgba). |
| Border Radius | number | Corner rounding in pixels. A high value creates pill or circle shapes. |
Border radius can be set uniformly (all four corners) or per-corner on some components (e.g., bottom sheet top corners). The container and card components support individual corner radius values for asymmetric rounding.
---
## Spacing
Spacing properties control the internal and external whitespace around a component. Setgreet uses a four-sided spacing model.
### Padding
Internal spacing between the component boundary and its content. Defined as four values: top, right, bottom, left.
Example: a button with `padding: { top: 12, right: 24, bottom: 12, left: 24 }` has more horizontal breathing room than vertical.
### Margin
External spacing between the component boundary and its siblings. Also defined as four values: top, right, bottom, left.
Margins create separation between adjacent components in the layout flow. Use margin instead of padding when you want space outside the component's visible boundary.
---
## Shadows
Shadow properties add depth to components by rendering a drop shadow beneath them. Shadows are available on Card, Container, Button, and other visual components.
| Property | Type | Description |
| ----------- | ------ | -------------------------------------------------------------------------- |
| Offset X | number | Horizontal shadow displacement in pixels. |
| Offset Y | number | Vertical shadow displacement in pixels. |
| Blur Radius | number | How much the shadow is blurred. Higher values create softer shadows. |
| Spread | number | How far the shadow extends beyond the component boundary. |
| Color | color | Shadow color (typically a semi-transparent black like `rgba(0,0,0,0.15)`). |
Shadows are rendered natively on both platforms and respect the component's border radius for accurate shadow shapes.
---
## Visibility
Components can be conditionally shown or hidden based on user data.
### Static visibility
The **visible** property (boolean) controls whether a component is rendered at all. Set it to `false` to hide a component without removing it from the screen definition. Hidden components do not take up layout space.
### Conditional visibility
Components support **visibility conditions** that evaluate at runtime based on user attributes or event properties. A component with conditions is only rendered when its conditions are met.
| Condition field | Description |
| --------------- | ---------------------------------------------------------------------- |
| Source | `userAttribute` or `eventProperty` |
| Field | The attribute or property key to evaluate (e.g., `plan`, `country`). |
| Operator | equals, notEquals, contains, greaterThan, lessThan, exists, notExists. |
| Value | The comparison value. |
Multiple conditions can be combined to create precise targeting rules for individual components within a screen.
### Use cases
- Show a "Pro" badge only to users on the Pro plan
- Display a discount banner only for users in a specific country
- Hide an advanced feature explanation for new users
---
# Text & Buttons
Source: https://www.setgreet.com/docs/components/text-and-buttons
> Add text content and interactive buttons to your screens.
# Text & Buttons
Text and button components are the foundation of every in-app experience. Use text to communicate with users, and buttons to drive actions like navigation, dismissal, or opening URLs.
## Text
The Text component displays styled text content on a screen. It is the most commonly used component and supports rich formatting and dynamic content through variable bindings.
### Properties
| Property | Type | Description |
| -------------- | -------- | ------------------------------------------------------------------------ |
| Text Content | textarea | The text to display. Supports `{{variable}}` syntax for dynamic content. |
| Font Size | number | Size in points (8-128). Default: 16. |
| Font Family | font | Typeface selection. Default: Inter. |
| Font Weight | select | Weight from thin to black. Default: regular. |
| Text Color | color | Text color in hex or rgba. Default: #000000. |
| Text Alignment | select | Left, center, or right alignment. Default: left. |
| Line Height | number | Line spacing multiplier. |
| Letter Spacing | number | Spacing between characters. |
| Width | size | Component width. Can be a pixel value, percentage, or "auto". |
| Padding | spacing | Internal spacing (top, right, bottom, left). |
| Margin | spacing | External spacing (top, right, bottom, left). |
### Variable bindings
Text content supports [variable bindings](/docs/personalization/variable-bindings) using double-curly-brace syntax. You can insert user attributes or event properties directly into your text:
```
Welcome back, {{user.firstName}}!
You have {{user.credits}} credits remaining.
```
When the bound data is unavailable, the fallback value you configure is displayed instead.
### Localization
The Text Content property is localizable. When you add translations for a screen, each text component can have its content overridden per language. See [Localization](/docs/personalization/localization) for details.
---
## Button
The Button component is the primary interactive element in Setgreet flows. Buttons trigger actions: navigating between screens, dismissing a flow, opening a URL, or requesting system permissions.
### Variants
| Variant | Use case |
| --------- | ---------------------------------------------- |
| Primary | Main call-to-action. Bold, filled background. |
| Secondary | Alternative action alongside a primary button. |
| Text | Low-emphasis action. No background, text only. |
| Success | Positive confirmation actions. |
| Danger | Destructive or warning actions. |
### Sizes
Buttons support four sizes: **Tiny**, **Small**, **Medium** (default), and **Large**. The **Full Width** option stretches the button to fill its container.
### Actions
Every button has an action that fires when tapped:
| Action | Description |
| ------------------------------- | -------------------------------------------------- |
| Next | Navigate to the next screen in the flow. |
| Previous | Navigate back to the previous screen. |
| Skip | Skip the current flow entirely. |
| Dismiss | Close the flow. |
| URL | Open an external URL in the device browser. |
| Request Notification Permission | Trigger the native notification permission dialog. |
| Request Location Permission | Trigger the native location permission dialog. |
| Request Camera Permission | Trigger the native camera permission dialog. |
| Request Review | Prompt the native app store review dialog. |
| Share | Open the native share sheet. |
| Open Settings | Open the device settings page for the app. |
| Track Event | Fire a custom analytics event. |
| Custom | Pass custom data to your app via SDK callback. |
### Appearance properties
| Property | Type | Description |
| ---------------- | ------ | ------------------------------------------------------------------------- |
| Background Color | color | Button fill color. |
| Text Color | color | Label text color. |
| Font Family | font | Typeface for the label. |
| Font Weight | select | Label font weight. |
| Border Radius | number | Corner rounding in pixels. |
| Border Width | number | Border stroke width. |
| Border Color | color | Border stroke color. |
| Icon | icon | Optional icon shown next to the label, chosen from the built-in icon set. |
| Icon Position | select | Left or right of the label text. |
### Icons
Pick a button icon from the built-in icon set in the property panel. Each icon
renders natively on every platform, so the same choice looks right on iOS and
Android, and it tints to match your label color. The set covers the icons
onboarding and call-to-action buttons use most: navigation arrows and chevrons,
common actions (check, close, plus, share, download, search, settings), status
and marketing glyphs (star, heart, bell, gift, sparkles, lock, shield, bolt),
media and contact icons (play, pause, mail, camera, calendar), and the Apple and
Google marks for sign-in buttons. Use **Icon Position** to place the icon to the
left or right of the label.
Button labels are localizable. When you add a new language to a screen, you can provide translated button text for
each locale.
### Branching
Buttons support [conditional branching](/docs/personalization/form-data). When multiple buttons are placed on a screen, each can route to a different next screen based on which one the user taps. This is configured in the flow graph editor by creating branch nodes.
---
# Create an Experiment
Source: https://www.setgreet.com/docs/experiments/create-experiment
> Set up an A/B test with variants and traffic allocation.
# Create an Experiment
This guide walks you through setting up a new experiment from start to finish.
## Prerequisites
- **Launch** plan or higher (experiments are not available on the free plan, except during a new workspace's first 14 days).
- Two or more published flows you want to compare. Each variant in an experiment points to an existing flow, so build every variant as its own flow first.
Before creating an experiment, make sure you have built all the flows you want to test. There is no "edit variant"
feature inside the experiment -- each variant must already exist as a flow.
## Step 1: Open the experiment creator
1. Navigate to **Experiments** in the sidebar.
2. Click **Create Experiment**.
3. Give the experiment a descriptive name (for example, "Onboarding CTA test - May 2026") and an optional description.
## Step 2: Add variants
Each variant maps to a flow you have already published. By default, the creator starts with two variants named "Control" and "Variant B".
For each variant:
1. Choose a name (for example, "Control" and "Variant B").
2. Select the flow that variant should use.
3. Set the traffic percentage.
You can add more variants if you want to compare more than two flows. Keep in mind that more variants require more traffic to reach statistically meaningful results.
## Step 3: Define traffic split
Set the percentage of eligible users assigned to each variant. The percentages must add up to **100%**.
| Split | When to use |
| ------------ | ----------------------------------------------------------------------------------------- |
| **50/50** | Standard A/B test with one variant. Fastest time to statistical significance. |
| **33/33/34** | Three-way test. Requires more traffic for meaningful results. |
| **80/20** | When you want to limit exposure to an experimental variant. Slower to reach significance. |
Choose your traffic split before starting the experiment. Changing the split mid-experiment invalidates your results
because the sample populations are no longer comparable.
## Step 4: Pick the primary metric
Choose one metric as the primary success indicator:
- **Completion rate** -- best for onboarding flows or multi-step experiences where you want users to finish.
- **Conversion rate** -- best when you have a specific goal event (like a purchase or feature activation) tied to the flow.
- **Dismiss rate** -- useful for testing whether a variant is less intrusive or annoying.
Only one primary metric is tracked per experiment, and it drives the lift calculation and winner recommendation.
## Step 5: Configure confidence and sample size
Two additional settings control when the experiment is considered "ready" to call a winner:
- **Confidence level** -- the statistical confidence required (default: 95%). Higher confidence means more certainty but needs more data.
- **Minimum sample size** -- the minimum number of users per variant before results are considered (default: 100).
Leave the defaults unless you have a reason to change them.
## Step 6: Review and save
Review the configuration:
- Experiment name and description
- Variants and their flow assignments
- Traffic split percentages
- Primary metric
- Confidence level and minimum sample size
Click **Save** to create the experiment in **Draft** status. The experiment does not start collecting data until you explicitly start it.
## Next steps
Once your experiment is saved, head to [Run & Monitor](/docs/experiments/run-and-monitor) to start the test and track results.
---
# Experiments
Source: https://www.setgreet.com/docs/experiments
> Run A/B tests on your flows to find what works best.
# Experiments
Experiments let you A/B test your flows to find the version that drives the best results. Instead of guessing which copy, layout, or structure works best, you can run a controlled test and let the data decide.
## How experiments work
An experiment splits eligible users into groups. Each group sees a different **flow** that you have already built, and Setgreet tracks a chosen metric for each one. You decide which flow performed best based on the results.
The basic structure:
- **Control** -- a flow you have already created (typically the current version).
- **Variant(s)** -- other flows you have already created that represent alternative approaches.
- **Traffic split** -- the percentage of eligible users assigned to each variant.
The SDK handles variant assignment automatically. Each user is consistently assigned to the same variant for the duration of the experiment, so their experience is stable.
Variants are existing flows, not copies of the control. Build each variant as a separate flow in the flow builder
before creating the experiment, then select them when configuring the experiment.
## Experiment lifecycle
| Status | Description |
| ------------- | ------------------------------------------------------------------------------------------------- |
| **Draft** | The experiment is configured but not running. You can still edit variants and traffic splits. |
| **Running** | The experiment is live. Users are being assigned to variants and metrics are being collected. |
| **Paused** | The experiment is temporarily stopped. No new users are assigned, but existing data is preserved. |
| **Completed** | The experiment has ended. Results are final and you can mark a winning variant. |
## What you can test
Experiments work at the flow level. You can test any difference between flow variants:
- Different copy or messaging
- Different screen counts or ordering
- Different component layouts
- Different images or media
- Different call-to-action buttons
## Available metrics
When creating an experiment, you choose one **primary metric** to measure success:
| Metric | Description |
| ------------------- | ------------------------------------------------------------- |
| **Completion rate** | Percentage of users who reached the final screen of the flow. |
| **Conversion rate** | Percentage of users who triggered a defined conversion goal. |
| **Dismiss rate** | Percentage of users who closed the flow before completing it. |
You can only pick one primary metric per experiment. It is the basis for calculating lift over control and for the winner recommendation.
Experiments require the **Launch** plan or higher, and are unlocked for the first 14 days on any new workspace. If
your plan no longer includes them, running experiments can still be paused or completed -- you just cannot create or
start new ones.
## Next steps
- [Create an Experiment](/docs/experiments/create-experiment) -- set up variants and traffic allocation.
- [Run & Monitor](/docs/experiments/run-and-monitor) -- track performance in real time.
- [Interpret Results](/docs/experiments/interpret-results) -- analyze outcomes and decide what to ship.
---
# Interpret Results
Source: https://www.setgreet.com/docs/experiments/interpret-results
> Analyze experiment results and decide what to ship.
# Interpret Results
After completing an experiment, the results page gives you the data you need to decide which flow to ship going forward.
## Understanding the results
The completed experiment view shows the final metrics for every variant:
- **Primary metric** -- the metric you selected when creating the experiment (completion rate, conversion rate, or dismiss rate).
- **Lift over control** -- the percentage difference between each variant and the control. A positive lift on completion/conversion rate means the variant outperformed the control. A negative lift on dismiss rate means fewer users dismissed the flow.
- **Sample size** -- the number of unique users exposed to each variant.
- **Confidence** -- the statistical confidence for the observed result.
## Winner badge
When the results show a clear leader on the primary metric and the sample size is adequate, Setgreet displays a **Winner** badge on the best-performing variant. If no variant has a clear advantage, no badge is shown -- in that case, the variants performed similarly and either can be used.
The Winner badge is a recommendation, not an automated action. Setgreet does not change your flows based on the
result. You always decide which flow to ship.
## Deciding what to ship
Each variant in an experiment is just a flow you already built, so applying the result is a manual decision:
- **If a variant wins** -- you can leave its flow as-is in your account and, if needed, unpublish or archive the losing flows to avoid confusion.
- **If the control wins** -- no action is needed. The control flow is already live.
- **If results are inconclusive** -- either flow is acceptable. Consider running a follow-up experiment with a larger sample or a more distinct variant.
## Best practices for interpreting results
- **Do not end experiments early.** If a variant looks like a winner after one day, resist the urge to stop. Early results are noisy and can reverse with more data.
- **Wait for the minimum sample size.** Results below the configured sample threshold are not reliable.
- **Account for external factors.** If you ran a marketing campaign during the experiment, it may have skewed results toward users who behave differently from your typical audience.
- **Document your learnings.** Note what worked and why in the experiment description. These insights compound over time and inform future flow design.
## Running follow-up experiments
Good experimentation is iterative. After deciding on a winner:
1. Hypothesize why it won (clearer CTA? fewer screens? better imagery?).
2. Build a new flow that pushes the winning element further.
3. Run a new experiment comparing the current winner to the new variant.
Small, compounding improvements to your flows often deliver better results than occasional large redesigns.
---
# Run & Monitor
Source: https://www.setgreet.com/docs/experiments/run-and-monitor
> Monitor your experiment's performance in real time.
# Run & Monitor
Once your experiment is configured, this page explains how to start it, monitor progress, and manage it while it runs.
## Starting an experiment
1. Go to **Experiments** in the sidebar.
2. Open your draft experiment.
3. Click **Start**.
From this point, eligible users who trigger the variant flows will be assigned to a variant based on your traffic split. Assignment is persistent -- a user always sees the same variant throughout the experiment.
Experiments respect each variant flow's existing trigger conditions. Only users who match a flow's targeting rules are
eligible for that variant.
## The results dashboard
The experiment detail page shows real-time results that auto-refresh every 30 seconds. The dashboard includes:
### Metrics summary
A view of each variant's performance on the primary metric, including:
- The variant name and its assigned flow.
- The number of users exposed to the variant.
- The primary metric value.
- Lift over control (percentage difference compared to the control variant).
- Statistical confidence for the current result.
### Sample size progress
Setgreet indicates whether the minimum sample size has been reached for each variant. Until the minimum is met, results are still considered preliminary.
### Winner recommendation
Once the primary metric shows a clear leader and the sample size is adequate, a **Winner** badge is displayed on the leading variant along with the lift over control.
## Pausing an experiment
If you need to temporarily halt an experiment:
1. Click **Pause** on the experiment detail page.
2. The experiment enters the **Paused** state.
While paused:
- No new users are assigned to variants.
- All collected data is preserved.
You can resume a paused experiment by clicking **Resume**. New users will begin being assigned again.
Pausing and resuming an experiment can introduce bias if the user population changes between the pause and resume
periods. Avoid pausing unless necessary.
## Completing an experiment
When you have collected enough data and are ready to finalize the results, click **Complete**. This:
- Stops assigning new users to variants.
- Moves the experiment to the **Completed** state.
- Optionally records the winning variant for reference.
Once completed, proceed to [Interpret Results](/docs/experiments/interpret-results) to decide what to ship.
## How long should you run an experiment?
There is no universal answer, but consider:
- **Sample size** -- wait until the minimum sample size is met for every variant.
- **Effect size** -- if you expect a large difference between variants (for example, testing completely different approaches), you need fewer users. Subtle differences need larger samples.
- **Stability** -- metrics often fluctuate early on. Wait until the numbers stabilize before drawing conclusions.
---
# Figma Plugin
Source: https://www.setgreet.com/docs/figma-plugin
> Convert Figma frames into native Setgreet flows without leaving your design file.
# Figma Plugin
The Setgreet Figma plugin turns your Figma frames into a draft Setgreet flow. Select the screens you want to ship, send them to Setgreet, and pick up in the flow editor with components, styles, and image assets already wired up.
## What it does
- **Frames become screens.** Each top-level frame you select becomes one screen in a new flow.
- **Layers become components.** Text, buttons, images, containers, cards, dividers, and the rest of Setgreet's component catalog are detected from your layer tree.
- **Auto Layout becomes Flexbox.** Auto Layout direction, gap, padding, sizing, and alignment map directly to Setgreet's layout system.
- **Image fills get uploaded.** Image and complex vector fills are exported as PNGs and uploaded to Setgreet's storage.
- **Output is a draft.** The created flow lands in your dashboard as a draft so nothing goes live until you publish.
## Install the plugin
1. Open the [Setgreet plugin on the Figma Community](https://www.figma.com/community/plugin/1634348230586495787/setgreet).
2. Click **Open in...** and pick the Figma file you want to import from.
3. In Figma, find the plugin under **Plugins > Setgreet** (or right-click the canvas > **Plugins > Setgreet**).
## Sign in
The plugin signs in with the same email and password you use for the [Setgreet dashboard](https://app.setgreet.com).
1. Open **Plugins > Setgreet** in Figma.
2. Enter your Setgreet account email and password.
3. Select the **organization** and **app** to import into.
Your session persists between plugin reopens until you sign out. There is no separate API key to generate.
If you do not see your organization in the dropdown, ask your workspace admin to invite your account or check that you
are signed into the right Setgreet identity.
## Prepare your Figma file
The plugin works best when your design follows the conventions below. Frames that fall outside these conventions still import, but the plugin uses a flatter fallback or exports the layer as an image.
### Use Auto Layout
Apply **Auto Layout** to every frame you plan to import, including nested containers. Auto Layout direction, gap, padding, and alignment map one-to-one to Setgreet's Flexbox system. Frames without Auto Layout are converted by positional analysis, which is less accurate.
### Name layers meaningfully
Layer names drive component detection. A layer called `Get Started` reads better than `Frame 42`. For exact control, prefix the layer name with a type in square brackets:
| Prefix | Maps to |
| -------------- | ----------- |
| `[text]` | Text |
| `[button]` | Button |
| `[image]` | Image |
| `[container]` | Container |
| `[card]` | Card |
| `[divider]` | Divider |
| `[spacer]` | Spacer |
| `[textInput]` | Text Input |
| `[dropdown]` | Dropdown |
| `[slider]` | Slider |
| `[checkbox]` | Checkbox |
| `[radioGroup]` | Radio Group |
| `[rating]` | Star Rating |
| `[nps]` | NPS Score |
| `[carousel]` | Carousel |
| `[video]` | Video |
| `[lottie]` | Lottie |
| `[progress]` | Progress |
Prefixed layers are mapped with full confidence. Unprefixed layers go through automatic detection: text nodes always become Text, frames with text and rounded corners become Buttons, frames with image fills become Images, and so on.
The prefixes above cover the components designers reach for most often. Setgreet has more component types (date pickers, testimonials, badges, accordions, and a few others) — if you need one of those, name the layer descriptively and override its type from the mapping panel before importing.
### Design at mobile dimensions
Setgreet flows render on phone screens. Stick to a mobile frame size such as 390×844 (iPhone) or 360×800 (Android) so the imported layout maps cleanly to the device.
### Keep selections clean
- Select **top-level frames only.** Nested frames inside a selected frame become components on the same screen, not separate screens.
- Multi-frame selections create a **linear flow** in the order you selected them. Reorder screens inside the plugin before importing.
## The import flow
1. **Select frames** in Figma — one for a single-screen flow, several for a multi-screen flow.
2. **Open the plugin** at **Plugins > Setgreet**.
3. **Pick the organization and app** the flow belongs to.
4. **Review the frame list.** The plugin shows a thumbnail for each frame with its dimensions. Reorder with the arrow buttons.
5. **Click Convert.** The plugin analyzes the layer tree and shows a mapping panel.
6. **Review the component mapping.** Each detected component shows a confidence indicator:
- **Green** — high confidence, the layer name prefix or instance match was unambiguous.
- **Yellow** — medium confidence, detection used visual structure cues.
- **Red** — low confidence, the layer fell back to an image export.
Override any detection from the dropdown beside the component.
7. **Click Import.** The plugin converts the design, exports any images, uploads them to Setgreet's storage, and creates the flow.
8. **Open the flow in Setgreet.** It lands in your dashboard as a draft, ready to edit and publish.
## What gets converted
### Components
| Figma element | Setgreet component | Detection |
| ----------------------------------------- | ------------------ | --------------------------- |
| Text layer | Text | Always detected |
| Frame with text + background + rounded | Button | Small height, centered text |
| Rectangle or frame with an image fill | Image | Image fill detection |
| Line or thin rectangle | Divider | Aspect ratio |
| Empty frame with no fills and no children | Spacer | Structure |
| Auto Layout frame with children | Container | Layout detection |
| Frame with background, rounding, + shadow | Card | Multiple visual cues |
| Small frame containing a vector child | Button (icon-only) | Size and child type |
| Vector, ellipse, or complex shape | Image (PNG export) | Fallback |
The list of component types available in the mapping dropdown is loaded from your organization when you sign in. If a type is disabled for your workspace it will not appear, and tagging a layer with that type will fail at import. Ask your workspace admin if a component you need is missing.
### Styles
| Figma property | Setgreet property |
| --------------------- | -------------------------------- |
| Font family | fontFamily (see [Fonts](#fonts)) |
| Font size | fontSize (px) |
| Font weight | fontWeight (100-900) |
| Text color | textColor (hex) |
| Text alignment | textAlign (left/center/right) |
| Line height | lineHeight |
| Letter spacing | letterSpacing |
| Solid fill | backgroundColor (hex) |
| Gradient fill | gradientColors + gradientAngle |
| Image fill | Exported and uploaded |
| Stroke | borderColor + borderWidth |
| Corner radius | borderRadius (per-corner) |
| Drop shadow | shadow (color, offset, blur) |
| Opacity | opacity (0-1) |
| Auto Layout direction | direction (horizontal/vertical) |
| Auto Layout gap | gap (px) |
| Auto Layout padding | padding (top/right/bottom/left) |
| Auto Layout alignment | justifyContent + alignItems |
| Sizing — Fill | width/height: 100% |
| Sizing — Hug | width/height: auto |
| Sizing — Fixed | width/height: px value |
### Screen background
The top-level frame's fill becomes the screen background. Solid fills become a background color, gradient fills become a gradient, and image fills are exported and uploaded.
### Flow shape
A successful import creates a new flow in **draft** status with the screens connected linearly: _Start → Screen 1 → … → End_. Triggers, conditions, and branching are added in the Setgreet editor — see [Limitations](#limitations-and-roadmap) below.
## Fonts
Flows currently render in **Inter** across the editor and the iOS / Android SDKs. Whatever font you use in Figma is replaced with Inter on import — pick fonts in your design for layout and hierarchy, not for final look. Additional font families are on the roadmap.
## Tips for best results
1. **Use Auto Layout everywhere.** This is the single most impactful thing you can do for conversion accuracy.
2. **Use `[type]` prefixes** on layers you want mapped precisely — they import with full confidence.
3. **Keep designs simple.** Overlapping elements, masks, and boolean operations are flattened to images. Simple, well-structured frames convert best.
4. **Design at mobile dimensions** — 390×844 (iPhone) or 360×800 (Android).
5. **Prefer solid fills for backgrounds.** Solid and gradient fills convert perfectly. Image backgrounds work but require an upload step.
6. **Review the mapping panel** before clicking Import. The detector is good, not perfect — overriding a few components takes seconds.
7. **Use the editor to finish the flow.** Triggers, button actions, branching, and final polish all happen in Setgreet, not in Figma.
## Limitations and roadmap
The plugin creates a faithful structural import. The dynamic parts of a flow are added in the Setgreet editor after import.
Not yet supported:
- **Triggers and conditions** — every imported flow ships with no trigger rules.
- **Button actions** — buttons import without navigation. Wire up next-screen, dismiss, or URL actions in the editor.
- **Branching flows** — multi-frame selections produce a single linear path. Add conditional branches in the editor.
- **Updating existing flows** — each import creates a new flow. Adding screens to a published flow from Figma is not supported.
- **Mixed text styles** — a single text layer with mixed fonts, sizes, or colors imports with only its dominant style.
- **Complex vector shapes** — boolean operations, masks, and intricate vectors fall back to PNG export rather than being decomposed into components.
- **Custom fonts** — all text renders in Inter today. See [Fonts](#fonts) above.
If you hit a missing case, let us know which one matters most so we can prioritize.
## Troubleshooting
| Symptom | Fix |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Plugin will not open | Restart Figma desktop. If the issue persists, reinstall the plugin from the Community listing. |
| Sign-in fails | Confirm the same email and password work at [app.setgreet.com](https://app.setgreet.com). Reset your password from the dashboard. |
| "No frames selected" message | Select top-level frames on the canvas, not groups, layers inside frames, or pages. |
| Everything imports as Image | The frames likely do not use Auto Layout. Add Auto Layout to each frame or use `[type]` layer-name prefixes. |
| Some component types are missing | Your organization may have those types disabled. Ask a workspace admin to enable them. |
| Images do not upload | Check your network. The plugin uploads directly to Setgreet's storage. |
| Flow does not appear in the dashboard | Confirm you selected the right organization and app. The flow lands in **Draft** status — filter the flow list accordingly. |
| Fonts look off after import | All text currently renders in Inter regardless of the Figma font. See [Fonts](#fonts) for details. |
| Effects (blur, inner shadow) do not show | Not every Figma effect is mapped yet. Replicate the look from the Setgreet editor. |
---
# Branching
Source: https://www.setgreet.com/docs/flows/branching
> Create conditional navigation paths based on user responses.
# Branching
Branching lets you send users down different paths in a flow based on what they select on a screen. Instead of building separate flows for each audience segment, a single flow can adapt in real time using **Branch** nodes.
## How branching works
1. A screen presents a component with selectable options (e.g., a radio group asking "What is your role?").
2. The user selects an option.
3. The flow reaches a Branch node connected to that screen.
4. The Branch node inspects the user's response and routes them to the matching path.
Each option maps to an outgoing edge on the Branch node. If a user selects "Product Manager", they follow the edge labeled "Product Manager". If they select "Engineer", they follow a different edge.
## Branchable components
Not every component supports branching. The following component types produce a response that a Branch node can evaluate:
| Component | Response value |
| -------------- | ---------------------------- |
| **RadioGroup** | The selected option's value. |
| **NPS** | The numeric score (0--10). |
| **Rating** | The numeric rating value. |
| **Dropdown** | The selected option's value. |
| **Checkbox** | The set of checked values. |
| **Button** | The button's action value. |
If the screen before a Branch node does not contain a branchable component, the Branch node has nothing to evaluate.
## Setting up a Branch node
1. Open your flow in the [flow builder](/docs/flows/flow-builder).
2. Click the **+ Add Node** button on the canvas and select **Add Branch**.
3. Connect the screen that contains a branchable component to the Branch node.
4. The Branch node automatically reads the available options from the connected component.
5. For each option, draw an edge from the Branch node to the screen you want the user to see next.
Branch edges are labeled with the option value they correspond to. These labels update automatically if you rename
options in the component.
## Example: role-based onboarding
Suppose your onboarding flow asks users to choose their role with a RadioGroup that has three options: Designer, Developer, and Product Manager.
```
Start → Welcome Screen → Role Selection Screen → Branch
├── "Designer" → Design Tips Screen → End
├── "Developer" → API Intro Screen → End
└── "Product Manager" → Dashboard Tour → End
```
Each role sees a tailored follow-up screen. The rest of the flow (welcome screen, final screen) remains shared.
## Saving responses as user attributes
When a user responds to a branchable component, you can save that response as a **user attribute** for future targeting and personalization. This is handled through the response save feature:
1. The SDK captures the user's selection on the branchable component.
2. The response is saved to the user's profile as an attribute (e.g., `role = "Designer"`).
3. Future flows can use this attribute in [trigger conditions](/docs/flows/triggers) to target or exclude specific segments.
This creates a feedback loop -- responses from one flow inform the targeting of subsequent flows.
Saved response attributes overwrite any previous value for the same key. If a user completes the same flow twice and
picks a different option, the attribute updates to the latest selection.
## Branching vs. condition nodes
Both Branch nodes and Condition nodes create diverging paths, but they serve different purposes:
| | Branch node | Condition node |
| ------------ | ---------------------------------------------------------- | -------------------------------------------------------------------- |
| **Input** | User's response on the previous screen. | A logical expression you define (user attributes, event properties). |
| **Timing** | Evaluated after the user interacts. | Evaluated before the user sees the next screen. |
| **Paths** | One per option value (many). | Two: Yes or No. |
| **Use case** | Personalize the flow based on what the user just answered. | Skip or show screens based on existing user data. |
You can combine both in the same flow. For example, use a Condition node early in the flow to check if the user is on a paid plan, and later use a Branch node to split based on a survey response.
## Tips
- **Keep branches shallow** -- deeply nested branches are harder to maintain. If you have more than two or three levels of branching, consider splitting into separate flows.
- **Preview each path** -- use the [preview feature](/docs/flows/sharing-and-preview) to walk through every branch and verify the experience before publishing.
---
# Flow Builder
Source: https://www.setgreet.com/docs/flows/flow-builder
> Use the visual flow builder to design native flows.
# Flow Builder
The flow builder is the visual canvas where you design the structure and navigation of your flows. It uses a node-and-edge graph model -- you place nodes on the canvas and connect them with edges to define how users move through the experience.
## Canvas basics
The builder opens with a zoomable, pannable canvas. Use these controls to navigate:
- **Zoom** -- scroll wheel or pinch to zoom in and out.
- **Pan** -- click and drag on empty canvas space to move around.
- **Fit to view** -- click the fit button in the canvas toolbar to center all nodes in the viewport.
- **Select** -- click a node to select it. Click an edge to select it.
- **Multi-select** -- hold Shift and click, or drag a selection box around multiple nodes.
## Node types
### Start node
Every flow has exactly one Start node. It marks the entry point of the flow and can optionally hold [trigger conditions](/docs/flows/triggers) that control when the flow is shown.
### Screen node
A Screen node represents a single screen in your flow. Clicking a Screen node opens the screen editor where you add and arrange [components](/docs/components). Each Screen node references a unique screen with its own layout, background, and component tree.
### Condition node
A Condition node evaluates a logical expression and routes users down one of two paths -- **Yes** or **No**. Use condition nodes to create different experiences for different user segments without building separate flows.
Conditions can reference:
- **User attributes** -- properties you pass via `Setgreet.identifyUser()` (e.g., plan, country, signup date).
- **Event properties** -- data from tracked events (e.g., purchase amount, feature usage).
### Branch node
A Branch node routes users based on their answer to a component on the previous screen. For example, if a screen contains a radio group asking "What is your role?", the Branch node can send product managers to one screen and engineers to another.
Supported branchable components: RadioGroup, NPS, Rating, Dropdown, Checkbox, and Button.
See [Branching](/docs/flows/branching) for a full guide.
### End node
An End node terminates the flow. When a user reaches an End node, the flow is dismissed. A flow can have multiple End nodes to support different exit points.
## Working with edges
Edges are the connections between nodes. They define the navigation path through the flow.
- **Create an edge** -- hover over a node's output handle (the dot on the right side) and drag to another node's input handle.
- **Delete an edge** -- select the edge and press Delete, or right-click and choose Remove.
- **Condition edges** -- edges from Condition nodes are labeled **Yes** or **No**.
- **Branch edges** -- edges from Branch nodes are labeled with the option value they correspond to (e.g., "Product Manager", "Engineer").
## Layers panel
When you select a screen node, the **Layers** tab appears in the right-side panel (next to the Properties tab). It shows the component tree for that screen. Use it to:
- See all components in the selected screen at a glance.
- Select components by clicking them in the tree.
- Reorder components by dragging items up or down.
- Nest components inside containers by dragging them onto a container layer.
## Canvas toolbar
The toolbar at the top of the builder provides quick actions:
| Action | Description |
| --------------- | ------------------------------------------ |
| **Add Screen** | Create a new Screen node on the canvas. |
| **Undo / Redo** | Step backward and forward through edits. |
| **Preview** | Open a live preview of the flow. |
| **Publish** | Publish the current draft to make it live. |
| **Fit to View** | Center all nodes in the viewport. |
Changes in the flow builder are saved automatically as you work. You do not need to manually save -- but you do need
to **publish** to make changes visible to app users.
---
# Flows
Source: https://www.setgreet.com/docs/flows
> Understand flows, the building blocks of your app's native mobile experiences.
# Flows
A **flow** is a self-contained in-app experience made up of one or more screens. Flows are the primary building block in Setgreet -- everything your users see inside your app is delivered through a flow.
## What you can build with flows
- **Onboarding walkthroughs** -- multi-screen sequences that introduce new users to your app.
- **Feature announcements** -- single-screen modals that highlight what is new.
- **In-app surveys** -- collect feedback with NPS, rating, and free-text components.
- **Upgrade prompts** -- targeted nudges that encourage plan upgrades.
- **Checklists and progress** -- guide users through setup steps with progress indicators.
- **Contextual tooltips** -- surface help content at the right moment.
## Flow lifecycle
Every flow moves through a well-defined set of states:
### Draft
When you create a new flow, it starts as a draft. You can add screens, configure components, set up triggers, and iterate freely. Draft flows are never served to the SDK -- your app users will not see them.
### Published
Publishing a flow makes it live. The SDK fetches published flows and renders them when trigger conditions match. Once published, the flow continues to be editable through the draft system (see [Publishing Flows](/docs/flows/publishing) for details).
### Unpublished
You can unpublish a flow at any time to remove it from SDK responses without deleting it. The flow retains all its configuration and can be re-published later.
## Flow priority
When a user matches multiple flows at the same time, Setgreet uses a **priority system** to decide which flow to show. Each flow has a numeric priority value -- the flow with the highest priority wins.
Set priority from the flow settings panel in the dashboard. Use higher numbers for more important or time-sensitive flows (e.g., a critical announcement should have higher priority than a general onboarding sequence).
If two flows have the same priority, the most recently published flow takes precedence.
## Flow structure
Under the hood, a flow is a directed graph of nodes and edges:
| Node type | Purpose |
| ------------- | ------------------------------------------------------ |
| **Start** | Entry point of the flow. Can hold trigger conditions. |
| **Screen** | A visual screen with components. |
| **Condition** | Evaluates a condition and branches to different paths. |
| **Branch** | Routes users based on their response to a component. |
| **End** | Terminates the flow. |
Nodes are connected by edges that define the navigation path. The flow builder provides a visual canvas for assembling these nodes -- see [Flow Builder](/docs/flows/flow-builder) for details.
## Next steps
- [Flow Builder](/docs/flows/flow-builder) -- learn the visual canvas editor.
- [Screens](/docs/flows/screens) -- understand screen configuration.
- [Triggers](/docs/flows/triggers) -- control when flows appear.
- [Branching](/docs/flows/branching) -- create conditional paths.
- [Publishing](/docs/flows/publishing) -- manage the draft and publish lifecycle.
---
# Publishing Flows
Source: https://www.setgreet.com/docs/flows/publishing
> Manage the draft and publish lifecycle of your flows.
# Publishing Flows
Publishing controls whether a flow is visible to your app users. Setgreet uses a **draft system** that lets you edit published flows safely -- your changes stay in draft until you explicitly publish them.
## The draft system
When you create a new flow, it starts in **Draft** status. Draft flows are never served to the SDK, so your app users will not see them. You can freely add screens, configure components, set up triggers, and rearrange the flow graph without any risk.
Once a flow is published and live, the draft system becomes essential. Every edit you make to a published flow is saved to **draft fields** rather than the live version. This means:
- Your app users continue to see the last published version.
- You can make changes over multiple sessions without affecting the live experience.
- A visual indicator in the flow builder shows when a flow has unpublished changes.
## Publishing
When you are ready to push changes live, click **Publish** in the flow builder toolbar. Publishing performs a single atomic operation:
1. Draft fields (nodes, edges, start node, canvas state) are copied to the live fields.
2. Draft screen content is promoted to published.
3. The draft is cleared and the pending changes indicator resets.
After publishing, the SDK picks up the new version on its next sync. There is no additional delay -- your changes are live immediately.
Publishing is an all-or-nothing operation. You cannot publish individual screens or partial graph changes. The entire
draft is promoted at once.
## Unpublishing
You can **unpublish** a flow at any time to remove it from SDK responses. Unpublishing does not delete the flow or discard any configuration -- it simply stops the SDK from serving it.
Common reasons to unpublish:
- A campaign has ended and you want to stop showing the flow.
- You discovered an issue and need to pull the flow while you fix it.
- You want to pause a flow temporarily and re-publish it later.
To unpublish, open the flow settings and change the status from Published to Draft.
## Flow priority
When a user matches the trigger conditions of multiple published flows at the same time, Setgreet uses **priority** to decide which flow to display. Each flow has a numeric priority value -- the higher the number, the higher the priority.
Set the priority from the flow settings panel in the dashboard. Use higher numbers for time-sensitive or critical flows:
- A limited-time promotion should have higher priority than a general onboarding sequence.
- A critical bug workaround announcement should take precedence over a feature tip.
If two flows have the same priority, the most recently published flow takes precedence.
## Lifecycle summary
| Status | SDK visibility | Editable | Notes |
| ---------------------------------- | -------------------------------------- | ------------------------------------------- | ----------------------------------------------------- |
| **Draft** | Not visible. | Yes -- edits save directly to live fields. | New flows start here. |
| **Published** | Visible when trigger conditions match. | Yes -- edits save to draft fields. | Requires explicit publish to update the live version. |
| **Published with pending changes** | Visible (last published version). | Yes -- draft fields hold unpublished edits. | Publish to promote the draft. |
| **Unpublished** | Not visible. | Yes. | Retains all configuration. Can be re-published. |
## Best practices
- **Always preview before publishing** -- use the [preview feature](/docs/flows/sharing-and-preview) to walk through the flow and verify every screen, branch path, and trigger condition.
- **Use descriptive flow names** -- when multiple team members work on flows, clear names prevent accidental edits to the wrong flow.
- **Set priority intentionally** -- review priorities across all published flows to ensure the most important experience wins when multiple flows compete.
- **Unpublish instead of delete** -- if you might need a flow again, unpublish it rather than deleting it. You preserve all configuration and can re-publish instantly.
- **Coordinate with your team** -- draft edits sync live across everyone in the flow (see [Realtime Collaboration](/docs/flows/realtime-collaboration)), and presence cues show who is editing where. Still, align with teammates before publishing so a draft goes live only when everyone agrees it is ready.
---
# Realtime Collaboration
Source: https://www.setgreet.com/docs/flows/realtime-collaboration
> Edit flows together -- live changes, cursors, and presence in the flow builder.
# Realtime Collaboration
When more than one person from your organization opens the same flow, the builder works like a shared canvas. Edits appear for everyone instantly, you can see who else is in the editor, and you can watch their cursors move -- no refresh needed.
## What you see
- **Live edits** -- when a teammate changes a component, reorders screens, or adds a screen, the change shows up in your editor right away. This works for both manual edits and changes made through [Build with AI](/docs/build-with-ai).
- **Who's online** -- the editor shows an avatar for each teammate currently viewing the flow. Each person has a consistent color.
- **Live cursors** -- you see other people's cursors moving on the canvas in real time, labeled with their name.
- **Screen presence** -- when a teammate is focused on a screen, their avatar appears on that screen so you can tell who is working where.
## How concurrent edits are handled
Setgreet uses a last-write-wins model. There is no locking -- everyone can edit freely, and the most recent save is the one that sticks. In practice, conflicts are rare because the changes you and your teammates make are usually to different screens or different components, and they merge cleanly.
Presence information -- cursors, avatars, and who's-online -- is never stored. It exists only while you have the
editor open and disappears when you leave.
## Working well together
- **Split the work** -- because everyone sees the same live state, it is easy to divide a flow by screen and edit in parallel.
- **Use the presence cues** -- if you see a teammate's avatar on a screen, you both know who is editing it.
- **Build with AI alongside others** -- AI edits broadcast to everyone in the flow the same way manual edits do, so a teammate using the AI assistant won't surprise you with changes you can't see.
## Availability
Realtime collaboration is on by default. If your organization needs it turned off, an administrator can disable it. When it's off, the editor still works normally -- it simply won't live-sync or show presence, and your changes save the same way they always have.
---
# Screens
Source: https://www.setgreet.com/docs/flows/screens
> Add and configure screens within your flows.
# Screens
A **screen** is an individual page within a flow. Each screen contains a tree of components that define what the user sees and interacts with. A flow can have one screen (a simple announcement modal) or many (a multi-step onboarding sequence).
## Adding screens
To add a screen to your flow, click the **+ Add Node** button on the canvas and select **Add Screen** from the menu.
Each new screen starts empty. Click on the screen node to open the screen editor and begin adding components.
## Screen editor
The screen editor is where you build the visual content of a screen. It shows a device-sized preview in the center with a component panel on the left and a properties panel on the right.
- **Component panel** -- browse and drag components onto the screen.
- **Canvas** -- the live preview of how the screen looks on a device. Click components to select them.
- **Properties panel** -- configure the selected component's props (text content, colors, sizing, actions, etc.).
## Presentation styles
Screens support two presentation modes on mobile devices:
### Fullscreen (default)
The screen fills the entire device screen. This is the standard presentation for onboarding flows, feature tours, and any experience that should command the user's full attention.
### Bottom sheet
The screen appears as a sheet that slides up from the bottom of the device, overlaying the current app content. Bottom sheets are ideal for quick prompts, feedback requests, and contextual actions.
Bottom sheet configuration options:
| Setting | Description |
| -------------------------- | --------------------------------------------------------------------------- |
| **Corner radius** | Rounding of the top corners. |
| **Show handle** | Display a drag handle at the top of the sheet. |
| **Handle color & size** | Customize the handle appearance. |
| **Background color** | Sheet background color. |
| **Overlay** | Enable a dimmed overlay behind the sheet. Control its color and opacity. |
| **Draggable** | Allow the user to drag the sheet up or down. |
| **Dismiss on overlay tap** | Close the sheet when the user taps outside it. |
| **Dismiss on swipe down** | Close the sheet when the user swipes down. |
| **Initial height** | How tall the sheet is when it first appears: small, medium, large, or full. |
## Screen background
You can set a background color for any screen. Click on the screen in the editor and use the background color picker in the properties panel to choose a color.
## Safe area handling
Screens respect device safe areas (notch, home indicator, status bar) by default. You can configure safe area insets per screen if you need edge-to-edge content or custom padding.
The screen editor shows a realistic device frame so you can see exactly how safe areas and presentation styles will
look on a real device.
## Reordering screens
To change the order of screens in a flow, rearrange the edges on the flow builder canvas. The navigation order is determined by the graph structure, not by any fixed list order.
---
# Sharing & Preview
Source: https://www.setgreet.com/docs/flows/sharing-and-preview
> Preview flows and share them with your team before going live.
# Sharing & Preview
Before publishing a flow to your users, you can preview it yourself and share it with teammates for review. Setgreet provides several ways to validate your work without affecting the live experience.
## Previewing a flow
The flow builder includes a built-in preview mode that renders your flow exactly as it will appear on a mobile device.
To open a preview:
1. Open the flow in the flow builder.
2. Click **Preview** in the canvas toolbar.
3. The preview opens in a device-frame view showing the current draft of the flow.
The preview renders the draft version, so you see your latest changes even if the flow is already published with an older version live. You can step through each screen, interact with components, and follow branch paths to verify the experience end to end.
Preview uses the current draft data. It does not affect published content or trigger any analytics events.
## Shareable links
Shareable links let you send a flow preview to anyone -- teammates, stakeholders, or clients -- without giving them dashboard access.
### Enabling a shareable link
1. Open the flow and go to **Share Settings**.
2. Toggle **Public Link** on. A unique share URL is generated for the flow.
3. Copy the link and send it to reviewers.
The link stays stable even if you toggle sharing off and back on. The same token is reused, so bookmarks continue to work.
### Share settings
| Setting | Description |
| ------------------- | ----------------------------------------------------------------------------------------------------------------- |
| **Public Link** | Enables or disables the shareable URL. When disabled, the link returns a not-found page. |
| **Allow Copy** | When enabled, viewers can duplicate the flow into their own Setgreet organization. Requires Public Link to be on. |
| **Preview Enabled** | When enabled, viewers see an interactive preview of the flow. Requires Public Link to be on. |
Disabling the public link automatically disables both the copy and preview options.
### Regenerating the link
If you need to invalidate an existing link (e.g., after sharing it with the wrong audience), you can regenerate the share token. This creates a new URL and immediately invalidates the old one. Anyone with the old link will no longer have access.
## QR codes for mobile testing
Since flows are rendered natively on mobile devices, testing on a real phone or tablet gives you the most accurate experience. Setgreet generates a QR code so you can quickly preview the flow on your device:
1. Enable **Public Link** and **Preview Enabled** in Share Settings.
2. In the flow builder's preview panel, find the **Preview on Device** section with the QR code.
3. Scan the QR code with your phone's camera. The Setgreet app opens and renders the flow natively on your device.
This is especially useful for verifying presentation styles (fullscreen vs. bottom sheet), safe area handling, and touch interactions exactly as your users will experience them.
## Copying shared flows
When the **Allow Copy** option is enabled on a shared link, viewers can duplicate the flow into their own organization. The copy includes:
- All screens and their component configurations.
- The flow graph (nodes and edges).
- Localization content.
The copied flow is created as a new draft in the target organization. It is not linked to the original -- changes to one do not affect the other.
Copied flows do not include trigger conditions or app assignments. The recipient must configure these for their own
context.
## Tips
- **Share early and often** -- get feedback before publishing. It is much easier to iterate on a draft than to fix a live flow.
- **Test on real devices** -- the device-frame preview in the dashboard is helpful, but always verify on an actual phone before publishing. Use QR codes to make this quick.
- **Revoke access when done** -- after a review cycle, disable the public link to prevent unintended access.
- **Use copy for cross-team collaboration** -- if another team in your company wants to reuse your flow, enable copying rather than rebuilding from scratch.
---
# Templates
Source: https://www.setgreet.com/docs/flows/templates
> Start new flows from pre-built templates.
# Templates
Templates are pre-configured flow starting points that save you from building common patterns from scratch. Setgreet includes a built-in template library with professionally designed templates you can use as a starting point.
## Choosing a template
When you click **Create Flow** from the Flows page, you are taken to the template selection screen. All available templates are displayed as phone-sized previews showing how the flow will look on a device.
Click a template to create a new flow from it. Setgreet generates the flow with all the template's screens, components, and graph structure pre-populated, and opens it directly in the [flow builder](/docs/flows/flow-builder).
The new flow is automatically named (e.g., "Flow 1", "Flow 2"). You can rename it by clicking the flow name in the top bar of the editor.
The new flow is independent of the template. Changes you make do not affect the original template, and template
updates do not retroactively change flows you have already created.
## What a template includes
A template defines the following:
| Element | Description |
| ---------------------- | ----------------------------------------------------------------------------- |
| **Screens** | One or more screens with pre-arranged component trees. |
| **Components** | Configured component instances with default text, colors, sizing, and layout. |
| **Background** | Screen background color. |
| **Presentation style** | Fullscreen or bottom sheet, including bottom sheet configuration. |
| **Flow graph** | The node-and-edge structure connecting the screens. |
Templates do **not** include:
- Trigger conditions (you configure these for your specific audience).
- App assignment (the app is selected when publishing).
- Localization content (you add translations after creating the flow).
## Customizing after creation
After creating a flow from a template, treat it like any other flow. Common first steps:
1. **Update copy** -- replace placeholder text with your own messaging.
2. **Adjust branding** -- match colors, fonts, and images to your app's design system.
3. **Configure triggers** -- set [trigger conditions](/docs/flows/triggers) to target the right audience.
4. **Add or remove screens** -- templates are a starting point, not a constraint. Add extra screens or remove ones you do not need.
5. **Set up branching** -- if the template includes branchable components, configure [Branch nodes](/docs/flows/branching) to create personalized paths.
## Tips
- **Start with a template, then iterate** -- even if no template perfectly matches your use case, starting from the closest one is faster than building from scratch.
- **Preview before publishing** -- after customizing a template-based flow, always [preview](/docs/flows/sharing-and-preview) it to verify the experience.
---
# Flow Triggers
Source: https://www.setgreet.com/docs/flows/triggers
> Control when and to whom your flows are displayed.
# Flow Triggers
Triggers are the conditions that control **when** and **to whom** a flow is displayed. Without triggers, a flow matches all users. With triggers, you can target specific segments, respond to events, and create contextual experiences.
## How triggers work
Triggers are configured at the **flow level**. When the SDK syncs, it evaluates each published flow's trigger conditions against the current user's attributes and event properties. A flow is shown only if its trigger conditions evaluate to true.
If a flow has no trigger conditions, it matches every user -- useful for flows like a universal welcome screen.
## Condition sources
Each condition compares a **key** against an expected **value**. The key can come from one of two sources:
### User attributes
Properties you set on the user via `Setgreet.identifyUser()`. Common examples:
- `plan` -- the user's subscription tier.
- `country` -- geographic location.
- `signupDate` -- when the user created their account.
- `role` -- job title or function.
- Any custom attribute you pass during identification.
### Event properties
Properties attached to tracked events. These let you trigger flows based on something the user just did:
- `purchaseAmount` -- the value of a recent purchase.
- `featureName` -- which feature the user just interacted with.
- `sessionCount` -- how many times the user has opened the app.
## Operators
Conditions support a range of comparison operators:
| Operator | Description | Example |
| -------------------- | ---------------------------------- | ------------------------------------ |
| `equals` | Exact match. | `plan equals "pro"` |
| `notEquals` | Does not match. | `country notEquals "US"` |
| `contains` | String contains substring. | `email contains "@company.com"` |
| `notContains` | String does not contain substring. | `email notContains "@test.com"` |
| `startsWith` | String starts with prefix. | `name startsWith "Dr."` |
| `endsWith` | String ends with suffix. | `email endsWith ".edu"` |
| `greaterThan` | Numeric greater than. | `sessionCount greaterThan 5` |
| `lessThan` | Numeric less than. | `purchaseAmount lessThan 100` |
| `greaterThanOrEqual` | Numeric greater than or equal to. | `sessionCount greaterThanOrEqual 10` |
| `lessThanOrEqual` | Numeric less than or equal to. | `purchaseAmount lessThanOrEqual 50` |
## Combining conditions with AND / OR
Triggers support logical grouping to express complex targeting rules.
### Top-level operator
At the top level, conditions are joined by an **AND** or **OR** operator:
- **AND** -- all conditions must be true for the flow to match.
- **OR** -- at least one condition must be true.
### Condition groups
For more complex logic, you can nest conditions inside **groups**. A group bundles multiple conditions together with an AND operator internally. Groups at the top level are then joined by the top-level AND or OR.
**Example**: Show the flow to Pro users in the US, OR to any Enterprise user:
```
OR
├── Group (AND)
│ ├── plan equals "pro"
│ └── country equals "US"
└── Condition
└── plan equals "enterprise"
```
This evaluates as: `(plan == "pro" AND country == "US") OR (plan == "enterprise")`.
## Setting up triggers in the dashboard
1. Open your flow in the flow builder.
2. Click **Publish** in the top toolbar -- the publish modal opens.
3. Click **Edit Trigger Conditions** to open the trigger editor.
4. Click **Add condition or group**.
5. Choose the key type (User Attribute or Event Property).
6. Enter the key name, select an operator, and set the expected value.
7. Add more conditions or groups as needed.
8. Set the top-level operator to AND or OR.
Triggers are saved independently from the flow graph. You can update triggers without re-publishing the flow's screens
and navigation structure.
## Tips for effective targeting
- **Start broad, then narrow** -- launch with no triggers to validate your flow works, then add conditions to target specific segments.
- **Use user attributes for stable segments** -- attributes like plan, country, and role rarely change and make reliable targeting criteria.
- **Use event properties for contextual flows** -- trigger a feedback survey after a purchase, or a feature tip after first use.
- **Test with your own user** -- set your own user attributes to match the trigger conditions and verify the flow appears as expected.
If a trigger condition references a key that does not exist on the user, the condition is treated as a non-match. To
reliably target users who lack a specific attribute, compare against an explicit sentinel value or use your app to
ensure every relevant user has the attribute set via `identifyUser`.
---
# Create Your Account
Source: https://www.setgreet.com/docs/getting-started/create-account
> Sign up for Setgreet and set up your organization.
# Create Your Account
Setgreet uses organizations as the top-level container for your apps, flows, and team members. Every account belongs to at least one organization.
## Sign up
1. Go to [app.setgreet.com](https://app.setgreet.com) and click **Sign Up**.
2. Create your account using an email address or a social login provider.
3. Verify your email if prompted.
You will land on the organization creation screen automatically after signing up.
## Create an organization
An organization represents your company or team. All apps, flows, templates, and billing live under the organization.
1. Enter an **organization name** (e.g., your company name).
2. Click **Create Organization**.
You are now the **owner** of this organization and have full administrative access.
## Invite team members
You can invite colleagues to collaborate on flows immediately, or come back to this later.
1. Open **Settings** from the sidebar.
2. Go to the **Team** tab.
3. Click **Invite Member** and enter an email address.
4. Choose a role:
- **Owner** -- full access, including billing and organization settings.
- **Admin** -- can manage apps, flows, and team members.
- **Member** -- can create and edit flows.
- **Viewer** -- read-only access to flows and analytics.
For detailed information about roles and permissions, see the [Team Management](/docs/team) section.
## Next step
Now that your organization is ready, [create your first app](/docs/getting-started/create-app) to register a mobile app in the dashboard.
---
# Create Your First App
Source: https://www.setgreet.com/docs/getting-started/create-app
> Register your mobile app in the Setgreet dashboard.
# Create Your First App
An **app** in Setgreet represents your mobile application. You create one app, and it covers both iOS and Android -- a single App Key is used by both platform SDKs. Apps are the scope boundary for flows, analytics, and SDK configuration.
## Create an app
1. Open the [Setgreet dashboard](https://app.setgreet.com).
2. Click **Apps** in the sidebar, then click **Create App**.
3. Enter an **App Name** -- a display name for your app (e.g., "MyApp").
4. Click **Create**.
Your new app appears in the apps list with a unique **App Key**.
## Find your App Key
The App Key is the identifier you pass to the SDK when initializing Setgreet in your mobile project. You use the same App Key for both your iOS and Android apps. You will need it in the next step.
1. Go to **Apps** in the sidebar.
2. Click on your app to open its settings.
3. Copy the **App Key** from the overview panel.
The App Key is safe to embed in your mobile app binary. It identifies your app but does not grant write access to your
account.
One Setgreet app serves both iOS and Android. Whether you use native development or a cross-platform framework like
React Native or Flutter, you only need one app. The SDK tracks platform-specific data (like SDK versions)
automatically.
## Next step
With your app created, [install the Setgreet SDK](/docs/getting-started/install-sdk) in your mobile project.
---
# Create Your First Flow
Source: https://www.setgreet.com/docs/getting-started/create-first-flow
> Build and publish your first in-app flow in minutes.
# Create Your First Flow
In this guide you will build a simple two-screen welcome flow, publish it, and see it render in your app. The whole process takes about 15 minutes.
## Step 1 -- Create a flow
1. In the [dashboard](https://app.setgreet.com), click **Flows** in the sidebar.
2. Click **Create Flow**.
3. Choose a **template** to start from -- pick one that matches your use case, or select a blank template to start from scratch.
After selecting a template, you land in the **flow builder** -- a visual canvas where you design the structure of your flow. You can rename the flow by clicking its name in the top bar of the editor.
## Step 2 -- Add screens
The builder starts with a **Start** node. From here you add screens to build out your flow.
1. Click the **+** button on the canvas and select **Add Screen** from the menu.
2. Name the first screen "Welcome" and click on it to open the screen editor.
3. Add a second screen the same way and name it "Get Started".
## Step 3 -- Add components to your screens
Click on the "Welcome" screen node to open the component editor.
1. Drag a **Text** component onto the screen and set its content to a greeting, for example "Welcome to MyApp".
2. Drag an **Image** component below it and upload a logo or illustration.
3. Drag a **Button** component to the bottom and set its label to "Next".
4. Use a **Spacer** component between the image and button to push the button to the bottom of the screen.
Repeat for the "Get Started" screen with different content and a "Let's Go" button.
Components use a flexbox layout system. Spacers with **Fill Space** enabled (the default) expand to consume remaining
vertical space, which is how you pin buttons to the bottom of a screen.
## Step 4 -- Connect the screens
Back on the flow builder canvas, make sure the screens are connected in order:
**Start** -> **Welcome** -> **Get Started**
Drag edges between nodes if they are not already connected. The edge from a screen node fires when the user taps the primary action button on that screen.
## Step 5 -- Preview your flow
Before publishing, preview what your flow looks like:
1. Click the **Preview** button in the top toolbar.
2. Step through each screen to verify layout, content, and navigation.
## Step 6 -- Understand draft vs. published
Setgreet uses a **draft system** to protect live users from incomplete changes:
- **Draft** -- your working copy. Edits are saved automatically but are not visible to app users.
- **Published** -- the live version served by the SDK. Publishing copies the current draft to the live version.
- **Unpublished** -- removes the flow from SDK responses entirely. You can re-publish at any time.
When you edit an already-published flow, your changes go into a new draft. The live version remains untouched until you publish again.
## Step 7 -- Publish
1. Click **Publish** in the top-right corner of the flow builder.
2. Confirm the publish action.
Your flow is now live. The SDK will pick it up on the next sync (within seconds for most configurations).
## Step 8 -- Test on a device
1. Open your app on a device or simulator where the Setgreet SDK is initialized.
2. Make sure you have called `Setgreet.identifyUser(...)` so the SDK can evaluate targeting.
3. The Welcome flow should appear automatically since it has no trigger conditions (it matches all users by default).
If the flow does not appear, check that the App Key matches, the SDK is initialized before the screen loads, and the
flow status is **Published** in the dashboard.
## Next steps
- Add [trigger conditions](/docs/flows/triggers) so the flow only shows to new users.
- Explore the [component library](/docs/components) to build richer screens.
- Set up [branching](/docs/flows/branching) to create conditional paths based on user responses.
---
# Getting Started
Source: https://www.setgreet.com/docs/getting-started
> Get up and running with Setgreet in under 30 minutes.
# Getting Started
This guide walks you through the four steps to go from a fresh Setgreet account to a live in-app flow rendered natively on a real device.
## The quickstart path
| Step | What you will do | Time |
| -------------------------------------------------------------------- | ------------------------------------------------------ | ------ |
| 1. [Create your account](/docs/getting-started/create-account) | Sign up, create an organization, and invite teammates. | 2 min |
| 2. [Create your first app](/docs/getting-started/create-app) | Register an iOS or Android app and grab your App Key. | 2 min |
| 3. [Install the SDK](/docs/getting-started/install-sdk) | Add the Setgreet SDK to your mobile project. | 10 min |
| 4. [Create your first flow](/docs/getting-started/create-first-flow) | Build a simple onboarding flow and publish it. | 15 min |
## Prerequisites
- A mobile app project (iOS 15+, Android API 23+, React Native 0.60+, or Flutter 3.0+).
- Access to the [Setgreet dashboard](https://app.setgreet.com).
You can explore the dashboard and build flows before integrating the SDK. The SDK is only needed to render flows on a
device.
## What happens under the hood
When you publish a flow, the Setgreet API delivers it to the SDK in your app. The SDK evaluates trigger conditions locally and renders matching flows using fully native UI components -- no web views, no iframes. Screens are built with a flexbox-based layout system, so they adapt to any screen size automatically.
All of this is controlled from the dashboard. You never need to write flow logic in your app code or submit an app update to change what users see.
---
# Install the SDK
Source: https://www.setgreet.com/docs/getting-started/install-sdk
> Add the Setgreet SDK to your mobile app.
# Install the SDK
The Setgreet SDK is responsible for fetching published flows from the API, evaluating trigger conditions on the device, and rendering matching flows using fully native UI components. It is lightweight, runs on the main thread only when rendering, and requires minimal configuration.
## Supported platforms
| Platform | Min. version | Package manager | Repository |
| ---------------- | ------------ | ---------------------- | ---------------------------------------------------------------------------------- |
| **iOS** | iOS 15+ | Swift Package Manager | [setgreet-ios-sdk](https://github.com/setgreet/setgreet-ios-sdk) |
| **Android** | API 23+ | Gradle (Maven Central) | [setgreet-android-sdk](https://github.com/setgreet/setgreet-android-sdk) |
| **React Native** | RN 0.60+ | npm / yarn | [setgreet-react-native-sdk](https://github.com/setgreet/setgreet-react-native-sdk) |
| **Flutter** | Flutter 3.0+ | pub.dev | [setgreet-flutter-sdk](https://github.com/setgreet/setgreet-flutter-sdk) |
Each SDK repository contains a detailed README with installation instructions, initialization code, and API reference. Follow the guide for your platform below.
## Installation overview
Regardless of platform, the integration follows the same three steps:
### 1. Add the dependency
Add the Setgreet SDK package to your project using the platform's standard package manager. See the repository README for the exact dependency declaration.
### 2. Initialize the SDK
Call the Setgreet initializer early in your app's lifecycle (typically in `AppDelegate`, `Application.onCreate`, or your root component) and pass your **App Key**:
```
// Pseudocode -- see the platform README for real syntax
Setgreet.initialize(appKey: "YOUR_APP_KEY")
```
### 3. Identify the user
Once you know who the current user is (after login, or from a stored session), identify them so that Setgreet can evaluate targeting conditions and track analytics per user:
```
// Pseudocode
Setgreet.identifyUser(userId: "user-123", attributes: {
plan: "pro",
signupDate: "2026-01-15"
})
```
User attributes you pass here become available for [flow triggers](/docs/flows/triggers) and [personalization](/docs/personalization) in the dashboard.
You can call `identifyUser` with additional attributes at any time. The SDK merges new attributes with existing ones.
## Platform-specific guides
For full installation instructions, configuration options, and troubleshooting, refer to the README in each SDK repository:
- **iOS** -- [github.com/setgreet/setgreet-ios-sdk](https://github.com/setgreet/setgreet-ios-sdk)
- **Android** -- [github.com/setgreet/setgreet-android-sdk](https://github.com/setgreet/setgreet-android-sdk)
- **React Native** -- [github.com/setgreet/setgreet-react-native-sdk](https://github.com/setgreet/setgreet-react-native-sdk)
- **Flutter** -- [github.com/setgreet/setgreet-flutter-sdk](https://github.com/setgreet/setgreet-flutter-sdk)
## Next step
With the SDK installed and initialized, head back to the dashboard to [create your first flow](/docs/getting-started/create-first-flow).
---
# Component Analytics
Source: https://www.setgreet.com/docs/analytics/component-analytics
> Track per-component interactions including clicks, inputs, ratings, and NPS responses.
# Component Analytics
Component analytics track how users interact with individual components inside your flows. While [flow analytics](/docs/analytics/flow-analytics) show the big picture (views, completions, drop-offs), component analytics reveal what happens _within_ each screen -- which buttons get tapped, what ratings users give, and how they engage with inputs.
## Accessing component analytics
1. Open the flow in the flow builder.
2. Click the flow menu in the top bar and select **Component Analytics**.
The Component Analytics dashboard opens in a dedicated view.
Component Analytics requires **Advanced Analytics**, which is included on the Growth plan or higher.
## Event types
The Setgreet SDK automatically tracks these component-level events:
| Event | Description |
| ---------------------- | ------------------------------------------------------ |
| **component_view** | The component was rendered on screen. |
| **component_click** | A button or tappable element was pressed. |
| **component_input** | A text field or form input received a value. |
| **component_rating** | A user submitted a star rating. |
| **component_nps** | A user submitted an NPS score (0-10). |
| **component_video** | A video component interaction (play, pause, complete). |
| **component_carousel** | A carousel swipe or navigation action. |
Each event is associated with the specific flow, screen, and component that generated it, along with the user ID and timestamp.
## Rating and NPS distribution
For rating and NPS components, the analytics panel displays response distribution charts:
### Rating components
A bar chart shows the count of each rating value (for example, 1 through 5 stars). Use this to quickly see whether users skew positive or negative.
### NPS components
The NPS breakdown groups responses into three categories:
| Category | Score range |
| -------------- | ----------- |
| **Detractors** | 0-6 |
| **Passives** | 7-8 |
| **Promoters** | 9-10 |
The calculated NPS score (promoters percentage minus detractors percentage) is displayed alongside the distribution chart.
## Understanding engagement patterns
Component analytics help you answer questions like:
- **Are users interacting with your CTA?** -- Check click rates on button components.
- **Where do users hesitate?** -- Low interaction rates on an input field may indicate confusion or friction.
- **Is your feedback component placed well?** -- Compare view counts against submission rates to gauge completion.
- **Which carousel slide gets the most attention?** -- Track swipe events to see how far users navigate.
## Batch event tracking
The Setgreet SDK collects component events on the device and sends them to the server in batches for efficiency. Each batch can contain up to **100 events** per request. This means:
- Events may arrive with a slight delay (typically a few seconds) after the interaction.
- If a user interacts with many components in quick succession, events are queued and sent together.
- Events are persisted on-device until successfully delivered, so no analytics data is lost.
Component analytics events are separate from flow display tracking. Flow views and completions are tracked
independently, while component events capture granular interactions within each screen.
## Next steps
- [Flow Analytics](/docs/analytics/flow-analytics) -- see how the overall flow performs.
- [Conversion Goals](/docs/analytics/conversion-goals) -- connect component interactions to business outcomes.
---
# Conversion Goals
Source: https://www.setgreet.com/docs/analytics/conversion-goals
> Track business outcomes tied to your flows with conversion goals.
# Conversion Goals
Conversion goals let you measure whether your flows drive real business outcomes. Instead of only tracking views and completions, you can define specific events that represent success -- like a user activating a feature, completing a purchase, or upgrading their plan -- and see how often those events occur after a flow is shown.
## How conversion goals work
A conversion goal connects a **flow** to a **target event**. When a user sees the flow and later triggers that event within a defined time window, Setgreet records it as a conversion.
The conversion tracking pipeline works as follows:
1. A user is shown a flow (a flow display event is recorded).
2. The user later triggers an event in your app that you have configured as a conversion goal.
3. Setgreet checks whether the user saw the associated flow within the **conversion window**.
4. If they did, a conversion is recorded and linked to that flow display.
Each user is counted only once per flow display -- if the same user triggers the goal event multiple times after viewing a flow, only the first conversion is recorded.
## Setting up a conversion goal
Conversion goals are managed inside the **Path Analytics** panel of a flow:
1. Open the flow in the flow builder.
2. Click the flow menu in the top bar and select **Path Analytics**.
3. Find the Conversion Goals section and click **Add Goal** (or edit an existing one).
4. Configure the goal:
- **Name** -- a descriptive label (for example, "Feature X Activated" or "Plan Upgraded"). Up to 200 characters.
- **Event name** -- must match the event name your app sends to the Setgreet SDK (for example, `feature_activated` or `plan_upgraded`).
- **Conversion window** -- the number of hours after a flow view during which conversions are counted. The default is **24 hours**, and the maximum is **168 hours** (7 days).
- **Property filter (optional)** -- narrow conversions to events that match specific property values.
### Property filters
Property filters let you target a subset of events. For example, if your `purchase_completed` event includes a `plan` property, you can filter conversions to only count events where `plan` equals `"pro"`.
Each filter has three parts:
| Field | Description |
| ------------ | ------------------------------------------------------------- |
| **Key** | The event property name (e.g., `plan`, `amount`, `category`). |
| **Operator** | How to compare: `equals`, `contains`, or `greaterThan`. |
| **Value** | The value to match against. |
You can add one property filter per goal.
## Viewing conversion data
Once a goal is set up, the Path Analytics panel shows conversion metrics alongside the standard flow analytics:
| Metric | Description |
| ------------------------------ | ------------------------------------------------------------ |
| **Total Conversions** | Number of conversion events recorded within the window. |
| **Unique Users Converted** | Number of distinct users who converted. |
| **Conversion Rate** | Percentage of unique flow viewers who converted. |
| **Average Time to Conversion** | Mean time between the flow display and the conversion event. |
These metrics are scoped to the date range selected in the panel.
## Conversion funnel
The conversion funnel view shows the path from flow impression to goal completion:
1. **Flow views** -- total unique users who saw the flow.
2. **Flow completions** -- users who finished the flow.
3. **Goal conversions** -- users who triggered the goal event within the conversion window.
This three-step funnel helps you identify where to optimize. If completions are high but conversions are low, the flow content may not be compelling enough to drive action. If views are high but completions are low, users may be dropping off before seeing the key message.
## Measuring flow effectiveness
Conversion goals are the most direct way to answer the question: _"Is this flow actually working?"_
Use them to:
- **Compare flow variants** -- if you are running [experiments](/docs/experiments), conversion goals let you measure which variant drives better outcomes.
- **Justify flow investment** -- show stakeholders that an onboarding flow increased feature adoption by a measurable percentage.
- **Identify underperforming flows** -- flows with high views but low conversion rates are candidates for redesign.
- **Track time-sensitive campaigns** -- set shorter conversion windows for urgent prompts (e.g., a flash sale announcement) and longer windows for educational flows.
Conversion goals require your app to send tracking events via the Setgreet SDK. Make sure the event names in your goal
configuration match the event names your app sends exactly.
## Managing goals
You can create, update, and deactivate conversion goals from the Path Analytics panel:
- **Activate / Deactivate** -- toggle a goal on or off without deleting it. Deactivated goals stop recording new conversions but retain historical data.
- **Edit** -- update the goal name, event name, property filter, or conversion window at any time. Changes apply to future conversions only.
- **Delete** -- permanently remove a goal and its conversion data.
## Next steps
- [Flow Analytics](/docs/analytics/flow-analytics) -- review overall flow performance alongside conversion data.
- [Experiments](/docs/experiments) -- A/B test flow variants and measure conversions per variant.
---
# Dashboard
Source: https://www.setgreet.com/docs/analytics/dashboard
> View organization-wide analytics on the dashboard.
# Dashboard
The dashboard is the first screen you see when you log in to [app.setgreet.com](https://app.setgreet.com). It provides an organization-level overview of how your flows are performing across all apps over the last 30 days.
## Key metrics
The dashboard displays these top-level metrics at the top of the page:
| Metric | Description |
| ------------------- | ------------------------------------------------------------- |
| **Flow Views** | Total number of times any flow was displayed across all apps. |
| **Unique Users** | Number of distinct users who saw at least one flow. |
| **Published Flows** | Number of flows currently published across the organization. |
| **Total Apps** | Number of apps registered in your organization. |
These numbers give you a quick sense of overall engagement without digging into individual flows.
## App filtering
If your organization has multiple apps, use the app tabs above the stats to narrow the dashboard to a specific app. This is useful when you want to compare performance across platforms or focus on a single product.
## Daily flow views chart
The main chart on the dashboard plots **daily flow views over the last 30 days**. Each data point shows the total number of flow displays for that day.
Use this chart to spot trends:
- **Spikes** may indicate a successful flow launch or a high-traffic event.
- **Drops** could signal a targeting issue, an app update that affected SDK initialization, or lower user traffic.
- **Steady growth** suggests your flows are reaching a consistent and expanding audience.
## Top flows and recent activity
Below the chart, the dashboard surfaces:
- **Top Flows** -- the flows with the highest views, with a direct link to open each flow.
- **Recent Activity** -- a feed of recent changes and events in your organization.
## Next steps
For deeper analysis, drill into individual flows:
- [Flow Analytics](/docs/analytics/flow-analytics) -- see per-flow metrics and screen-by-screen drop-off.
- [Component Analytics](/docs/analytics/component-analytics) -- understand how users interact with specific components.
- [Conversion Goals](/docs/analytics/conversion-goals) -- track business outcomes tied to flows.
---
# Flow Analytics
Source: https://www.setgreet.com/docs/analytics/flow-analytics
> Analyze per-flow performance with views, completions, drop-offs, and screen-by-screen paths.
# Flow Analytics
Flow analytics give you a detailed picture of how a single flow performs. While the [dashboard](/docs/analytics/dashboard) shows organization-wide numbers, flow analytics let you drill into a specific flow to understand where users engage, where they drop off, and how they respond.
## Accessing flow analytics
1. Open the flow in the flow builder.
2. Click the flow menu in the top bar and select **Path Analytics**.
The Path Analytics panel opens with metrics scoped to that flow.
## Key metrics
The overview tab displays these top-level numbers for the flow:
| Metric | Description |
| ------------------- | ------------------------------------------------------ |
| **Total Views** | Total number of times the flow was displayed to users. |
| **Unique Users** | Number of distinct users who saw the flow. |
| **Completion Rate** | Percentage of users who reached the end of the flow. |
| **Drop-off** | Users who started the flow but did not complete it. |
## Tabs in the panel
The Path Analytics panel is organized into tabs, each offering a different view of the data:
- **Overview** -- the key metrics and conversion summary for the flow.
- **Funnel** -- a funnel visualization showing how users progress through the flow step by step.
- **Flow map** -- a Sankey-style diagram of the paths users take between screens.
- **Branches** -- performance breakdown per branch when the flow contains Branch nodes.
Not every tab appears for every flow -- the Funnel, Flow map, and Branches tabs only show up when there is enough data and the flow has the relevant structure.
## Drop-off analysis
The funnel and flow map views show a screen-by-screen breakdown of user progression. For each screen you can see:
- How many users reached that screen.
- How many users left the flow at that screen.
- The drop-off rate for that screen.
This makes it easy to spot problematic screens. If a particular screen has a high drop-off rate, it may need shorter copy, a clearer call-to-action, or a simpler layout.
For flows with [branching](/docs/flows/branching), the Branches tab shows each branch separately so you can compare how different paths perform.
## Filtering by date
Use the date range picker at the top of the panel to adjust the reporting window. All metrics and charts update to reflect the selected range.
## Exporting data
You can export the screen-level breakdown as a CSV file for offline analysis or sharing with stakeholders.
## Conversion goals
If you have set up [conversion goals](/docs/analytics/conversion-goals) for the flow, their metrics appear alongside the standard flow analytics -- total conversions, conversion rate, and average time to conversion.
For per-component interaction data (clicks, inputs, ratings, NPS), open the [Component
Analytics](/docs/analytics/component-analytics) dashboard from the flow menu.
## Next steps
- [Component Analytics](/docs/analytics/component-analytics) -- drill into individual component interactions within a flow.
- [Conversion Goals](/docs/analytics/conversion-goals) -- measure business outcomes tied to this flow.
---
# Analytics
Source: https://www.setgreet.com/docs/analytics
> Track and analyze the performance of your flows.
# Analytics
Setgreet gives you layered analytics so you can understand how your in-app experiences perform -- from a high-level overview down to individual component interactions.
## Analytics layers
Setgreet tracks performance at three levels of detail:
| Layer | What it tells you |
| ---------------------------------------------------------- | -------------------------------------------------------------------------------- |
| [Dashboard](/docs/analytics/dashboard) | Organization-wide metrics across all apps and flows. A quick pulse check. |
| [Flow Analytics](/docs/analytics/flow-analytics) | Per-flow performance: views, completions, drop-offs, and screen-by-screen paths. |
| [Component Analytics](/docs/analytics/component-analytics) | Per-component interactions: clicks, input values, ratings, and NPS responses. |
Each layer builds on the one above it. Start with the dashboard for a big-picture view, then drill into specific flows and components to find optimization opportunities.
## Conversion goals
Beyond standard engagement metrics, you can define [conversion goals](/docs/analytics/conversion-goals) to track specific business outcomes tied to your flows. For example, you might track how many users who completed an onboarding flow went on to activate a key feature within 24 hours.
## How tracking works
The Setgreet SDK sends analytics events to the Setgreet API as users interact with flows. Events are processed and aggregated server-side, so the data you see in the dashboard reflects real interactions from real devices.
Key events tracked automatically:
- **Flow displayed** -- the flow was shown to a user.
- **Screen viewed** -- a specific screen within a flow was seen.
- **Flow completed** -- the user reached the end of the flow.
- **Flow dismissed** -- the user closed the flow before completing it.
- **Component interaction** -- a button was tapped, an input was filled, a rating was given.
Analytics data is available in near real-time. There may be a brief delay (under a minute) between a user interaction
and its appearance in the dashboard.
---
# Setgreet for AI agents
Source: https://www.setgreet.com/docs/mcp/agents
> A drop-in instructions block that teaches Cursor, Claude Code, and Copilot to install the Setgreet SDK and use the MCP server correctly.
# Setgreet for AI agents
Coding agents work best with a short instructions file in your repo. This page gives you a copy-paste block that teaches your agent two things: how to install the Setgreet SDK into your app, and how to author flows through the [MCP server](/docs/mcp) instead of hand-writing flow JSON.
## Where to put it
Paste the block below into whichever file your agent already reads. One file covers most tools:
- **`AGENTS.md`** at your repo root -- the [open standard](https://agents.md) read by Cursor, Claude Code, Copilot, and others.
- **`CLAUDE.md`** for Claude Code, or `.cursor/rules/setgreet.mdc` for Cursor, if you prefer tool-specific files.
You do not need all three. `AGENTS.md` is the simplest choice.
## The block
```markdown
## Setgreet
This app uses Setgreet for in-app flows (onboarding, paywalls, surveys), rendered
natively and published over the air without an app release.
### Authoring flows
- Author, edit, and publish flows through the Setgreet MCP server, not by hand.
Do not write flow JSON directly.
- Connect the MCP server at https://mcp.setgreet.com/mcp (see
https://www.setgreet.com/docs/mcp for per-client setup).
- Publishing a flow affects the live app. Always show the drafted screens and ask
for confirmation before calling publish_flow. Destructive tools require an
explicit confirm; never pass it without asking.
- To generate a flow, use compose_flow. To edit one, use get_screen +
update_component / reorder_screens / add_screen_from_pattern.
### Installing the SDK
Install the SDK for this app's platform and initialize it early in the app
lifecycle with the App Key from the Setgreet dashboard (Settings, App). Use the
latest published version, not a placeholder.
- iOS (Swift): add the Swift package https://github.com/setgreet/setgreet-ios-sdk,
then `Setgreet.shared.initialize(appKey: "YOUR_APP_KEY", config: SetgreetConfig())`
in AppDelegate.
- Android (Kotlin): add `com.setgreet:setgreet` from Maven Central, then
`Setgreet.initialize(context, "YOUR_APP_KEY", SetgreetConfig())` in Application.onCreate.
- React Native: `npm install @setgreet/react-native-sdk`, then `initialize("YOUR_APP_KEY", {})`
in your root component.
- Flutter: add `setgreet` from pub.dev, then
`Setgreet.initialize("YOUR_APP_KEY", config: SetgreetConfig())` at startup.
Confirm the exact init signature against the SDK README for your platform, then
flows appear automatically based on their targeting rules.
Full SDK docs: https://www.setgreet.com/docs/sdk
```
Replace `YOUR_APP_KEY` with the App Key from your dashboard, and pin the latest published SDK version rather than a
placeholder. Your agent should look up the current version from the SDK's package registry or GitHub releases.
## Why this exists
The MCP server runs against your Setgreet workspace, so it can author and publish flows for you. It cannot reach into your app's codebase to install the SDK. This instructions file closes that gap: the agent uses the MCP server for everything flow-related, and follows the SDK steps here for the one job the MCP server cannot do.
## Next steps
- [Use Setgreet with AI agents](/docs/mcp) -- connect the MCP server.
- [Recipes](/docs/mcp/recipes) -- end-to-end agent workflows.
- [Platform SDKs](/docs/sdk/platforms) -- full per-platform install guides.
---
# Use Setgreet with AI agents
Source: https://www.setgreet.com/docs/mcp
> Connect Claude, Cursor, VS Code, Windsurf, Codex, and any MCP-compatible AI agent to your Setgreet platform.
# Use Setgreet with AI agents
There are three ways to build with AI on Setgreet:
- **In-product AI** -- describe a flow in the dashboard and the assistant drafts and edits native screens for you. No setup. See [Build with AI](/docs/build-with-ai).
- **MCP server** -- connect Claude, Cursor, Codex, or any MCP client to your workspace and let it author, publish, and analyze flows through your existing tools. That is this page.
- **Agent instructions file** -- a drop-in block that teaches your coding agent to install the SDK and use the MCP server correctly in your own repo. See [Setgreet for AI agents](/docs/mcp/agents).
The MCP server lets AI agents read and write your Setgreet workspace -- flows, screens, analytics, experiments, segments, theme, localization, billing, team, and integrations -- through a single OAuth connection. It implements the [Model Context Protocol](https://modelcontextprotocol.io), the open standard supported by Claude, Cursor, VS Code, Windsurf, Codex, and a growing list of other clients. Setgreet hosts the server at `https://mcp.setgreet.com/mcp` -- there is no install, no local process, no API key to manage.
## Install
Pick your client. Each tab is a copy-paste install. The hosted endpoint is the same in every case; only the client config differs.
Open `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS (or `%APPDATA%\Claude\claude_desktop_config.json` on Windows) and add the Setgreet server:
```json
{
"mcpServers": {
"setgreet": {
"url": "https://mcp.setgreet.com/mcp"
}
}
}
```
Restart Claude Desktop. The first tool call opens a browser for OAuth sign-in.
```bash
claude mcp add setgreet --transport http --scope user https://mcp.setgreet.com/mcp
```
The `--scope user` flag makes Setgreet available across every project; swap it for `--scope project` to write a `.mcp.json` at the project root that you can commit. Run `/mcp` inside a Claude Code session to confirm `setgreet` is listed.
Add Setgreet to either `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project):
```json
{
"mcpServers": {
"setgreet": {
"url": "https://mcp.setgreet.com/mcp"
}
}
}
```
Restart Cursor. Setgreet appears in Settings -> MCP.
Add Setgreet to `.vscode/mcp.json` (workspace) or your user MCP config (Command Palette -> `MCP: Open User Configuration`):
```json
{
"servers": {
"setgreet": {
"type": "http",
"url": "https://mcp.setgreet.com/mcp"
}
}
}
```
Or one-shot from the terminal:
```bash
code --add-mcp '{"name":"setgreet","type":"http","url":"https://mcp.setgreet.com/mcp"}'
```
Setgreet is then available in Copilot Chat under Agent mode.
Add Setgreet to `~/.codeium/windsurf/mcp_config.json`:
```json
{
"mcpServers": {
"setgreet": {
"serverUrl": "https://mcp.setgreet.com/mcp"
}
}
}
```
Restart Windsurf and refresh the MCP server list in Cascade.
Codex CLI configures HTTP MCP servers in `~/.codex/config.toml` -- the `codex mcp add` CLI is stdio-only today. Add this block:
```toml
[mcp_servers.setgreet]
url = "https://mcp.setgreet.com/mcp"
```
The next `codex` session picks up the new server.
ChatGPT calls custom MCP servers "Connectors" and exposes them via the dashboard, not a config file. You need a Business, Enterprise, or Edu workspace with **Developer Mode** enabled (Workspace Settings -> Permissions & Roles -> Connected Data Developer mode) -- Plus and Pro plans get read/fetch-only custom connectors even with Developer Mode on.
1. In ChatGPT, open **Settings -> Connectors -> Create**.
2. Name it `Setgreet`, set the MCP Server URL to `https://mcp.setgreet.com/mcp`, and write a one-line description.
3. Save. The first tool call prompts the OAuth sign-in.
Setgreet's MCP transport is Streamable HTTP -- it speaks the same wire format ChatGPT expects for custom connectors.
ChatGPT connector support is still in beta and the available tool surface depends on your workspace plan. Claude
Desktop, Claude Code, Cursor, VS Code, Windsurf, and Codex CLI get the full tool surface today.
Any spec-compliant MCP client can connect over Streamable HTTP at:
```text
https://mcp.setgreet.com/mcp
```
The server advertises OAuth discovery at
[`/.well-known/oauth-authorization-server`](https://api.setgreet.com/.well-known/oauth-authorization-server) per
[RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414), so MCP clients that support OAuth discovery auto-configure
from that single URL.
## Authorize
The first time your agent calls a Setgreet tool, the MCP server opens a browser to the Setgreet dashboard. You sign in with your existing account, the browser hands the redirect back to the client, and the agent prints your user, organization, role, and granted scopes.
Setgreet uses OAuth 2.1 with PKCE -- there is no shared secret to leak, and the agent never touches your refresh token. Access tokens are short-lived (5 minutes) and refresh transparently in the background; refresh tokens rotate on every use and stay valid for 30 days.
You can list and revoke active MCP sessions from the dashboard at **Settings -> MCP Connections** (`/settings/mcp-connections`). Revoking a session takes effect immediately -- the agent's next tool call gets `authentication_required` and prompts re-authorization.
To verify the install, ask the agent:
```text
Call the setgreet whoami tool.
```
It returns your user, org, role, granted scopes, and token expiry.
## How scopes work
Scopes are not requested by the client. They are **derived from your dashboard role** at token issuance and written directly into the JWT. A viewer's token literally cannot carry `flows:write` -- the scope is absent from the `scope` claim, so even a malicious client cannot use it. Admins can create segments and manage team; viewers cannot. Role demotions take effect on the next token refresh (within 5 minutes).
Destructive operations (delete, unpublish, complete experiment, remove member, regenerate share token) require an explicit `confirm: true` parameter -- the agent has to ask you before passing it. If it doesn't, the MCP server refuses the call with `confirmation_required` before the API is touched.
End-user PII (email, phone, name, address, IP) is partitioned behind a separate `endusers:read:pii` scope granted to admins and above. Without it, the backend silently replaces those fields with `"[redacted]"` -- enforced server-side, not at the MCP layer.
## Plan availability
MCP access is included on **every plan, including Free**. See [pricing](/pricing).
## What's next
- [Recipes](/docs/mcp/recipes) -- end-to-end agent workflows with copy-paste prompts.
- [Setgreet for AI agents](/docs/mcp/agents) -- the drop-in instructions file for coding agents.
- [Tools](/docs/mcp/tools) -- the full tool catalog grouped by domain.
- [Troubleshooting](/docs/mcp/troubleshooting) -- error codes (`authentication_required`, `scope_required`, `plan_required`, `confirmation_required`) and how to resolve each.
- [llms.txt](/llms.txt) and [llms-full.txt](/llms-full.txt) -- machine-readable maps of these docs for agents.
---
# Recipes
Source: https://www.setgreet.com/docs/mcp/recipes
> End-to-end agent workflows that string Setgreet MCP tools into real tasks, with copy-paste prompts.
# Recipes
The [tool catalog](/docs/mcp/tools) lists every MCP tool. This page strings them into real tasks. Each recipe is a prompt you can paste into Claude, Cursor, or any [connected client](/docs/mcp), plus the tools the agent will call and the point where it stops to ask you.
These recipes assume the [MCP server is connected and authorized](/docs/mcp). If a tool call returns
`authentication_required`, ask the agent to `Call the setgreet whoami tool` first to trigger sign-in.
## Build an onboarding flow
Go from an idea to a live native flow in one conversation.
> Build a 4-screen onboarding flow for my app. Start with a welcome screen, then two value screens, then a notification-permission prompt. Use the setgreet tools. Show me the screens before you publish anything.
What the agent does:
1. `list_apps` (and `switch_app`) to set the app context so the flow is themed correctly.
2. `compose_flow` to generate the draft from your brief, or `add_screen_from_pattern` repeatedly for tighter control.
3. `get_flow_context` to show you the drafted screens.
4. **Stops and asks** before `publish_flow`, because publishing takes the flow live to your users immediately. The prompt above tells the agent to show you the screens first; make that a habit, since `publish_flow` is not one of the tools the server hard-gates behind an explicit confirm.
`compose_flow` spends an AI credit. Editing the draft afterward with the screen-editing tools does not. See [Build
with AI](/docs/build-with-ai#credits).
## Iterate a flow by chat
Refine an existing draft without opening the editor.
> In the flow "Welcome v2", change the paywall CTA copy to "Start my free week" and move the notification-permission screen to right after the welcome screen.
What the agent does:
1. `list_flows` to resolve the flow by name, then `get_flow_context` to read the current screens.
2. `get_screen` on the relevant screen, then `update_component` to change the CTA copy.
3. `reorder_screens` to move the permission screen.
4. Re-`publish_flow` when you confirm.
Edits target the DRAFT, so live users see nothing until you publish. If another teammate changed the screen since the agent read it, the tool returns `conflict` rather than overwriting -- ask the agent to re-read and retry.
## Translate a flow into more languages
Localize an entire flow in one pass.
> Translate the flow "Welcome v2" into Spanish, German, French, Portuguese, and Japanese.
What the agent does:
1. `list_flows` to resolve the flow, `list_locales` to see what already exists.
2. `bulk_translate_flow` once per target language -- each call translates every screen and preserves your `{{personalization}}` variables.
3. `get_flow_localization` to confirm coverage.
Translation is work-proportional (strings x languages) and spends credits. The agent can estimate the cost before
running if you ask it to.
## Run an A/B test and read the result
Test a change, then let the agent tell you which variant won.
> Create an experiment on the flow "Welcome v2" with two variants: control, and a version where the paywall uses a free-trial layout. Split traffic 50/50 and start it.
What the agent does:
1. `create_experiment` with weighted variants (this creates a DRAFT).
2. `start_experiment` to move it to RUNNING.
Then, days later:
> How is the "Welcome v2" paywall experiment doing? Is either variant winning yet?
3. `get_experiment_results` for per-variant metrics and statistical significance.
4. `complete_experiment` to declare a winner -- **only after you confirm**, since it is terminal and cannot be undone.
## Audit a flow's drop-off
Find where users leave, before deciding what to fix.
> Where are users dropping off in the flow "Welcome v2"? Break it down screen by screen and tell me which component loses the most people.
What the agent does:
1. `get_flow_dashboard` for the headline numbers (impressions, completions, drop-off).
2. `get_flow_paths` for the screen-by-screen funnel.
3. `get_flow_components_analytics` to find the specific button or input that loses users.
4. Optionally `get_flow_inputs` to read NPS, ratings, and form answers.
From here you can chain straight into "Iterate a flow by chat" to ship the fix.
## Install the Setgreet SDK
Have your coding agent wire the SDK into your app.
The MCP server authors and publishes flows, but it runs against your Setgreet workspace, not your app's codebase. To install the SDK, point your coding agent at the [Setgreet agent instructions](/docs/mcp/agents) -- a copy-paste block you add to your repo so Cursor, Claude Code, or Copilot installs and initializes the SDK correctly for your platform.
## Next steps
- [Tools](/docs/mcp/tools) -- the full catalog these recipes draw from.
- [Setgreet for AI agents](/docs/mcp/agents) -- the drop-in instructions file for coding agents.
- [Troubleshooting](/docs/mcp/troubleshooting) -- error codes and fixes.
---
# Tools
Source: https://www.setgreet.com/docs/mcp/tools
> The full catalog of Setgreet MCP tools grouped by domain.
# Tools
The Setgreet MCP server exposes its full platform surface, from flow authoring and screen editing to analytics, experiments, localization, and team management. Each tool requires an active OAuth session and the right scope on your role -- if your role doesn't have the scope, the tool isn't usable (and in some clients won't even appear in the tool list).
Don't see a tool you expected? Your role probably doesn't have the scope. Ask your org owner to bump your role from
the dashboard, then sign out and sign in again to mint a fresh token. See
[Troubleshooting](/docs/mcp/troubleshooting#tool-not-found) for the full resolution path.
## Authentication (3)
OAuth 2.1 + PKCE driven entirely from MCP tools. The agent never touches your verifier or refresh token.
| Tool | Required scope | Description |
| ------------------ | -------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `sign_in` | (none) | Begins OAuth sign-in. Returns `authorization_url` + `state`. Optional `org_id` pins the new token to a specific org. |
| `complete_sign_in` | (none) | Finishes sign-in. Accepts `{ code, state }` or `{ redirect_url }` (the tool parses code+state out of the redirect's query string). |
| `sign_out` | (none) | Revokes the current refresh token and clears local session storage. Idempotent. The current access JWT stays valid until exp. |
## Connectivity & identity (1)
| Tool | Required scope | Description |
| -------- | -------------- | ---------------------------------------------------------------------------------- |
| `whoami` | (token) | Resolved user, organization, role, granted scopes, OAuth client, and token expiry. |
## Flow CRUD & lifecycle (7)
Destructive ops (`unpublish_flow`, `delete_flow`) require `confirm: true`.
| Tool | Required scope | Description |
| ------------------ | -------------- | ---------------------------------------------------------------------------- |
| `list_flows` | `flows:read` | Paginated list of flows in the current organization. |
| `get_flow_context` | `flows:read` | High-info read: flow + graph + screens in one payload (Figma-style). |
| `update_flow_name` | `flows:write` | Rename a flow. |
| `duplicate_flow` | `flows:write` | Clone a flow as a clean DRAFT (appId + triggers stripped). |
| `publish_flow` | `flows:write` | Promote draft to live and assign to an app. |
| `unpublish_flow` | `flows:write` | Revert to DRAFT (destructive; `confirm: true`). |
| `delete_flow` | `flows:write` | Soft-delete the flow + its screens, graph, locales (destructive; `confirm`). |
## Screen editing (8)
Granular edit-by-chat tools that mutate a flow's DRAFT screens -- the same tools that power in-product AI editing. Reads use `screens:read`; mutations use `screens:write`. `remove_component` is destructive and requires `confirm: true`. Most mutations accept `expected_version` for optimistic concurrency -- if another editor changed the screen since the agent last read it, the tool returns `conflict` instead of silently overwriting.
| Tool | Required scope | Description |
| ------------------------- | --------------- | ------------------------------------------------------------------------------------------------------ |
| `get_screen` | `screens:read` | Fetch one DRAFT screen: components, background, presentation style. |
| `update_screen` | `screens:write` | Replace ALL components on a DRAFT screen with the provided array. Use sparingly. |
| `update_screen_meta` | `screens:write` | Update screen-level metadata (name, presentation style) on a DRAFT screen. |
| `add_component_to_screen` | `screens:write` | Add one component to a DRAFT screen, appended or inserted at an index. |
| `update_component` | `screens:write` | Shallow-merge new `props` into one component on a DRAFT screen. |
| `remove_component` | `screens:write` | Remove one component by id (walks the full component tree). Destructive (`confirm`). |
| `reorder_screens` | `screens:write` | Reorder a flow's DRAFT screens. `screen_order` must be a permutation of the existing draft screen ids. |
| `add_screen_from_pattern` | `screens:write` | Compose a new DRAFT screen from a catalog pattern and append or insert it into the flow. |
## Flow composition (2)
Generate whole flows and screens with AI. Composition spends AI credits (see [Build with AI](/docs/build-with-ai)).
| Tool | Required scope | Description |
| --------------- | -------------- | -------------------------------------------------------------------------------------------- |
| `compose_flow` | `flows:write` | Generate a complete DRAFT flow from a natural-language brief, optionally grounded on an app. |
| `list_patterns` | (token) | List the screen-pattern catalog available to `compose_flow` and `add_screen_from_pattern`. |
## Analytics escape hatch (1)
| Tool | Required scope | Description |
| ------------------------- | -------------- | ------------------------------------------------------------------------------ |
| `execute_analytics_query` | `sql:read` | Structured query over flow impressions, user activity, SDK events. Org-scoped. |
Not raw SQL -- the input is a typed JSON DSL (collection + match + group + sort + limit + project) that compiles to a Mongo aggregation on the backend. Org scoping is a non-skippable first stage. Operators `$where`, `$expr`, `$function`, and `$regex` are rejected at compile time. Default 100 rows, max 1000, capped at 10s via `maxTimeMS`.
## Named flow-analytics (7)
First-class tools for the most common "how is this flow performing" questions. All require `flows:read` and accept optional `from`/`to` ISO date filters.
| Tool | Required scope | Description |
| ------------------------------- | -------------- | -------------------------------------------------------------------------- |
| `get_flow_dashboard` | `flows:read` | Cheap unified payload (impressions, completions, drop-off, dismissals). |
| `get_flow_components_analytics` | `flows:read` | Per-component metrics -- which buttons/inputs convert vs drop users. |
| `get_flow_paths` | `flows:read` | Screen-transition paths and drop-offs. The closest thing to a funnel. |
| `get_flow_inputs` | `flows:read` | Aggregated NPS, ratings, form values from input components. |
| `get_flow_dismissals` | `flows:read` | How and where users dismiss the flow (close button, background tap, time). |
| `get_flow_impressions` | `flows:read` | Unique impressions, total impressions, frequency distribution. |
| `get_flow_timeseries` | `flows:read` | Bucketed metrics over time (`granularity: hour\|day\|week`). |
## Experiments (9)
Destructive ops (`complete_experiment`, `delete_experiment`) require `confirm: true`.
| Tool | Required scope | Description |
| ------------------------ | ------------------- | -------------------------------------------------------- |
| `list_experiments` | `experiments:read` | All experiments in the current org. |
| `get_experiment` | `experiments:read` | Metadata + variants + status for one experiment. |
| `get_experiment_results` | `experiments:read` | Per-variant metrics + statistical significance. |
| `create_experiment` | `experiments:write` | New DRAFT experiment with weighted variants. |
| `update_experiment` | `experiments:write` | Edit a DRAFT experiment (RUNNING must be paused first). |
| `start_experiment` | `experiments:write` | Transition DRAFT/PAUSED → RUNNING. |
| `pause_experiment` | `experiments:write` | Transition RUNNING → PAUSED. |
| `complete_experiment` | `experiments:write` | Terminal -- declare winner, can't be undone (`confirm`). |
| `delete_experiment` | `experiments:write` | DRAFT only, destructive (`confirm`). |
## Audience segments (6)
| Tool | Required scope | Description |
| -------------------- | ------------------- | ------------------------------------------------------------------ |
| `list_segments` | `segments:read` | All segments in current org, optionally filtered by app. |
| `get_segment` | `segments:read` | Segment metadata + condition tree. |
| `create_segment` | `segments:write` | New audience segment with condition tree. |
| `update_segment` | `segments:write` | Edit name, description, conditions. |
| `delete_segment` | `segments:write` | Destructive (`confirm`); blocked if any flow targets the segment. |
| `list_segment_users` | `endusers:read:pii` | Paginated end-user profiles matching the segment. **Returns PII.** |
## Conversion goals (5)
| Tool | Required scope | Description |
| -------------------------- | ------------------- | ------------------------------------------------------------------- |
| `list_conversion_goals` | `conversions:read` | Up to 5 goals per flow. |
| `create_conversion_goal` | `conversions:write` | Track an SDK event as a conversion (with optional property filter). |
| `update_conversion_goal` | `conversions:write` | Rename, retarget, change attribution window, toggle active. |
| `delete_conversion_goal` | `conversions:write` | Destructive (`confirm`). |
| `get_conversion_analytics` | `conversions:read` | Per-goal counts, conversion rate, time-to-convert, trend. |
## Tenant context (4)
| Tool | Required scope | Description |
| --------------------- | -------------- | ------------------------------------------------------------------------------------- |
| `list_organizations` | (token) | Lists every org the authenticated user belongs to (with role + member counts). |
| `switch_organization` | (token) | Returns a `reauth_required` envelope + `authorization_url` -- never quietly switches. |
| `list_apps` | `apps:read` | Lists apps in the current org. |
| `switch_app` | `apps:read` | Updates the in-process current-app hint and returns the resolved app profile. |
`switch_organization` does not make an API call. The MCP token is pinned to a `(user, org)` pair, so org switches require a fresh PKCE round-trip. App context is session-local (not a JWT claim) and clears on `sign_out`.
## App theme (6)
| Tool | Required scope | Description |
| ------------------------------ | -------------- | ----------------------------------------------------------------- |
| `get_app_theme` | `theme:read` | Full cross-platform theme document for the app. |
| `update_app_theme` | `theme:write` | Upsert the full theme. |
| `sync_theme_from_sdk` | `theme:write` | Merge an SDK-extracted theme payload into the stored theme. |
| `list_theme_tokens` | `theme:read` | Flat resolved token map for the app. |
| `validate_theme_accessibility` | (none) | Local WCAG 2.1 contrast check across M3 color pairs. No API call. |
| `compare_themes` | (none) | Local token-level diff between two flat token maps. No API call. |
## Localization (4)
| Tool | Required scope | Description |
| ----------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------- |
| `list_locales` | `locales:read` | Flat list of every (screen, locale, translations) row for a flow. Group client-side to answer "what languages?". |
| `get_flow_localization` | `locales:read` | All localization documents for a flow, optionally filtered to one screen. |
| `set_flow_localization` | `locales:write` | Upsert a single (screen, locale) translations dict. |
| `bulk_translate_flow` | `locales:write` | Persist a full translation pass -- one locale, every screen -- in one round-trip. |
## End-users (3)
| Tool | Required scope | Description |
| ------------------ | ----------------- | ----------------------------------------------------------------------------------------------------- |
| `list_end_users` | `endusers:read`\* | Paginated end-users for an app. Filter by search, segment, or identity. \*PII fields require `:pii`. |
| `get_end_user` | `endusers:read` | One end-user profile + recent SDK events. PII redacted unless token carries `endusers:read:pii`. |
| `list_user_events` | `endusers:read` | Recent SDK events for a user. Wraps the same profile endpoint focused on the embedded `events` array. |
App id is not a JWT claim -- pass `app_id` per call or call `switch_app` first to set the in-process default.
## Billing (4)
Read-only from MCP -- no `billing:write` scope exists. Granted to every role.
| Tool | Required scope | Description |
| ------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------ |
| `get_billing_period` | `billing:read` | Current subscription plan + billing cycle (monthly / yearly) + renewal anchor. |
| `get_usage_summary` | `billing:read` | Current-period MAU usage, plan limit, hard cap, overage charge, plus `periodStart` / `periodEnd` boundaries. |
| `get_billing_history` | `billing:read` | Historical billing periods (one entry per closed cycle). Pass `limit` to widen. |
| `list_subscription_plans` | `billing:read` | All plans available on the platform (Free / Launch / Growth / Scale) with limits, prices, features. |
The MCP-view projection strips internal payment-provider variant ids and card metadata. The dashboard view sees the full payload.
## Org & team management (5)
Destructive op (`remove_member`) requires `confirm: true`.
| Tool | Required scope | Description |
| --------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `list_org_members` | `team:read` | Roster of users in the current org with roles. |
| `invite_member` | `team:write` | Send an invitation email for `admin` / `member` / `viewer` (owner is not invitable). |
| `update_member_role` | `team:write` | Change a member's role. Cannot promote to / demote from `owner` -- that's a separate transfer-ownership flow. |
| `remove_member` | `team:write` | Remove a user's membership. **Destructive (`confirm`).** |
| `update_organization` | (owner role, no MCP scope) | Update name, size, or transfer ownership. **Owner-only on the backend** -- non-owners get a `forbidden` error. |
## Integrations (3)
| Tool | Required scope | Description |
| --------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `list_integrations` | `integrations:read` | Webhook + analytics integrations (Segment, Amplitude, Mixpanel, raw webhook) configured for the org. |
| `get_integration` | `integrations:read` | One integration's config + event filter + enabled state. Resolved client-side from the list endpoint. |
| `dispatch_test_event` | `integrations:write` | Fire a synthetic test event through the integration's delivery pipeline. Backend constructs the test payload server-side. Not destructive. |
## Flow sharing & OneLink (3)
Destructive op (`regenerate_share_token`) requires `confirm: true`.
| Tool | Required scope | Description |
| ------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get_share_settings` | `share:read` | Three independent toggles (public link, copy, preview), share token, share URL, OneLink preview URL, and view/copy counts. Auto-creates the share document on read. |
| `update_share_settings` | `share:write` | Toggle `is_public_link_enabled`, `is_copy_enabled`, `is_preview_enabled` independently. Pass only the fields you want to change. |
| `regenerate_share_token` | `share:write` | Rotate the share token, **invalidating the live shared URL.** Destructive (`confirm`). |
## Next steps
- [Use Setgreet with AI agents](/docs/mcp) -- install for your client and authorize.
- [Recipes](/docs/mcp/recipes) -- end-to-end agent workflows that string these tools together.
- [Troubleshooting](/docs/mcp/troubleshooting) -- error codes returned by these tools and how to resolve them.
---
# Troubleshooting
Source: https://www.setgreet.com/docs/mcp/troubleshooting
> Common MCP errors and how to resolve them.
# Troubleshooting
Every MCP tool returns structured errors. Each error has a stable `code` field -- match against the codes below to find the resolution.
## `authentication_required`
> The MCP server has no valid access token for this session.
**What it means.** Either you've never signed in, your refresh token has been revoked, or local token storage was cleared (e.g. you reinstalled the server).
**How to fix it.**
1. Call the `sign_in` tool. The agent surfaces an `authorization_url`.
2. Open the URL in your browser and complete the Firebase sign-in.
3. The browser redirects back with `code` + `state`. Pass them to `complete_sign_in` (the agent usually handles this automatically if it's an interactive client).
4. Retry the original tool call.
## `scope_required`
> Your token does not carry the scope this tool needs.
**What it means.** Scopes are derived from your role at token issuance. A viewer's token can never call `flows:write` tools -- the scope is absent from the JWT, not just blocked at the API.
**How to fix it.**
1. Call `whoami` to see your current role and the scopes your token actually carries.
2. If your role is too low, ask your org owner to bump it via **Settings → Team** in the dashboard.
3. After the role change, sign out and sign in again to mint a fresh token -- role changes take effect on the next refresh (within 5 minutes), but signing back in is the deterministic path.
Your dashboard role is authoritative for what your token can do. Admins and owners can manage segments, experiments, integrations, and team; members can edit flows, themes, and locales; viewers are read-only. See the [MCP Server overview](/docs/mcp#how-scopes-work) for the short summary.
## `confirmation_required`
> The agent called a destructive operation without `confirm: true`.
**What it means.** Destructive tools (`delete_flow`, `unpublish_flow`, `complete_experiment`, `delete_experiment`, `delete_segment`, `delete_conversion_goal`, `remove_member`, `regenerate_share_token`) refuse to execute unless the agent explicitly passes `confirm: true`. The MCP layer rejects the call **before** it hits the API.
**How to fix it.** The agent should surface the operation summary to you and ask permission. Once you confirm, it should retry with `confirm: true`.
If the agent never asks (e.g. an autonomous workflow), you need to either grant blanket permission in your client's tool-use policy or have the workflow whitelist the confirmation explicitly. Setgreet will not bypass this server-side -- it's a hard requirement.
## `plan_required`
> Your organization's plan does not include MCP access.
**What it means.** MCP access is included on **every plan, including Free**, so in practice this only appears when the
organization has no subscription plan assigned at all -- an account in a broken or partially-provisioned state. The
check runs at three layers:
1. At sign-in (`POST /authorize/complete`).
2. At every token refresh.
3. At every API request via the plan-gate middleware (60-second cache).
**How to fix it.** Open **Settings → Billing** in the dashboard to confirm a plan is assigned, and
[contact support](/contact) if it is not. The error payload includes an `upgrade_url`:
```json
{
"code": "plan_required",
"upgrade_url": "https://app.setgreet.com/settings/billing"
}
```
Once a plan is assigned, the next MCP request succeeds -- plan-mutation paths invalidate the cache synchronously, so
you don't need to wait for the 60-second TTL.
## `org_mismatch`
> Your token is bound to a different organization than the resource you're touching.
**What it means.** MCP tokens are pinned to a single `(user, org)` pair. The backend enforces this at the URL layer via `enforceMCPOrgInPath` -- a token issued for org A cannot hit `/organizations/B/...` paths, even if you happen to be a member of both.
**How to fix it.** You need to switch orgs deliberately:
1. Call `list_organizations` to see every org you belong to.
2. Call `switch_organization` with the target `org_id`. **This does not silently re-issue a token.** It returns a `reauth_required` envelope with a fresh `authorization_url`.
3. Open the URL in your browser and sign in again. The new token is bound to the new org.
The PKCE round-trip is intentional -- a quiet org switch would let a compromised token escalate from a small org to a large one.
## Tool not found
> The agent says "I don't have a tool called `X`".
**What it means.** Almost always: your role doesn't carry the scope that gates the tool, and the client filtered it out of the tool list. Some clients display the tool but fail the call; others omit it entirely.
**How to fix it.**
1. Run `whoami` and check the `scopes` array.
2. Cross-reference against the [tool catalog](/docs/mcp/tools) -- find the row for the tool you wanted and see what scope it needs.
3. If your role is too low, see [scope_required](#scope_required).
A related case: if you've recently upgraded your plan or role, sign out and sign in again to refresh the tool list -- some clients cache the catalog per session.
## `forbidden`
> The endpoint requires a role you don't have, even though your scope checks out.
**What it means.** A few backend routes have role gates on top of scopes -- most commonly `update_organization` (rename, transfer ownership, change size), which is **owner-only** even for admins with `org:write`.
**How to fix it.** Ask the org owner to perform the action. There is no MCP-side workaround for owner-only paths.
## Token refresh / 401 loops
> Tools fail intermittently with `authentication_required` even though you signed in recently.
**What it means.** The MCP server tried to refresh the access token and the refresh failed. Common causes:
- The refresh token was revoked from **Settings → MCP Connections** in the dashboard.
- Your role was demoted to a level that no longer has plan access (rare).
- Your organization lost its subscription plan since the last successful refresh.
- The MCP server's clock is significantly skewed from the backend's.
**How to fix it.**
1. Call `sign_out` to clear the broken session.
2. Call `sign_in` to start fresh.
3. If the new sign-in returns `plan_required`, see [plan_required](#plan_required).
## Sign-out doesn't clear other clients
This is by design. `sign_out` revokes only the refresh token associated with the current session. If you used the same OAuth client from two terminals, signing out of one does not sign out the other -- each terminal has its own refresh token family.
To sign out **everywhere**, use the dashboard:
- **Settings → MCP Connections → Revoke all sessions** -- this hits `DELETE /api/v1/me/mcp-sessions?confirm=true`, which revokes every active refresh token across every client and every org.
## My access token expired in the middle of a long operation
Access tokens live for 5 minutes. The OAuth-aware HTTP client in the MCP server automatically refreshes on 401, so this should be invisible. If you see explicit `token_expired` errors:
1. The refresh token may have been revoked -- see [Token refresh / 401 loops](#token-refresh--401-loops).
2. The system clock on the MCP server host may be wrong -- check with `date -u` and compare against [time.is](https://time.is).
## Still stuck?
- Run `whoami` and capture its full output -- the resolved scopes are the fastest way to diagnose authorization errors.
- Reach out via the support channels listed at [setgreet.com](https://setgreet.com).
---
# Amplitude
Source: https://www.setgreet.com/docs/integrations/amplitude
> Forward events to Amplitude for product analytics.
# Amplitude
The Amplitude integration sends Setgreet events directly to your Amplitude project, so you can analyze in-app flow performance alongside the rest of your product analytics.
## How it works
When enabled, Setgreet forwards events to Amplitude using the Amplitude HTTP V2 API. Events appear in your Amplitude project as standard events, ready for charts, funnels, and cohort analysis.
## Setup
1. Click **Integrations** in the sidebar.
2. Click **Add Integration** and select **Amplitude**.
3. Enter your **Amplitude API Key**. Find this in your Amplitude project under **Settings > Projects > Your Project**.
4. Select which **event types** to forward.
5. Click **Save**.
## Event mapping
Setgreet sends events to Amplitude using the same event names documented in the [event types](/docs/integrations#event-types) table (for example, `flow_completed`, `flow_component_click`).
Each Amplitude event includes these properties:
- `flowId` -- the Setgreet flow ID.
- `flowName` -- the human-readable flow name.
- `screenId` / `screenName` -- the screen for screen and component events.
- `componentId` / `componentType` / `componentLabel` -- the component for component events.
- `sessionId` -- the SDK session identifier.
- `appId` / `organizationId` -- the app and organization.
- `custom` -- additional event-specific data (selected option, input value, rating score, etc.).
The `user_id` field is set to the end user's ID as identified in the Setgreet SDK.
## Use cases
- **Funnel analysis.** Build an Amplitude funnel that starts with `flow_started` and ends with a product event (like `subscription_started`) to measure the impact of your in-app flows on conversions.
- **Cohort comparison.** Compare the behavior of users who completed an onboarding flow versus those who dismissed it.
- **Retention impact.** Use Amplitude's retention charts to see if users who interact with specific flows have better long-term retention.
Events are sent server-side from Setgreet, so they are attributed to the same user ID your app uses. Make sure the
user ID you pass to `Setgreet.identifyUser()` matches the user ID in Amplitude for accurate attribution.
---
# Integrations
Source: https://www.setgreet.com/docs/integrations
> Forward events to your analytics and data tools.
# Integrations
Integrations let you forward Setgreet events to the third-party tools your team already uses. Instead of keeping flow interaction data locked inside Setgreet, you can pipe it into your analytics platform, data warehouse, or custom backend.
## Supported integrations
| Integration | Description |
| ----------------------------------------- | --------------------------------------------------------------- |
| [Webhooks](/docs/integrations/webhooks) | Send events to any HTTP endpoint you control. |
| [Segment](/docs/integrations/segment) | Forward events to Segment for routing to 300+ downstream tools. |
| [Amplitude](/docs/integrations/amplitude) | Send events directly to Amplitude for product analytics. |
| [Mixpanel](/docs/integrations/mixpanel) | Send events directly to Mixpanel for behavioral analytics. |
## Event types
Setgreet dispatches the following events to configured integrations:
| Event | Description |
| ------------------------- | ---------------------------------------------- |
| `flow_started` | A flow started for a user. |
| `flow_completed` | A user reached the final screen of a flow. |
| `flow_dismissed` | A user dismissed a flow before completing it. |
| `flow_screen_viewed` | A user viewed a specific screen within a flow. |
| `flow_screen_transition` | A user navigated between screens. |
| `flow_component_view` | A component was rendered on screen. |
| `flow_component_click` | A button or tappable element was pressed. |
| `flow_component_input` | A text field or form input received a value. |
| `flow_component_rating` | A user submitted a star rating. |
| `flow_component_nps` | A user submitted an NPS score. |
| `flow_component_video` | A video component interaction. |
| `flow_component_carousel` | A carousel swipe or navigation action. |
| `flow_component_custom` | A custom component interaction. |
Each event includes standard metadata: user ID, flow ID, screen ID, component ID (where applicable), timestamp, and any custom properties.
## Per-integration event filtering
You do not have to forward every event to every integration. Each integration has its own event filter, so you can choose exactly which events to send.
For example, you might:
- Send all events to your **Webhook** for a complete audit trail.
- Send only `flow_completed` to **Amplitude** to keep your analytics focused on outcomes.
- Send component interaction events to **Mixpanel** for detailed behavioral analysis.
## Setting up integrations
1. Click **Integrations** in the sidebar.
2. Click **Add Integration**.
3. Select the integration type.
4. Provide the required credentials (API key, write key, webhook URL, etc.).
5. Select which events to forward.
6. Save and activate.
Integration events are sent server-side from Setgreet's backend, not from the mobile SDK. This means events are
delivered reliably even if the user closes the app immediately after an interaction.
---
# Mixpanel
Source: https://www.setgreet.com/docs/integrations/mixpanel
> Forward events to Mixpanel for behavioral analytics.
# Mixpanel
The Mixpanel integration sends Setgreet events to your Mixpanel project, enabling you to analyze in-app flow interactions alongside your existing behavioral analytics.
## How it works
When enabled, Setgreet sends events to Mixpanel using the Mixpanel Ingestion API. Events appear in your Mixpanel project with full properties, ready for segmentation, funnels, and flow analysis.
## Setup
1. Click **Integrations** in the sidebar.
2. Click **Add Integration** and select **Mixpanel**.
3. Enter your **Mixpanel Project Token**. Find this in your Mixpanel project under **Settings > Project Settings**.
4. Select which **event types** to forward.
5. Click **Save**.
## Event mapping
Setgreet sends events to Mixpanel using the same event names documented in the [event types](/docs/integrations#event-types) table (for example, `flow_completed`, `flow_component_click`).
Each Mixpanel event includes these properties:
- `flowId` -- the Setgreet flow ID.
- `flowName` -- the human-readable flow name.
- `screenId` / `screenName` -- the screen for screen and component events.
- `componentId` / `componentType` / `componentLabel` -- the component for component events.
- `sessionId` -- the SDK session identifier.
- `appId` / `organizationId` -- the app and organization.
- `custom` -- additional event-specific data (selected option, input value, rating score, etc.).
The `distinct_id` field is set to the end user's ID as identified in the Setgreet SDK.
## Use cases
- **Behavioral segmentation.** Create Mixpanel cohorts based on Setgreet flow interactions -- for example, "users who completed the onboarding flow in the last 7 days."
- **Funnel analysis.** Build funnels that combine Setgreet events with your product events to measure end-to-end conversion.
- **Flow optimization.** Use Mixpanel's Flows report to visualize the sequence of events before and after a Setgreet flow display, revealing how flows fit into the broader user journey.
Make sure the user ID you pass to `Setgreet.identifyUser()` matches the `distinct_id` you use in Mixpanel so that
Setgreet events are attributed to the correct user profile.
---
# Segment
Source: https://www.setgreet.com/docs/integrations/segment
> Forward events to Segment for unified customer data.
# Segment
The Segment integration forwards Setgreet events to your Segment workspace, where they can be routed to any of Segment's 300+ downstream destinations -- data warehouses, analytics tools, marketing platforms, and more.
## How it works
When enabled, Setgreet sends events to Segment using the Segment HTTP Tracking API. Each Setgreet event becomes a Segment `track` call, allowing you to combine Setgreet flow data with the rest of your customer data in a single platform.
## Setup
1. Click **Integrations** in the sidebar.
2. Click **Add Integration** and select **Segment**.
3. Enter your **Segment Write Key**. You can find this in your Segment workspace under **Sources > Your Source > Settings > API Keys**.
4. Select which **event types** to forward.
5. Click **Save**.
Create a dedicated Segment source for Setgreet (for example, "Setgreet Production") to keep the data organized and
make it easy to manage permissions.
## Event mapping
Setgreet sends events to Segment using the same event names documented in the [event types](/docs/integrations#event-types) table. Each Setgreet event becomes a Segment `track` call with:
- **userId** -- the end user's ID as identified in the SDK.
- **event** -- the event name (for example, `flow_completed`, `flow_component_click`).
- **properties** -- flow ID, flow name, screen ID, component details, platform, and any interaction values in a `custom` object.
- **timestamp** -- when the event occurred.
## Filtering events
You do not have to send every event type to Segment. On the integration settings page, toggle individual event types on or off. For example, if you only care about flow outcomes in your data warehouse, enable `flow_completed` and disable the rest.
## Use cases
- **Unified customer profiles.** Combine Setgreet flow interaction data with product usage, marketing touchpoints, and support interactions in a single Segment profile.
- **Trigger downstream actions.** Route `flow_completed` events from Segment to your email tool (Braze, Iterable, etc.) to send a follow-up message after onboarding.
- **Warehouse sync.** Send Setgreet events to BigQuery, Snowflake, or Redshift via Segment for custom reporting and analysis.
---
# Webhooks
Source: https://www.setgreet.com/docs/integrations/webhooks
> Send Setgreet events to custom webhook endpoints.
# Webhooks
Webhooks let you receive Setgreet events at any HTTP endpoint you control. This is the most flexible integration -- use it to pipe events into your own backend, a data warehouse, a Slack channel via Zapier, or any other system that accepts HTTP requests.
## How webhooks work
When an event occurs in Setgreet (a flow is displayed, a user completes a flow, a component is interacted with), Setgreet sends an HTTP POST request to your webhook URL with the event payload.
## Setup
1. Click **Integrations** in the sidebar.
2. Click **Add Integration** and select **Webhook**.
3. Enter your **Webhook URL** -- the endpoint that will receive events.
4. Optionally set a **Secret** to sign payloads with HMAC-SHA256.
5. Select which **event types** to forward (see [event types](/docs/integrations#event-types)).
6. Click **Save**.
## Payload format
Every webhook request is an HTTP POST with a JSON body that batches one or more events inside a delivery envelope:
```json
{
"events": [
{
"event": "flow_completed",
"timestamp": "2026-06-15T14:32:10.123Z",
"userId": "user_abc123",
"properties": {
"flowId": "flow_xyz789",
"flowName": "Onboarding Walkthrough",
"screenId": "screen_456",
"screenName": "Welcome",
"componentId": "comp_789",
"componentType": "button",
"componentLabel": "Get Started",
"sessionId": "session_abc",
"appId": "app_def",
"organizationId": "org_ghi",
"custom": {}
},
"context": {
"source": "setgreet",
"platform": "ios"
}
}
],
"deliveredAt": "2026-06-15T14:32:10.500Z",
"source": "setgreet"
}
```
### Envelope fields
| Field | Description |
| ------------- | ---------------------------------------------------------------- |
| `events` | An array of one or more event objects delivered in this request. |
| `deliveredAt` | ISO 8601 timestamp of when Setgreet sent this delivery. |
| `source` | Always `"setgreet"`. |
### Event object fields (inside `events[]`)
| Field | Description |
| --------------------------- | -------------------------------------------------------------------------- |
| `event` | The event type string (see [event types](/docs/integrations#event-types)). |
| `timestamp` | ISO 8601 timestamp of when the event occurred. |
| `userId` | The end user's ID (as set via the SDK `identifyUser` call). |
| `properties.flowId` | The ID of the flow involved. |
| `properties.flowName` | The human-readable name of the flow. |
| `properties.screenId` | The screen ID (present for screen-level and component-level events). |
| `properties.screenName` | The screen's display name. |
| `properties.componentId` | The component ID (for component events). |
| `properties.componentType` | The component's type (e.g., `button`, `rating`, `textInput`). |
| `properties.componentLabel` | A human-readable label for the component. |
| `properties.sessionId` | The SDK session identifier. |
| `properties.appId` | The app the event originated from. |
| `properties.organizationId` | The organization the app belongs to. |
| `properties.custom` | Additional event-specific data (component values, ratings, etc.). |
| `context.source` | Always `"setgreet"`. |
| `context.platform` | The platform the event came from (e.g., `ios`, `android`). |
For high-volume apps, multiple events may be batched into a single delivery. Always iterate over the `events` array
instead of assuming a single event per request.
## Signing and verification
If you set a webhook secret, Setgreet signs every payload with an **HMAC-SHA256** signature computed from the raw JSON request body and your secret. The signature is sent in the `X-Setgreet-Signature` HTTP header as a lowercase hex-encoded digest.
Verify the signature on your server by computing the HMAC-SHA256 of the raw request body with the same secret and comparing it to the header. This confirms the request really came from Setgreet and the body was not modified in transit.
## Retry behavior
If your endpoint returns a non-2xx status code or times out, Setgreet retries delivery automatically:
- Each request has a **10-second timeout**. If your endpoint does not respond within that window, it counts as a failure.
- Failed deliveries are retried up to **3 times** with fixed delays of **1 second, 10 seconds, and 60 seconds** between attempts.
- A `Retry-After` response header is honored when present, overriding the default delay for that attempt.
- After all retries are exhausted, the event is moved to the dead-letter queue and no longer retried.
## Monitoring delivery status
On the integration detail page you can check delivery health:
- **Recent deliveries** -- a list of recent webhook calls with their HTTP status codes and timestamps.
- **Delivery chart** -- a visualization of delivery volume and failure rate over time.
- **Dead-lettered events** -- events that exhausted all retries, available for inspection.
If an integration repeatedly fails, Setgreet may automatically pause it to avoid wasted requests. You can unpause it once the endpoint is healthy again.
## Best practices
- **Respond quickly.** Your endpoint should return a 200 status code as fast as possible. Process the event asynchronously if needed -- acknowledge receipt first, then handle the data.
- **Validate the payload.** Check the `event` field and structure before processing. Ignore unknown event types gracefully to avoid breaking when new events are added.
- **Verify the HMAC signature** if you configured a secret, especially for webhooks exposed to the public internet.
- **Use a queue.** For high-volume apps, have your webhook endpoint push events into a message queue (SQS, RabbitMQ, etc.) and process them with a worker.
---
# Displaying Flows
Source: https://www.setgreet.com/docs/sdk/displaying-flows
> Control how and when flows appear in your app.
# Displaying Flows
Flows can appear in your app automatically based on trigger conditions, or you can show them manually by ID. This page explains both approaches and the lifecycle callbacks you can listen for.
## Automatic display
By default, the SDK forwards user signals to the Setgreet API whenever the user state changes -- after an `identifyUser` call, an event track, or a screen track. The server evaluates trigger conditions for the current user and, if a flow matches, returns it in the response. The SDK then displays the returned flow automatically.
This is the recommended approach for most use cases. You configure targeting in the dashboard, and the SDK handles the rest.
### How trigger evaluation works
1. The SDK sends a signal (identify, track event, or screen view) to the Setgreet API.
2. The server checks all published flows to see if any trigger conditions match the current user state.
3. If a match is found, the server returns the flow in its response.
4. If multiple flows match, the server returns the one with the highest priority (set in the dashboard).
5. The SDK renders the returned flow on the device.
Trigger evaluation is server-side, so the SDK requires a network connection to show flows. Offline caching is not
supported today.
## Manual display
If you want to show a specific flow at a specific moment in your app's code, use the `showFlow` method with the flow's ID:
```swift
// iOS
Setgreet.showFlow(flowId: "flow_abc123")
```
```kotlin
// Android
Setgreet.showFlow("flow_abc123")
```
```typescript
// React Native
import { showFlow } from '@setgreet/react-native-sdk';
showFlow('flow_abc123');
```
```dart
// Flutter
await Setgreet.showFlow("flow_abc123");
```
Manual display ignores the flow's trigger conditions -- the flow is shown immediately regardless of user attributes, events, or segments.
You can find the flow ID in the dashboard by opening a flow -- it appears in the URL (for example,
`app.setgreet.com/flows/flow_abc123`).
## Flow callbacks
Register callbacks to listen for flow lifecycle events. This lets your app respond to what happens during a flow -- for example, navigating to a screen after the flow closes, or logging a custom event.
```swift
// iOS
Setgreet.shared.setFlowCallbacks { callbacks in
callbacks
.onFlowStarted { event in
print("Flow started: \(event.flowId)")
}
.onFlowCompleted { event in
print("Flow completed: \(event.flowId)")
}
.onFlowDismissed { event in
print("Flow dismissed: \(event.flowId), reason: \(event.reason)")
}
.onActionTriggered { event in
print("Action triggered on flow \(event.flowId)")
}
}
```
```kotlin
// Android
Setgreet.setFlowCallbacks {
onFlowStarted { event ->
println("Flow started: ${event.flowId}")
}
onFlowCompleted { event ->
println("Flow completed: ${event.flowId}")
}
onFlowDismissed { event ->
println("Flow dismissed: ${event.flowId}, reason: ${event.reason}")
}
onActionTriggered { event ->
println("Action triggered on flow ${event.flowId}")
}
}
```
### Available lifecycle callbacks
| Callback | Description |
| ----------------------- | -------------------------------------------------------------------- |
| `onFlowStarted` | The flow began and the first screen appeared. |
| `onFlowCompleted` | The user reached the flow's final screen. |
| `onFlowDismissed` | The flow closed without being completed. |
| `onScreenChanged` | The user navigated to another screen within the flow. |
| `onActionTriggered` | A button or component action was triggered. |
| `onPermissionRequested` | A permission request action fired (notifications, location, camera). |
| `onTrackEvent` | An in-flow `Track Event` action fired. |
| `onCustomAction` | A custom action defined in the dashboard fired. |
| `onError` | An error occurred while loading or rendering the flow. |
## Dismiss reasons
When `onFlowDismissed` fires, the event includes a reason explaining why the flow closed:
| Reason | Description |
| -------------- | -------------------------------------------------------------- |
| `userClose` | The user tapped the close button. |
| `userSkip` | The user tapped a skip button. |
| `backPress` | The user pressed the hardware or system back button (Android). |
| `swipeDown` | The user swiped a bottom sheet flow down. |
| `replaced` | The flow was replaced by another flow with higher priority. |
| `programmatic` | The flow was closed by app code. |
| `completed` | The flow reached its end and closed naturally. |
## Action types
Buttons and interactive components can trigger these actions:
| Action type | Description |
| ------------------------------- | --------------------------------------------------------- |
| `next` | Navigate to the next screen in the flow. |
| `previous` | Navigate to the previous screen. |
| `skip` | Skip the current step and move on. |
| `dismiss` | Close the flow. |
| `url` | Open an external URL in the system browser. |
| `requestNotificationPermission` | Trigger the notification permission prompt. |
| `requestLocationPermission` | Trigger the location permission prompt. |
| `requestCameraPermission` | Trigger the camera permission prompt. |
| `requestReview` | Present the native "rate this app" prompt. |
| `share` | Open the native share sheet. |
| `openSettings` | Open the device settings page for the app. |
| `trackEvent` | Fire an event through the SDK's tracking pipeline. |
| `custom` | A custom action handled by your app via `onCustomAction`. |
Use these action callbacks to integrate flow outcomes with your app's navigation, state management, or business logic.
---
# Event Tracking
Source: https://www.setgreet.com/docs/sdk/event-tracking
> Track events and screen views for targeting and analytics.
# Event Tracking
Events are the signals that tell Setgreet what your users are doing in your app. By tracking events, you unlock event-based flow triggers, richer analytics, and integration forwarding.
## Tracking custom events
Use the `trackEvent` method to record an event with an optional properties dictionary:
```swift
// iOS
Setgreet.trackEvent(event: "purchase_completed", properties: [
"amount": 49.99,
"currency": "USD",
"plan": "pro"
])
```
```kotlin
// Android
Setgreet.trackEvent("purchase_completed", mapOf(
"amount" to 49.99,
"currency" to "USD",
"plan" to "pro"
))
```
```typescript
// React Native
import { trackEvent } from '@setgreet/react-native-sdk';
trackEvent('purchase_completed', {
amount: 49.99,
currency: 'USD',
plan: 'pro',
});
```
```dart
// Flutter
await Setgreet.trackEvent("purchase_completed", {
"amount": 49.99,
"currency": "USD",
"plan": "pro",
});
```
Event names should be descriptive and consistent. Use `snake_case` as a convention (for example, `feature_activated`, `profile_completed`, `item_added_to_cart`).
## Screen tracking
Track screen views to enable screen-based flow triggers. Call `trackScreen` whenever a user navigates to a new screen in your app:
```swift
// iOS
Setgreet.trackScreen(name: "settings")
```
```kotlin
// Android
Setgreet.trackScreen("settings")
```
```typescript
// React Native
import { trackScreen } from '@setgreet/react-native-sdk';
trackScreen('settings');
```
```dart
// Flutter
await Setgreet.trackScreen("settings");
```
Screen names should match across platforms if you want consistent targeting. For example, use `"home"` on both iOS and Android rather than `"HomeViewController"` on iOS and `"HomeActivity"` on Android.
## How events are used
Events power three key features:
### 1. Flow triggers
You can configure a flow to display when a specific event is fired. For example:
- Show a rating prompt when `order_delivered` fires.
- Show an upgrade nudge when `free_limit_reached` fires.
- Show a feature tour when `feature_page_viewed` fires.
Event-based triggers can include property filters. For example, trigger a flow only when `purchase_completed` fires AND `amount` is greater than `100`.
### 2. Analytics
Events contribute to [flow analytics](/docs/analytics/flow-analytics) and [conversion goals](/docs/analytics/conversion-goals). If a flow's conversion goal is `feature_activated`, Setgreet tracks how many users who saw the flow subsequently fired that event.
### 3. Integrations
Events can be forwarded to third-party tools through [integrations](/docs/integrations). Setgreet events (flow displays, completions, component interactions) are forwarded automatically. Custom events you track via `trackEvent` are available for matching against conversion goals.
The SDK batches events and sends them to the server periodically for efficiency. Events are persisted locally and
retried if the network is unavailable, so no data is lost.
## Best practices
- **Track meaningful moments.** Focus on events that represent user intent or milestones -- sign up, feature use, purchase, error encountered. Avoid tracking every tap or scroll.
- **Keep event names stable.** Changing event names breaks flow triggers that reference them. Treat event names as a contract between your app and your Setgreet configuration.
- **Use properties for context.** Instead of creating separate events like `plan_upgrade_pro` and `plan_upgrade_enterprise`, use a single `plan_upgraded` event with a `plan` property.
- **Track screens consistently.** Use the same screen names across iOS, Android, React Native, and Flutter so that screen-based triggers work across all platforms.
---
# SDK Overview
Source: https://www.setgreet.com/docs/sdk
> Understand how the Setgreet SDK renders native flows in your app.
# SDK Overview
The Setgreet SDK is a lightweight native library that you embed in your mobile app. It is the bridge between the flows you design in the dashboard and the screens your users see on their devices.
## How the SDK works
The SDK handles three core responsibilities:
1. **Forward user signals** -- when your app calls `identifyUser`, `trackEvent`, or `trackScreen`, the SDK sends that signal to the Setgreet API. The server evaluates trigger conditions for the current user and decides whether a flow should be shown.
2. **Render natively** -- when the API returns a matching flow, the SDK renders it using fully native UI components (SwiftUI on iOS, Jetpack Compose on Android). There are no web views or iframes. Screens use a flexbox-based layout system that adapts to any screen size automatically.
3. **Track events** -- flow displays, screen views, component interactions, and dismiss events are tracked and sent to the Setgreet API for [analytics](/docs/analytics).
## Dashboard and SDK relationship
The dashboard is where you **design and publish** flows. The SDK is where flows **come to life** on a user's device.
| Action | Where it happens |
| ------------------------- | --------------------- |
| Create and edit flows | Dashboard |
| Define trigger conditions | Dashboard |
| Publish a flow | Dashboard |
| Forward user signals | SDK (network call) |
| Evaluate triggers | Server (Setgreet API) |
| Render screens | SDK (native UI) |
| Collect interactions | SDK, sent to API |
This separation means your team can update flows, change targeting, and publish new experiences without touching app code or waiting for an app store release.
## What you need to implement
The SDK is designed to require minimal integration code. At a minimum, you need to:
1. **Initialize the SDK** with your App Key on app launch.
2. **Identify users** by calling `identifyUser` with a user ID and attributes after login.
3. **Track events** (optional) if you want to trigger flows based on custom events.
Everything else -- server evaluation of triggers, rendering screens, and tracking analytics -- is handled automatically.
The SDK requires a network connection to fetch flows. Offline flow caching is not supported today.
## Next steps
| Topic | What you will learn |
| ---------------------------------------------------- | ---------------------------------------------------------------- |
| [User Identification](/docs/sdk/user-identification) | How to identify users and set attributes. |
| [Event Tracking](/docs/sdk/event-tracking) | How to track custom events and screen views. |
| [Displaying Flows](/docs/sdk/displaying-flows) | How to control when and how flows appear. |
| [Platform SDKs](/docs/sdk/platforms) | Installation guides for iOS, Android, React Native, and Flutter. |
---
# Platform SDKs
Source: https://www.setgreet.com/docs/sdk/platforms
> Find SDK installation guides for iOS, Android, React Native, and Flutter.
# Platform SDKs
Setgreet provides native SDKs for the four major mobile platforms. Each SDK is published on GitHub with full installation and integration documentation.
## Supported platforms
| Platform | Language | Minimum version | Installation | Repository |
| ---------------- | ---------- | --------------------- | -------------------------------- | ---------------------------------------------------------------------------------- |
| **iOS** | Swift | iOS 15.0+ | Swift Package Manager, CocoaPods | [setgreet-ios-sdk](https://github.com/setgreet/setgreet-ios-sdk) |
| **Android** | Kotlin | Android 6.0+ (API 23) | Maven Central (Gradle) | [setgreet-android-sdk](https://github.com/setgreet/setgreet-android-sdk) |
| **React Native** | TypeScript | React Native 0.60+ | npm / yarn | [setgreet-react-native-sdk](https://github.com/setgreet/setgreet-react-native-sdk) |
| **Flutter** | Dart | Flutter 3.0+ | pub.dev | [setgreet-flutter-sdk](https://github.com/setgreet/setgreet-flutter-sdk) |
## Quick installation
### iOS (Swift Package Manager)
In Xcode, add a Swift package with the URL:
```
https://github.com/setgreet/setgreet-ios-sdk.git
```
Or add it to your `Package.swift`:
```swift
dependencies: [
.package(url: "https://github.com/setgreet/setgreet-ios-sdk.git", .upToNextMajor(from: "LATEST_VERSION"))
]
```
### Android (Gradle)
Add the dependency to your app-level `build.gradle.kts`:
```kotlin
dependencies {
implementation("com.setgreet:setgreet:LATEST_VERSION")
}
```
### React Native (npm)
```bash
npm install @setgreet/react-native-sdk
```
Or with yarn:
```bash
yarn add @setgreet/react-native-sdk
```
### Flutter (pub.dev)
Add to your `pubspec.yaml`:
```yaml
dependencies:
setgreet: ^1.0.0
```
Then run:
```bash
flutter pub get
```
## Initialization
After installing the SDK, initialize it on app launch with your App Key (found in the dashboard in your app's settings):
```swift
// iOS - in AppDelegate or App init
Setgreet.shared.initialize(appKey: "your_app_key", config: SetgreetConfig())
```
```kotlin
// Android - in Application.onCreate()
Setgreet.initialize(context, "your_app_key")
```
```typescript
// React Native - in App.tsx or index.js
import { initialize } from '@setgreet/react-native-sdk';
initialize('your_app_key');
```
```dart
// Flutter - in main.dart
import 'package:setgreet/setgreet.dart';
await Setgreet.initialize(appKey: 'your_app_key');
```
Each GitHub repository contains a complete integration guide with detailed setup instructions, example projects, and
troubleshooting tips. Start there for platform-specific guidance.
## What each SDK includes
All platform SDKs share the same core capabilities:
- **Flow rendering** -- fully native UI components with flexbox layout.
- **User identification** -- `identifyUser`, `resetUser`, and attribute management.
- **Event tracking** -- custom events (`trackEvent`) and screen views (`trackScreen`).
- **Trigger evaluation** -- local, on-device condition matching.
- **Analytics collection** -- automatic tracking of views, completions, and interactions.
- **Offline support** -- flows are cached locally and events are queued for delivery.
- **Flow callbacks** -- lifecycle events for integrating with your app logic.
## Next steps
- [User Identification](/docs/sdk/user-identification) -- learn how to identify users and set attributes.
- [Event Tracking](/docs/sdk/event-tracking) -- track custom events for triggering and analytics.
- [Displaying Flows](/docs/sdk/displaying-flows) -- control how and when flows appear.
---
# User Identification
Source: https://www.setgreet.com/docs/sdk/user-identification
> Identify users in your app for personalized experiences.
# User Identification
Identifying users is how you connect the people using your app to the targeting and personalization features in Setgreet. Once a user is identified, you can target them with flows based on their attributes, personalize content with their data, and track their behavior across sessions.
## The identifyUser call
The `identifyUser` method sets the current user's ID and optional attributes. Call it whenever you know who the user is -- typically after login or signup.
```swift
// iOS
Setgreet.identifyUser(userId: "user_123", attributes: [
"email": "jane@acme.com",
"plan": "pro",
"signupDate": "2026-01-15"
])
```
```kotlin
// Android
Setgreet.identifyUser("user_123", mapOf(
"email" to "jane@acme.com",
"plan" to "pro",
"signupDate" to "2026-01-15"
))
```
```typescript
// React Native
import { identifyUser } from '@setgreet/react-native-sdk';
identifyUser('user_123', {
email: 'jane@acme.com',
plan: 'pro',
signupDate: '2026-01-15',
});
```
```dart
// Flutter
await Setgreet.identifyUser(
userId: "user_123",
attributes: {
"email": "jane@acme.com",
"plan": "pro",
"signupDate": "2026-01-15",
},
);
```
The `userId` should be a stable, unique identifier from your system -- the same ID you use in your database or authentication provider. Do not use ephemeral identifiers like session tokens.
## Anonymous users
Before `identifyUser` is called, the SDK assigns an auto-generated **anonymous ID** to the device. This allows Setgreet to:
- Display flows that do not require user targeting (for example, a generic welcome screen).
- Track flow analytics for unidentified users.
- Associate pre-identification behavior with the user once `identifyUser` is called.
The anonymous ID is a UUID stored on the device and persists across app launches until `resetUser` is called.
Some platforms expose the anonymous ID if you need it for server-side correlation. For example, in Flutter:
```dart
final anonId = await Setgreet.anonymousId;
```
## Updating attributes
You can call `identifyUser` multiple times with the same `userId` to update attributes. Each call merges the new attributes with the existing ones -- it does not replace the full set.
```swift
// Update a single attribute after a plan change
Setgreet.identifyUser(userId: "user_123", attributes: [
"plan": "enterprise"
])
```
This is useful for keeping Setgreet in sync when user properties change mid-session (for example, after a plan upgrade or a profile update).
## Reset
Call `resetUser` when the user logs out. This clears the identified user and generates a new anonymous ID.
```swift
// iOS
Setgreet.resetUser()
```
```kotlin
// Android
Setgreet.resetUser()
```
```typescript
// React Native
import { resetUser } from '@setgreet/react-native-sdk';
resetUser();
```
```dart
// Flutter
Setgreet.resetUser();
```
After `resetUser`, the SDK behaves as if it is a fresh, unidentified device. Call `identifyUser` again when the next user logs in.
Always call `resetUser` on logout. If you skip this step and a second user logs in on the same device, flows may be
shown based on the previous user's attributes.
## When to identify
| Moment | Why |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **After login** | The most common place. You know the user ID and can pass current attributes. |
| **After signup** | Identify the user immediately after account creation so onboarding flows can be personalized. |
| **When attributes change** | Call `identifyUser` again with updated attributes so trigger conditions and variable bindings reflect the latest data. |
| **On app launch (if already authenticated)** | If the user is still logged in from a previous session, re-identify on launch to ensure the SDK has fresh attributes. |
## What counts as an MAU
Each unique `userId` (or anonymous ID if the user is not identified) that triggers at least one SDK request during a billing period counts as one Monthly Active User (MAU) for [billing](/docs/billing) purposes. Calling `identifyUser` itself does not consume additional MAU -- the user is counted once per billing period regardless of how many times `identifyUser` is called.
---
# Team Management
Source: https://www.setgreet.com/docs/team
> Manage your team and control access to your organization.
# Team Management
Every Setgreet account belongs to an **organization**. An organization is the workspace where your team collaborates on apps, flows, and settings. Team management lets you invite colleagues, assign roles, and control who can do what.
## How organizations work
When you sign up for Setgreet, you create an organization. Everything in Setgreet -- apps, flows, analytics, integrations, billing -- lives inside this organization. All team members share the same organization and see the same data, scoped by their role.
## Role-based access control
Setgreet uses a role hierarchy to control access. Each team member is assigned one of four roles, each with increasing permissions:
| Role | Summary |
| ---------- | -------------------------------------------------------------------------------------------- |
| **Viewer** | Read-only access to flows and analytics. Cannot create or modify anything. |
| **Member** | Can create and edit flows, manage apps, and view analytics. |
| **Admin** | Everything a Member can do, plus manage team members and organization settings. |
| **Owner** | Full access to everything, including billing, ownership transfer, and organization deletion. |
See [Roles & Permissions](/docs/team/roles-and-permissions) for a detailed breakdown of what each role can do.
## Inviting team members
Admins and Owners can invite new members by email from **Settings > Organization**. Invitations include a role assignment and expire after 7 days. See [Invitations](/docs/team/invitations) for details.
## Team member limits
The number of team members allowed depends on your plan:
| Plan | Team members |
| -------------------- | ------------ |
| **Free** | 1 |
| **Launch** ($49/mo) | 3 |
| **Growth** ($299/mo) | 10 |
| **Scale** ($799/mo) | Unlimited |
If you need more team members, upgrade your plan in **Settings > Billing**. New workspaces get 3 seats for the first
14 days, then return to the free plan's single seat.
The organization Owner counts toward the team member limit. On the free plan, the Owner is the only member.
---
# Invitations
Source: https://www.setgreet.com/docs/team/invitations
> Invite team members and manage pending invitations.
# Invitations
Invitations are how you add new team members to your Setgreet organization. Admins and Owners can send invitations by email, assigning a role at the time of invitation.
## Inviting a team member
1. Go to **Settings > Organization**.
2. Click **Invite Member**.
3. Enter the person's **email address**.
4. Select the **role** to assign (Viewer, Member, or Admin).
5. Click **Send Invitation**.
The invitee receives an email with a link to accept the invitation and join your organization. If they do not already have a Setgreet account, they will be prompted to create one.
Check your plan's team member limit before inviting. The free plan allows 1 member (the Owner only), Launch allows up
to 3, Growth up to 10, and Scale has no limit. You cannot send invitations that would exceed your plan's limit.
## Invitation expiration
Invitations expire after **7 days**. If the invitee does not accept within that window, the invitation becomes invalid.
### Resending invitations
If an invitation expires or the invitee did not receive the email:
1. Go to **Settings > Organization**.
2. Find the expired or pending invitation.
3. Click **Resend**. This sends a new email with a fresh 7-day expiration.
## Accepting an invitation
When an invitee clicks the link in the invitation email:
1. They are directed to Setgreet's sign-up or sign-in page.
2. After authenticating, they are added to the organization with the assigned role.
3. They can immediately see the organization's apps, flows, and data based on their role permissions.
## Declining an invitation
Invitees can decline an invitation by ignoring the email or letting it expire. There is no explicit "decline" action -- unused invitations simply expire after 7 days.
If you need to cancel a pending invitation before it expires, open **Settings > Organization**, find the pending invitation, and cancel it from the actions menu.
## Managing pending invitations
The **Settings > Organization** page shows all pending invitations alongside active team members. For each pending invitation, you can see:
- The invitee's email address.
- The assigned role.
- When the invitation was sent.
- When it expires.
## Transferring organization ownership
While not technically an invitation, ownership transfer is a related team management action. The current Owner can transfer ownership to any existing team member:
1. Go to **Settings > Organization**.
2. Click **Transfer Ownership**.
3. Select the target member and confirm.
The new Owner gets full access. The former Owner is demoted to Admin. See [Roles & Permissions](/docs/team/roles-and-permissions) for details on what each role can do.
Ownership transfer is immediate and cannot be undone by the former Owner. Make sure you are transferring to the right
person.
---
# Roles & Permissions
Source: https://www.setgreet.com/docs/team/roles-and-permissions
> Understand the role hierarchy and what each role can do.
# Roles & Permissions
Setgreet uses a four-level role hierarchy: **Viewer**, **Member**, **Admin**, and **Owner**. Each role inherits all permissions from the roles below it and adds additional capabilities.
## Role hierarchy
```
Viewer → Member → Admin → Owner
```
A Member can do everything a Viewer can, plus more. An Admin can do everything a Member can, plus more. The Owner has unrestricted access.
## Permission matrix
| Permission | Viewer | Member | Admin | Owner |
| ------------------------------- | ------ | ------ | ----- | ----- |
| View flows | Yes | Yes | Yes | Yes |
| View analytics | Yes | Yes | Yes | Yes |
| View team members | Yes | Yes | Yes | Yes |
| Create and edit flows | -- | Yes | Yes | Yes |
| Publish and unpublish flows | -- | Yes | Yes | Yes |
| Create and manage apps | -- | Yes | Yes | Yes |
| Manage integrations | -- | Yes | Yes | Yes |
| Create experiments | -- | Yes | Yes | Yes |
| Create and manage segments | -- | Yes | Yes | Yes |
| Invite team members | -- | -- | Yes | Yes |
| Remove team members | -- | -- | Yes | Yes |
| Change member roles | -- | -- | Yes | Yes |
| Manage organization settings | -- | -- | Yes | Yes |
| Manage billing and subscription | -- | -- | -- | Yes |
| Transfer organization ownership | -- | -- | -- | Yes |
| Delete organization | -- | -- | -- | Yes |
## Role details
### Viewer
Viewers have read-only access. They can browse flows, view analytics dashboards, and see team member lists, but they cannot create, edit, or publish anything. This role is ideal for stakeholders who need visibility without the ability to make changes.
### Member
Members are the standard role for team contributors. They can create and edit flows, manage apps, set up integrations, run experiments, and define audience segments. Members cannot manage other team members or access billing.
### Admin
Admins handle team operations. In addition to all Member capabilities, they can invite and remove team members, change roles (up to Admin), and manage organization settings like the organization name and default preferences.
Admins can assign roles up to Admin level. Only the Owner can promote someone to Owner (via ownership transfer).
### Owner
The Owner has unrestricted access to everything in the organization. This includes billing management, subscription changes, ownership transfer, and the ability to delete the organization entirely. There is exactly one Owner per organization.
## Changing roles
Admins and Owners can change a team member's role:
1. Go to **Settings > Organization**.
2. Click the role dropdown next to the member's name.
3. Select the new role.
Role changes take effect immediately. The affected member will see their updated permissions on their next page load.
## Transferring ownership
The Owner can transfer ownership to another team member:
1. Go to **Settings > Organization**.
2. Click **Transfer Ownership**.
3. Select the team member who will become the new Owner.
4. Confirm the transfer.
After the transfer, the former Owner is demoted to Admin. Ownership transfer cannot be undone -- the new Owner must transfer it back if needed.
---
# Form Data Collection
Source: https://www.setgreet.com/docs/personalization/form-data
> Collect user input within flows and save responses for targeting and personalization.
# Form Data Collection
Input components in Setgreet let you collect user responses inside a flow. Responses are tracked in analytics and, for branchable components, can be saved as user attributes for future flow targeting and personalization.
## The variableName property
Every [input component](/docs/components/input) -- TextInput, Dropdown, RadioGroup, Checkbox, MultiSelect, Slider, DatePicker -- and every feedback component (NPS, Rating) has a `variableName` property. This is the key used to identify the component's response in analytics events.
Set a clear, descriptive `variableName` (for example, `userRole` or `favoriteColor`) so you can recognize the response in your analytics dashboards.
The `variableName` is used for analytics labeling. It does **not** make the response available for use on later
screens within the same flow.
## Saving responses as user attributes
You can persist a response as a **user attribute** so it is available for future flows. This is configured on the **Branch node**, not on the input component itself.
### How it works
1. Add a **Branch node** to your flow that reads from a branchable component on the previous screen.
2. In the Branch node configuration, enable **Save to Attribute**.
3. Provide an attribute name (for example, `role`).
4. When the user selects an option and the flow reaches the Branch node, the selected value is saved as a user attribute with the name you provided.
Once saved, the attribute is available for:
- [Trigger conditions](/docs/flows/triggers) in future flows.
- [Variable bindings](/docs/personalization/variable-bindings) in future flows.
- [Segments](/docs/audience/segments) for audience grouping.
### Which components support Save to Attribute
Save to Attribute only works with **branchable components**:
- RadioGroup
- NPS
- Rating
- Dropdown
- Checkbox
- Button
It is **not** available for TextInput, Slider, DatePicker, or MultiSelect. Free-text and numeric inputs are captured in analytics only.
### Example: onboarding role selection
During onboarding, you ask users to pick a role with a RadioGroup that has three options: Designer, Developer, Product Manager.
1. Add a Branch node after the role selection screen.
2. Enable **Save to Attribute** and set the attribute name to `role`.
3. When a user selects "Designer", the attribute `role = "Designer"` is saved to their profile.
4. A later feature announcement flow can target users where `role equals "Designer"` using a [trigger condition](/docs/flows/triggers).
## Branching based on responses
RadioGroup and other branchable components can drive [flow branching](/docs/flows/branching). When you connect a Branch node to a screen containing a branchable component, each option value maps to a different path in the flow graph.
See [Branching](/docs/flows/branching) for the full setup.
## Viewing responses in analytics
Responses from all input components are tracked as `component_input` events and are available in flow analytics. The analytics surface shows:
- **Distribution by option** -- for branchable components (RadioGroup, NPS, Rating, Dropdown, Checkbox, Button), counts per option selected.
- **Input counts** -- the number of times each input component received a value.
Free-text responses from TextInput are captured as events but are not rendered as individual response lists in the current analytics UI.
## Best practices
- **Use descriptive variable names.** `userRole` is clearer than `q1` when reviewing analytics months later.
- **Keep attribute names consistent across flows.** If two flows collect the same concept (for example, role), configure Save to Attribute with the same attribute name so the data is unified on the user profile rather than duplicated.
- **Set required fields intentionally.** Mark inputs as required only when you genuinely need the data -- optional inputs reduce friction and improve completion rates.
- **Save only what you need.** Enable Save to Attribute only for responses you will actually use in future targeting or personalization. Not every response needs to be persisted.
- **Combine with branching.** The most effective flows collect a response early and branch the experience based on it -- creating personalized journeys from the user's own input.
---
# Personalization
Source: https://www.setgreet.com/docs/personalization
> Make flows dynamic with user data, multiple languages, and collected input.
# Personalization
Static screens get ignored. Personalized experiences convert. Setgreet gives you three tools to make every flow feel like it was built for the person seeing it.
## Three pillars of personalization
| Pillar | What it does | Example |
| ------------------------------------------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------ |
| [Variable Bindings](/docs/personalization/variable-bindings) | Insert user-specific data into component properties at render time. | "Welcome back, Sarah" instead of "Welcome back, user". |
| [Localization](/docs/personalization/localization) | Deliver flow content in your users' native language. | The same onboarding flow in English, Spanish, and Japanese. |
| [Form Data Collection](/docs/personalization/form-data) | Capture user input and feed it back into targeting and content. | Ask a preference question on screen 1, use the answer on screen 3. |
These three capabilities work together. You can localize a screen that contains variable bindings and collects form data -- all at the same time.
## Why personalization matters
Generic in-app messages are easy to dismiss. When a flow addresses users by name, speaks their language, and adapts based on what they have already told you, engagement goes up meaningfully.
- **Higher completion rates** -- users are more likely to finish a flow that feels relevant to them.
- **Better data quality** -- when you reference what you already know, users trust you with more.
- **Fewer flows, more impact** -- one personalized flow can replace several static variants.
Personalization relies on user attributes set through the SDK `identifyUser` call. See [User
Attributes](/docs/audience/user-attributes) for details on sending attribute data.
## Next steps
- [Variable Bindings](/docs/personalization/variable-bindings) -- connect user data to screen content.
- [Localization](/docs/personalization/localization) -- add multi-language support to your flows.
- [Form Data Collection](/docs/personalization/form-data) -- capture and reuse user input.
---
# Localization
Source: https://www.setgreet.com/docs/personalization/localization
> Deliver flow content in your users' native language with multi-language support.
# Localization
Setgreet supports multi-language flows so your in-app experiences reach users in their native language. You create a flow once in your default language, then add translations for any additional languages you need -- without duplicating the flow.
## How localization works
The base language of every flow is **English (`en-US`)** -- this is the content you write in the flow editor when you first build the flow. Localizations are stored as translation layers on top of the base content: for each additional language, you provide translated values for the text properties across the flow.
When the SDK renders a flow, it checks the device locale and selects the matching translation. If no translation exists for the device locale, the English base content is shown.
## Adding a localization
1. Open a flow in the flow builder.
2. Open the flow menu in the top bar and click **Localization**.
3. In the Localization modal, click **Add Language** and choose a target language.
4. Enter translated content for each text field across all screens in the flow (titles, body text, button labels, input placeholders, option labels).
5. Save to apply the translations.
Each additional language appears as its own column alongside the base language, so you can translate every screen's content in one place.
Setgreet supports 100+ languages. The language list uses standard locale codes (e.g., `en`, `es`, `fr`, `ja`, `de`,
`pt-BR`).
## Auto-translate with AI
Instead of filling in every field by hand, you can ask Setgreet's AI to translate a flow for you. Open the **Build with AI** chat in the editor and describe what you want, for example "translate this flow into French, German, and Japanese."
Before anything runs, the assistant shows you exactly what the job covers and what it costs:
1. It resolves the target languages (the ones you name, or your flow's current languages if you just say "translate the flow").
2. It counts the text it will translate and shows the credit cost in a confirmation card.
3. You confirm, and the translation runs. New languages are added automatically and the translated fields appear in the Localization modal, ready for you to review or tweak.
A few things worth knowing:
- **Fills the gaps by default.** Auto-translate only fills text that is missing in each target language, so your existing or hand-edited translations are left untouched. Ask it to "re-translate everything" if you want to overwrite.
- **Keeps your variables intact.** Binding expressions like `{{user.firstName}}` are preserved exactly in every translation, so personalized sentences keep working in each language.
- **You stay in control.** Auto-translate is a starting point. Every translated field stays fully editable in the Localization modal, so you can refine tone or fix nuance before you publish.
Credit cost scales with the size of the job (the amount of text times the number of languages), and is always shown
before you confirm. AI auto-translate is rolling out gradually; if you do not see it in the editor yet, it is not
enabled for your workspace.
## What gets localized
Localization applies to all text-based component properties:
| Component | Localizable properties |
| --------------------- | ----------------------------- |
| Text, Title, Subtitle | Text content |
| Button | Label text |
| TextInput | Placeholder text |
| Dropdown | Placeholder and option labels |
| RadioGroup | Option labels |
| Checkbox | Option labels |
| MultiSelect | Option labels |
| NPS | Labels and prompts |
| Rating | Labels |
Non-text properties like colors, sizes, layout settings, and images are not localized -- they stay the same across all languages.
## Language detection
The SDK automatically detects the user's device language and matches it to available localizations:
1. The SDK reads the device locale (e.g., `es-MX`).
2. It looks for an exact match in the flow's localizations.
3. If no exact match is found, the flow renders in the base language (English, `en-US`).
You do not need to write any code for language detection. The SDK handles it automatically.
The SDK currently does exact-match matching only. If a user's locale is `es-MX` and only `es` is available (or vice
versa), the flow falls back to the base language. Add the specific locale codes your users have to guarantee a match.
## Base language and fallback
**English (`en-US`) is the base language** of every flow. It is the content you build in the flow editor before adding any translations. The base language is always used when no translation matches the user's device locale.
Fallback happens in two places:
- **Locale not translated** -- if there is no translation for the user's locale on a screen, the entire screen renders in the base language.
- **Missing translation within a locale** -- if a specific text property was not translated for a locale (for example, a button label was skipped), that individual property renders in the base language while the rest of the translated content is shown.
This means partially translated screens still render correctly -- untranslated properties show the original English text rather than blank content.
When you edit the base (English) content of a screen after adding translations, the stored translations do **not**
update automatically. Because translations are keyed by component and property, the old translated value will still
override the new base text until you edit it in each language. Review all translations whenever you change original
content.
## Variable bindings in localized text
Localization and [variable bindings](/docs/personalization/variable-bindings) work together. When you translate a screen, include the same binding expressions in your translated text:
**English (base):**
```
Welcome back, {{user.firstName}}!
```
**Spanish translation:**
```
Bienvenido de nuevo, {{user.firstName}}!
```
**Japanese translation:**
```
おかえりなさい、{{user.firstName}}さん!
```
The SDK resolves the binding expressions after selecting the appropriate language, so the user sees their name in the correctly localized sentence.
## Best practices
- **Start with your primary language.** Build and test the entire flow in your default language first. Add translations once the content is stable.
- **Use a consistent base language.** Pick one base language across all flows (typically English) to keep your workflow predictable.
- **Translate all screens in a flow.** A flow where some screens are translated and others are not creates a jarring experience.
- **Account for text length.** Translations often differ in length. German text is typically longer than English; Japanese may be shorter. Preview your screens in each language to check for overflow or awkward spacing.
- **Keep binding expressions identical.** When translating, copy the `{{source.path}}` expressions exactly. A typo in a binding expression means the value will not resolve.
- **Update translations when base content changes.** There is no automatic sync between base content and translations. When you edit the original text, review all language versions.
---
# Variable Bindings
Source: https://www.setgreet.com/docs/personalization/variable-bindings
> Connect user attributes and event properties to component properties for dynamic, personalized content.
# Variable Bindings
Variable bindings let you insert real user data into your flow screens at render time. Instead of showing static text, you connect component properties to user attributes or event properties -- and the SDK resolves them on the device before the screen appears.
## How bindings work
A binding is an expression embedded in a component property value. When the SDK renders the screen, it replaces each expression with the actual value from the current user's context.
For example, a text component with the value:
```
Welcome back, {{user.firstName}}!
```
Renders as:
```
Welcome back, Sarah!
```
The SDK evaluates bindings client-side, so the resolved content never leaves the device.
## Binding syntax
Bindings use double curly braces with a source prefix and a property path:
```
{{source.propertyPath}}
```
### Binding sources
| Source | Prefix | Where data comes from |
| -------------- | ------- | --------------------------------------------------------- |
| User attribute | `user` | Attributes passed via the SDK `identifyUser` call. |
| Event property | `event` | Properties attached to the event that triggered the flow. |
### Examples
| Expression | Resolves to |
| ----------------------- | ----------------------------------------------------- |
| `{{user.firstName}}` | The user's `firstName` attribute. |
| `{{user.plan}}` | The user's `plan` attribute. |
| `{{event.productName}}` | The `productName` property from the triggering event. |
| `{{user.company.name}}` | Nested attribute access using dot notation. |
## Adding a binding in the dashboard
1. Select a component on the screen editor.
2. Click the **binding icon** next to a text property (e.g., the text content of a Text component or the label of a Button).
3. Choose the binding source: **User Attribute** or **Event Property**.
4. Select or type the attribute key.
5. Optionally set a fallback value and a format.
You can also type the binding expression directly into any text property field using the `{{source.path}}` syntax.
## Fallback values
When the SDK cannot resolve a binding -- for example, the user does not have a `firstName` attribute set -- it uses the **fallback value** you define. If no fallback is set, the binding resolves to an empty string.
Always set fallback values for bindings that may not be available for all users:
| Binding | Fallback | Result if attribute is missing |
| -------------------- | -------- | ------------------------------ |
| `{{user.firstName}}` | `there` | "Welcome back, there!" |
| `{{user.plan}}` | `Free` | "Your current plan: Free" |
| `{{event.discount}}` | `10%` | "Save 10% today" |
If a binding has no fallback and the attribute is missing, the expression resolves to an empty string. This can result
in awkward text like "Welcome back, !" -- always set fallback values for user-facing content.
## Formatting options
Bindings support format modifiers that transform the resolved value before rendering. Add a format by appending a pipe character after the property path:
```
{{source.propertyPath|format}}
```
### Available formats
| Format | Input | Output |
| ------------ | ---------------------- | -------------- |
| `currency` | `49.99` | `$49.99` |
| `percentage` | `0.25` | `25%` |
| `number` | `1234567` | `1,234,567` |
| `date` | `2026-06-15T00:00:00Z` | `Jun 15, 2026` |
| `uppercase` | `pro` | `PRO` |
| `lowercase` | `ENTERPRISE` | `enterprise` |
### Formatting examples
```
Your balance: {{user.accountBalance|currency}}
→ Your balance: $1,249.00
You have completed {{user.progress|percentage}} of onboarding
→ You have completed 75% of onboarding (attribute value: 0.75)
Member since {{user.signupDate|date}}
→ Member since Jan 15, 2026
Current plan: {{user.plan|uppercase}}
→ Current plan: PRO
```
Currency and number formats respect the user's locale when available.
## Where bindings work
You can use binding expressions in most string-type component properties:
- **Text content** -- the primary text of Text, Title, and Subtitle components.
- **Button labels** -- the label text of Button components.
- **Image URLs** -- dynamically load different images per user.
- **Placeholder text** -- input field placeholders.
- **Link URLs** -- web URLs opened from buttons.
- **Any string property** -- if the property accepts a string, it supports bindings.
Bindings are evaluated after localization. If you localize a screen, put binding expressions in the translated text
too -- the SDK resolves bindings regardless of language.
## Combining multiple bindings
A single property can contain multiple binding expressions mixed with static text:
```
Hi {{user.firstName}}, your {{user.plan|uppercase}} plan renews on {{user.renewalDate|date}}.
```
Each expression is resolved independently.
## Best practices
- **Set fallbacks for every user-facing binding.** Not all users will have every attribute.
- **Use descriptive attribute keys.** `firstName` is clearer than `fn` when building flows months later.
- **Test with missing data.** Preview your flow without attributes to see how fallbacks render.
- **Keep bindings in localized text.** When translating screens, include the same binding expressions in the translated strings.
- **Send attributes before flows trigger.** Call `identifyUser` with up-to-date attributes before the SDK evaluates flow triggers, so bindings have fresh data.
---
# How many onboarding screens should your app have?
Source: https://www.setgreet.com/blog/how-many-onboarding-screens
Published: 2026-07-29
> The standard advice says 3 to 5 screens. Public data from 910 top iOS subscription apps suggests the real median is about double that. Here is what onboarding length actually looks like, category by category.
Short answer: probably more than the blogs told you. The standard advice is 3 to 5 onboarding screens. But directional estimates derived from a public third-party dataset of 910 top iOS subscription apps (a July 2026 snapshot of the [Open Paywall Gallery](https://github.com/paywallpro/paywall-gallery) published by PaywallPro) put the median onboarding plus walkthrough length between 8 and 17 screens, depending on category. Not one measured category had a median inside the classic 3-to-5 range.
Those figures are that third party's estimates, not our measurements, and preview screen counts are a proxy for flow length rather than an exact count. But the direction is hard to argue with: the apps at the top of the App Store run much longer onboarding than the conventional wisdom recommends.
## Where the 3 to 5 screen rule came from
The 3-to-5 rule dates from the era of the swipeable intro carousel: three decorative slides explaining features you had not used yet, ending in a Get Started button. For that format, shorter really was better, because every slide was a speed bump between the user and the product. If your onboarding is a passive feature tour, keep it tiny. Better yet, cut it entirely.
But that is not what modern onboarding is. The flows topping the charts today are active, not passive. They ask questions, personalize the experience, set expectations, request permissions at the right moment, and build up to a paywall that makes sense by the time it appears. Each screen does one job. Measured against that format, screen count stops being a proxy for friction.
## What top apps actually do, by category
From the same third-party snapshot, median onboarding plus walkthrough screens per category (again, directional estimates, aggregates only):
- **Health & fitness: 17 screens.** Goal setting, body metrics, habit questions, plan building.
- **Finance: 15 screens.** Trust building, account context, compliance disclosures.
- **Education: 14 screens.** Level assessment, goal selection, streak setup.
- **Social networking: 12 screens.** Profile setup, interests, contacts and notifications.
- **Photo & video, music, sports: about 11 screens.**
- **Business, lifestyle, medical: about 10 screens.**
- **Utilities, productivity, news, reference: about 9 screens.**
- **Entertainment: 8 screens.** The shortest measured category, and still above the old rule.
The pattern is intuitive once you see it. Apps that change your behavior (fitness, education, finance) front-load questions because the answers make the product visibly better and each answered question deepens commitment. Apps that deliver instant content (entertainment) get out of the way faster. Nobody serious ships three slides of feature art.
## Why longer onboarding can outperform
A well-built long onboarding is an investment loop. You ask about a goal, then show the plan adapting to the answer. You ask about experience level, then adjust the content. By the time a paywall appears, the user has co-built something and can see exactly what they would be paying for. This is why habit apps run 15-plus screens and still convert: motivation is highest right after install, and a good flow spends that motivation on personalization instead of a tour.
The failure mode is not length. It is screens that neither ask, teach, nor prime. A 6-screen flow of filler loses to a 14-screen flow where every screen earns its place.
## Screen count is the wrong metric anyway
The number that matters is per-screen drop-off, and where completion correlates with retention. Two apps with identical 12-screen flows can have completely different results depending on question order, value framing, and where the permission prompts sit. You cannot copy your way to the right flow. You have to measure yours.
1. Start from your category norm, not from a universal rule.
2. Instrument every screen. Find the exact screen where users bail.
3. Cut or rework the biggest drop-off point. Nothing else.
4. Watch downstream retention, not just completion. A shorter flow that completes more but retains worse is a loss.
5. Repeat weekly. Onboarding is a growth surface, not a launch asset.
This iteration loop is exactly what [Setgreet](https://www.setgreet.com/product) is built for: it drafts a category-calibrated [native onboarding flow](https://www.setgreet.com/use-cases/onboarding) for your app, gives you per-screen analytics, and lets you ship the next version without waiting on an App Store release.
## What actually fills a 12-screen flow
If 12 screens sounds bloated, it is usually because you are picturing 12 tutorial slides. Here is what a typical high-performing flow in a habit or learning app actually spends its screens on:
- **1 welcome screen** naming the outcome, not the app.
- **4 to 6 personalization questions**, one tap each: goal, current level, schedule, motivation.
- **1 or 2 plan-building moments** where the app visibly assembles something from the answers. This is the commitment engine of the whole flow.
- **1 social proof or expectation screen**: what results look like, and when.
- **1 permission primer** placed right after the user creates something worth being reminded about.
- **1 or 2 paywall screens** whose offer now references everything the user just told you.
Count it up and you are at 9 to 13 screens where a user taps through in well under two minutes, never reads a paragraph, and arrives at the paywall with a personalized reason to pay. That is what the category medians are made of. The screens the old advice warned you about (dense, passive, explanatory) are simply not in the flow.
It also explains the category spread. Entertainment apps median around 8 because they have little to personalize: pick a few genres and go. Health and fitness apps median around 17 because the plan-building loop is the product: every additional question makes the promised plan more specific, and the paywall stronger. Your screen count should fall out of how much personalization genuinely improves your first session, not out of a number in a blog post, including this one.
## Frequently asked questions
### Is 20 screens too many?
Not automatically. Some top health apps run past 20. The test is whether each screen asks, teaches, or primes, and whether your per-screen drop-off stays flat. The moment you see a cliff, you found a screen that is not earning its place.
### Should onboarding be skippable?
Make the passive parts skippable and the load-bearing parts not. If an answer personalizes the product or is required to deliver value, it is fine to require it. Users skip tutorials; they generally answer short questions about themselves.
### Do more screens hurt completion rate?
Each screen adds some drop-off, but the relationship is much weaker than intuition suggests when screens are quick single-tap questions. Completion rate alone is also the wrong target: judge a flow by activation and retention of the users who finish it, not by raw completion.
---
# Mobile app onboarding best practices that hold up in 2026
Source: https://www.setgreet.com/blog/mobile-app-onboarding-best-practices
Published: 2026-07-29
> Most onboarding advice is a list of platitudes. These are the practices that survive contact with real drop-off data: personalize early, sequence permissions, place the paywall deliberately, and iterate without app releases.
If you only take five things from this article: open with the outcome your app delivers, ask questions that visibly change the experience, request permissions in context instead of on launch, place your paywall where motivation peaks, and treat the whole flow as something you rework weekly. Everything below is the reasoning and the order of operations.
## Start with the job, not the tour
A new user gives you one first session, and most of their decision happens inside it. They did not download your app to learn your interface. They downloaded it to lose weight, learn Spanish, fix their budget, or kill twenty minutes. Your first screen should name that job in the user's words. Feature tours answer a question nobody asked; the job-first opening answers the only question that matters: am I in the right place?
A useful drill: write the first screen as if it were the answer to the App Store search query that brought the user here. If someone searched "calorie tracker", the first screen should confirm they found one, not introduce your brand values.
## Ask questions that change what happens next
Personalization questions are the strongest pattern in modern onboarding, and the most abused. The rule: only ask what you will visibly act on. Ask a goal, then show the plan adapting to it. Ask experience level, then change the starting content. Every answered question raises commitment, but only if the user can see the answer doing something. Questions that vanish into a database read as a survey, and surveys get abandoned.
Three to six single-tap questions is a healthy core for most categories. Habit-change apps go much longer and get away with it because every answer visibly shapes the plan. For what flow length looks like across categories, see our breakdown of [how many onboarding screens top apps use](https://www.setgreet.com/blog/how-many-onboarding-screens).
## Sequence permissions, never stack them
Nothing marks a lazy onboarding like the notification prompt firing on first launch, before the app has shown anything. Each permission request needs its own moment, preceded by a screen that explains what the user gets. Notifications after the user sets a goal worth reminding them about. Photos when they take the action that needs the camera roll. Location when the feature that uses it is on screen.
On iOS this is not just polish: the system prompt is effectively one-shot, and a declined prompt is very hard to recover from. Prime first, ask second. We wrote up the full pattern in [push notification permission prompts](https://www.setgreet.com/blog/push-notification-permission-best-practices).
## Place the paywall deliberately
For subscription apps the paywall is part of onboarding, not something that happens later. The default that works for most categories: paywall at the end of the flow, after the personalization loop has built up a concrete picture of value, and before the user lands in a free experience they may settle into. Where you put it, how you frame the trial, and what you show first are the highest-leverage tests you can run. The specifics are big enough that we gave them their own guide: [when should your app show the paywall?](https://www.setgreet.com/blog/onboarding-paywall-placement)
## Design for the drop-off curve
Every onboarding flow is a funnel, and you should be able to see it as one. Instrument each screen. The shape tells you what to fix: a cliff at one screen means that screen is broken (too much text, an unearned permission ask, a form where a tap should be). A gradual slide everywhere means the flow lacks momentum and needs earlier proof of value. No data means you are redesigning on vibes.
Judge changes by activation and early retention, not completion alone. Shortening a flow almost always raises completion; if it also lowers Day 7 retention, you optimized the wrong number. Benchmarks for what good retention looks like are in our [retention benchmarks guide](https://www.setgreet.com/blog/mobile-app-retention-benchmarks).
## Ship it like a growth surface, not a release
The teams that win at onboarding are not the ones with the best first version. They are the ones that iterate every week, because their onboarding does not ride the release train. When a copy change needs a build, review, and a phased rollout, iteration dies and the flow fossilizes.
This is the problem [Setgreet](https://www.setgreet.com/product) exists to solve: [onboarding flows](https://www.setgreet.com/use-cases/onboarding) render as real native screens in your app, and changes go live in minutes without an App Store release. Start from a category-matched flow, watch the per-screen funnel, ship the fix the same day. The [getting started guide](https://www.setgreet.com/docs/getting-started) shows the full loop.
## The mistakes that keep repeating
- **The feature dump.** Eight slides of interface explanation before any value. Users retain almost none of it.
- **Asking without acting.** Personalization questions whose answers change nothing visible.
- **Permission stacking.** Two or three system prompts in the first thirty seconds.
- **Login before value.** Forcing account creation before the user has seen anything worth an account.
- **Shipping once.** Treating onboarding as a launch deliverable instead of the highest-traffic funnel in the product.
## A first-session checklist you can run this week
Install your own app fresh, on a real device, and walk the first session with a stopwatch and a notebook. Score it against these questions:
1. Does the first screen name the job the user came for, in their words?
2. How many seconds until the user sees something personal to them? Under 60 is the bar.
3. Does every question visibly change what happens next? Cut the ones that do not.
4. Is any permission requested before its value is obvious? Move it.
5. Could a distracted user complete the flow with one thumb, on a train? Every typing field is a toll booth.
6. When the paywall appears, does it reference anything the user just said? A generic paywall after a personalization flow wastes the flow.
7. What happens on a network drop, a backgrounded app, a force quit? Broken resume states silently eat cohorts.
Then compare your notes against the funnel data. The screen you found tedious is usually the screen with the drop-off cliff; the fix you guessed is usually worth testing first. This audit takes an hour and routinely finds the biggest win of the quarter.
One more habit separates the teams that compound from the teams that stall: put a recurring 30-minute onboarding review on the calendar, every week, with the funnel chart open. Not to redesign anything, just to answer "what is the one thing we change this week?" Onboarding decays quietly as the product, the audience, and the acquisition mix shift under it. The weekly question keeps it honest.
## Frequently asked questions
### Should onboarding come before or after sign-up?
Deliver value, or at least a concrete preview of it, before asking for an account. The exception is apps where the account is the product, like banking or messaging. If sign-up must come first, keep it to one screen and defer everything optional.
### How long should mobile onboarding take?
Under two minutes of active time for most apps, even when the flow runs 10-plus screens of single-tap questions. Time-to-first-value matters more than screen count or seconds elapsed.
### How often should we change our onboarding?
Review the funnel weekly and ship at least one meaningful iteration per month. If your tooling makes that expensive, fix the tooling first; iteration speed compounds more than any single redesign.
---
# When should your app show the paywall?
Source: https://www.setgreet.com/blog/onboarding-paywall-placement
Published: 2026-07-29
> Paywall placement moves subscription conversion more than paywall design. Where to put it, what the data says about hard paywalls and trials, and how trial framing differs by app category.
The default that works for most subscription apps: show the paywall at the end of onboarding, after a personalization loop has made the value concrete, and before the user settles into a free experience. Motivation peaks right after install and decays with every session; a paywall placed after "I can see this working for me" and before that decay is the highest-converting moment most apps have.
## Why placement beats design
Teams burn weeks polishing paywall layouts and minutes deciding where the paywall goes. The order should be reversed. A plain paywall at the right moment outperforms a beautiful one shown before the user understands what they would be paying for. Design is worth testing, but placement and framing set the ceiling.
The subscription data backs the urgency. In RevenueCat's [State of Subscription Apps 2026](https://www.revenuecat.com/state-of-subscription-apps), built on over 115,000 apps, conversion is heavily front-loaded in the first session and the first days after install. If your paywall strategy assumes users will discover it in week two, most of them were never going to see it.
## The three placements that work
1. **End of onboarding (the default).** The personalization loop builds a concrete picture of value, the paywall lands as its natural conclusion. Works across most categories.
2. **On the locked feature.** The user reaches for something premium and the paywall explains exactly what unlocks. Highest intent, lower traffic. Best as a second surface, not the only one.
3. **On app open (hard paywall).** No free tier at all. Aggressive, but the same RevenueCat report found apps that ask for money upfront convert about 5x better than freemium (10.7% vs 2.1% median). It fits apps whose value is obvious before use; it punishes apps that need sessions to prove themselves.
Most strong subscription apps combine the first two: an onboarding paywall for everyone, feature-gate paywalls for the users who dismissed it. If you are choosing between free trial, hard paywall, and freemium as a model, that decision comes first: see [free trial, hard paywall, or freemium](https://www.setgreet.com/blog/free-trial-vs-hard-paywall).
## Match trial framing to your category
Whether your paywall should lead with "try free for 7 days" or with the price is not a matter of taste; it varies enormously by category. Directional estimates from a July 2026 snapshot of PaywallPro's [Open Paywall Gallery](https://github.com/paywallpro/paywall-gallery), a public dataset of 910 top iOS subscription apps, put the share of trial-led paywalls at:
- **Business: about 77%** of paywalls lead with a trial.
- **Education: about 73%.** Utilities: about 71%. Productivity: about 66%.
- **Health & fitness: about 52%.** Split down the middle.
- **Social networking: about 25%. Entertainment: about 24%.** Content apps mostly sell access directly.
These are third-party estimates, not our measurements, but the spread is the point: copying a fitness app's trial framing into an entertainment app means adopting a convention its users never see elsewhere. Start from your category's norm, then test against it.
## Respect the Day 0 effect
One more finding worth internalizing from the same RevenueCat report: 55% of all 3-day trial cancellations happen on Day 0, the day the trial starts. Users start a trial and immediately cancel to avoid forgetting. This is not a failure of your paywall; it is how a large share of users consume trials. It means your trial experience, not just your trial screen, does the converting: the first session after the paywall has to prove the subscription is worth keeping.
## Test placement, not pixels
Because placement is the highest-leverage variable, it should be the first thing you A/B test: end-of-onboarding vs feature-gate, trial-led vs price-led framing, one screen earlier or later in the flow. These are structural tests, and running them through app releases is painful enough that most teams never do it. With [Setgreet](https://www.setgreet.com/product), paywall and [upgrade screens](https://www.setgreet.com/use-cases/upgrade-prompts) are native flows you can move, reframe, and [A/B test](https://www.setgreet.com/blog/ab-test-onboarding-flow) without shipping a build, at a [flat MAU price](https://www.setgreet.com/pricing) that never takes a cut of your revenue.
## The second-chance surfaces most apps forget
The onboarding paywall converts a minority of viewers even when it is working well, which means most of your users dismissed it. What they see next is a placement decision too, and it is usually left to chance. The strong patterns:
- **Feature gates with specific offers.** When a free user hits a locked capability, the paywall should name that capability, not restate the generic pitch they already declined.
- **Progress-triggered offers.** After a streak, a completed workout, a finished project: moments where the product just proved itself are second motivation peaks.
- **Win-back placement.** A returning user who lapsed for weeks is closer to a fresh install than to an active user; showing them the onboarding paywall again, sometimes with a discount, is fair game.
- **The settings page.** Boring, but users with purchase intent go looking for it. Make the upgrade entry point findable in two taps.
Each surface deserves its own framing, because the user's context differs at each one. The mistake is shipping one paywall screen and routing every trigger to it: the message that fits a motivated Day 0 user reads as noise to a Day 30 free user who has seen it five times.
## Frequently asked questions
### Should the paywall be skippable?
If you run a free tier, yes, with a visible but quiet close affordance. Hiding the close button buys a little conversion and costs trust and reviews. If you run a hard paywall, there is nothing to skip to; just be sure your category can carry it.
### Is showing a paywall during onboarding too aggressive?
No. It is the norm among top subscription apps, and users expect it. What reads as aggressive is a paywall before any personalization or value framing, or one that reappears on every tap after being dismissed.
### Where should the paywall go for a freemium app?
Still show it at the end of onboarding as a soft offer, then rely on feature-gate paywalls at the moments free users hit limits. The onboarding paywall sets the anchor that a paid tier exists; the gates convert when intent is proven.
---
# Free trial, hard paywall, or freemium: what actually converts
Source: https://www.setgreet.com/blog/free-trial-vs-hard-paywall
Published: 2026-07-29
> Hard paywalls convert about 5x better than freemium, and long trials convert better than short ones. So why does anyone run freemium? Because conversion rate is not the whole equation. A decision framework for subscription apps.
The short version: hard paywalls win on conversion rate, freemium wins on audience size, and trials sit in between, with trial length mattering more than most teams think. The right model depends on one question: how fast can your app prove its value? The faster the proof, the harder your paywall can be.
## What the data says
RevenueCat's [State of Subscription Apps 2026](https://www.revenuecat.com/state-of-subscription-apps), drawing on over 115,000 apps and more than $16 billion in tracked revenue, gives the clearest published picture of how the three models perform:
- **Hard paywalls convert about 5x better than freemium**: a 10.7% vs 2.1% median download-to-paid rate.
- **Longer trials convert better.** Trials of 17 to 32 days convert at a 42.5% median vs 25.5% for trials under four days.
- **Yet short trials are gaining share.** The share of apps running trials of four days or less rose year over year, trading conversion rate for faster billing.
- **Trial cancellations are front-loaded**: 55% of 3-day trial cancellations happen on Day 0, the day the trial starts.
Read those numbers carefully, because the obvious conclusion (everyone should run a hard paywall with a month-long trial) is wrong. Median conversion rate says nothing about volume. A hard paywall converts a much smaller funnel: everyone who will not pay bounces at the door instead of becoming a free user you might convert later, or who might fill your leaderboards, marketplaces, and referral loops.
## The decision framework: speed of value proof
Ask how many sessions your app needs before a user would honestly say "this works for me."
- **Value proven in the first session** (photo editors, scanners, single-purpose utilities): hard paywall or a short trial. The user has seen everything they need; waiting only loses them.
- **Value proven over days** (fitness plans, learning streaks, budgeting): a trial long enough to contain the proof. If your habit loop takes ten days to feel real, a 3-day trial asks users to pay on faith. This is where the long-trial conversion advantage comes from.
- **Value that grows with the network or library** (social features, marketplaces, content catalogs): freemium, because free users are an input to the product, not just unconverted revenue.
Notice the framework has no entry for "we chose freemium because asking for money felt rude." That is the most common real reason, and the 5x gap is its price tag.
## What your category expects
Users arrive with expectations set by every other app in your category. Directional estimates from a July 2026 snapshot of PaywallPro's public [Open Paywall Gallery](https://github.com/paywallpro/paywall-gallery) dataset of 910 top iOS subscription apps show trial-led paywalls ranging from about 77% of Business apps down to about 24% of Entertainment apps. Deviating from your category norm is a legitimate test, but it should be a deliberate one. The full breakdown is in our guide to [paywall placement](https://www.setgreet.com/blog/onboarding-paywall-placement).
## The hybrid patterns worth knowing
- **Reverse trial.** Start every user with full premium access for a set period, then drop them to a free tier. They experience the ceiling before the floor, and the downgrade itself becomes an upgrade prompt. It combines freemium reach with trial urgency.
- **Freemium with a trial-led paywall.** The free tier exists, but the onboarding paywall still leads with a trial offer. The common pattern behind most "77% trial-led" business apps.
- **Hard paywall with a dismiss-to-limited mode.** Looks like a hard paywall, converts like one, but a small close affordance drops users into a heavily limited preview instead of losing them entirely.
## Price structure is part of the model
The trial question and the price-menu question travel together, and the menu also varies by category far more than teams assume. In the same third-party dataset snapshot, weekly plans appear on about 68% of Utilities paywalls and 52% of Photo & Video paywalls, but on essentially 0% of Finance paywalls and 5% of Sports paywalls. Median observed US prices cluster around $6.99 to $9.99 per week, $12 to $15 per month, and $50 to $80 per year, with Finance topping the annual range at around $119.
The pattern behind the numbers: weekly pricing thrives where the job is bursty or short-horizon (edit these photos, get through this trip) and disappears where the product is a long-term relationship with trust at stake. Weekly plans also pair naturally with hard paywalls and no trial, because the plan itself is the low-commitment entry. If you sell a long-horizon outcome, the conventional annual-with-trial anchor remains conventional because it works. As always, these are directional third-party estimates: treat them as the market's prior, not your answer.
## Whatever you choose, make it cheap to change
Monetization models are not one-way doors, but for most teams they behave like one because every paywall change rides an app release. The teams that find the right model are the ones that can test trial lengths, framing, and placement weekly. [Setgreet](https://www.setgreet.com/product) ships paywall and [upgrade screens](https://www.setgreet.com/use-cases/upgrade-prompts) as native flows you can restructure and [A/B test](https://www.setgreet.com/docs/experiments) without a release, alongside the billing stack you already run. Setgreet charges a [flat MAU price](https://www.setgreet.com/pricing), never a percentage of your revenue.
## Frequently asked questions
### What is a good trial length for a subscription app?
Long enough to contain your value proof. Published medians favor longer trials on conversion (42.5% for 17-32 days vs 25.5% under four days, per RevenueCat), but a long trial on an app whose value is obvious in one session just delays revenue. Match the trial to the proof, then test.
### Should I switch from freemium to a hard paywall?
First check whether your free tier feeds the product (network effects, content, referrals) or just houses users who will never pay. If it is the latter, test a hard paywall on a fraction of new installs before committing; the conversion lift is real, but so is the volume drop, and only your numbers can say which wins.
### Why do users cancel trials immediately?
Mostly self-protection against forgotten renewals, and it is common: 55% of 3-day trial cancellations happen on the start day. Early cancellation does not mean the user is lost; it means the trial period itself has to convince them to come back and resubscribe.
---
# Mobile app retention benchmarks: what good looks like in 2026
Source: https://www.setgreet.com/blog/mobile-app-retention-benchmarks
Published: 2026-07-29
> Strong apps keep 30 to 40% of users on Day 1 and 5 to 8% by Day 30, while the median app keeps far fewer. What the published benchmarks say, why they mislead, and which levers actually move retention.
The benchmark picture in one paragraph: strong performers (75th percentile) retain roughly 30 to 40% of new users on Day 1, 10 to 15% on Day 7, and 5 to 8% on Day 30, while the median app across categories lands near 4% by Day 30. In other words, even good apps lose the vast majority of installs within a month, and the difference between good and median is made almost entirely in the first days.
## The numbers, and where they come from
No single company measures all of mobile, so benchmarks are stitched from attribution and analytics vendors, chiefly AppsFlyer's State of App Marketing and Adjust's mobile trends reports, whose figures typically land within a few percentage points of each other. The ranges above follow UXCam's 2026 compilation of those sources. Treat every number as a directional band, not a target with decimals: measurement windows, install sources, and category mixes differ between reports.
- **Day 1: about 25 to 30% average**, 30 to 40% for strong performers.
- **Day 7: about 11 to 15%.** The steepest part of the curve is already behind you.
- **Day 30: about 4 to 6% median**, 5 to 8% for strong performers.
- **Category spread is wide.** Social and fintech apps retain materially better than casual games or utility apps; comparing yourself to the all-apps average can flatter or slander you by 2x.
Business model shifts the curve too. Subscription apps retain multiples better than ad-supported ones by Day 30 in the compiled data, partly because payment is itself a commitment device, and partly because subscription apps are built around a job worth returning to.
## Why benchmark-chasing misleads
Benchmarks answer "are we roughly normal?" and nothing else. Your retention curve is shaped by acquisition mix (paid installs retain worse than organic), by geography, and by how your category uses apps at all: a flight tracker with 5% Day 30 retention may be thriving while a habit tracker with the same number is dying. The useful comparison is your own curve, cohort over cohort, after each change you ship.
## The shape of the curve tells you where to work
- **Cliff between install and Day 1:** a first-session problem. Users did not reach value once. This is an [onboarding](https://www.setgreet.com/use-cases/onboarding) problem, and it is where most of the retention gap between median and strong apps lives.
- **Decay from Day 1 to Day 7:** a habit problem. Users saw value once but had no reason to return. Look at reminders, streaks, fresh content, and whether your [permission prompts](https://www.setgreet.com/blog/push-notification-permission-best-practices) earned the right to re-engage.
- **Slow bleed after Day 30:** a value-depth problem. The product delivered its first win but not an ongoing one. No flow fixes this; the product itself has to deepen.
The first-session cliff deserves most teams' attention because it is the largest number and the cheapest fix. A user who never activates is pure loss, and activation is determined by a surface you can iterate in days, not quarters. Our [onboarding best practices guide](https://www.setgreet.com/blog/mobile-app-onboarding-best-practices) covers the specific levers.
## Measure cohorts, then iterate weekly
Retention only becomes actionable when you can see a cohort's curve change in response to a specific change you shipped. That loop needs two things: per-screen funnel analytics on the first session, and the ability to ship the next onboarding iteration without waiting for a release cycle. [Setgreet](https://www.setgreet.com/product) provides both: flow-level [analytics](https://www.setgreet.com/docs/analytics) on every plan, including the free one, and native flow updates that go live in minutes.
## A retention diagnosis you can run this week
1. Pull the last 8 weekly cohorts and plot Day 1, Day 7, and Day 30 for each. Trend beats snapshot: a flat 20% Day 1 tells you less than a 24% that slid to 17%.
2. Split the newest cohorts by acquisition source. Paid installs retaining half as well as organic is normal; paid retaining a tenth as well means the ads are selling an app you did not build.
3. Find the activation event that best predicts Day 30 retention (first workout logged, first document created). That event, not installs, is what your onboarding should be optimized to produce.
4. Read the first-session funnel screen by screen and mark the biggest cliff. That screen is this week's work.
5. Ship one change against that cliff, then watch the next two cohorts. One change per cohort window, or you will not know what worked.
Teams that run this loop monthly tend to stop asking about benchmarks within a quarter, because their own cohort-over-cohort slope becomes the number that matters. The benchmark answered "are we roughly normal?"; the loop answers "are we getting better?", and only the second one compounds.
## Frequently asked questions
### What is a good Day 30 retention rate for a mobile app?
Across categories, the compiled 2026 benchmarks put the median near 4% and strong performers at 5 to 8%. Subscription apps and social apps should expect meaningfully higher; casual games and one-job utilities lower. Compare against your category and acquisition mix, not the global average.
### Why is Day 1 retention the number to fix first?
Because every later cohort is downstream of it. Improving Day 1 lifts the entire curve, it is measurable within a day of shipping a change, and the surface that controls it (the first session) is the easiest one to iterate.
### Do these benchmarks apply to subscription apps?
Directionally, but subscription apps track two curves: usage retention and subscriber renewal. RevenueCat's [State of Subscription Apps](https://www.revenuecat.com/state-of-subscription-apps) is the better reference for the renewal side; the usage benchmarks here still govern whether anyone survives long enough to subscribe.
---
# Push notification permission prompts: how to get more users to say yes
Source: https://www.setgreet.com/blog/push-notification-permission-best-practices
Published: 2026-07-29
> Roughly half of iOS users decline the notification prompt, and you only get one shot at it. How pre-permission priming works, when to ask, and what to say.
The one rule that matters: never fire the system permission prompt cold. Show your own screen first, tied to something the user just did, explaining exactly what notifications they will get and why those are worth having. Then, and only for users who said yes to your screen, trigger the system prompt. Everything else in this guide is refinement of that pattern.
## Why the prompt is so expensive to waste
On iOS, the system notification prompt is effectively one-shot: once a user declines, you cannot show it again, and recovering them means convincing them to dig through the Settings app, which almost nobody does. Since Android 13, Android works the same way, with a runtime prompt that stops showing after repeated denials. A cold prompt on first launch converts your single attempt at the moment the user has the least reason to trust you.
And the stakes are real: opt-in is far from universal. Batch's 2025 push notification benchmark put average opt-in around 56% on iOS and 67% on Android, with Android falling fast as the runtime-permission change rolls through the installed base. Published figures vary by vendor and vertical, but every source agrees on the shape: roughly half of iOS users say no, and the gap between well-asked and badly-asked apps is enormous.
## The pre-permission pattern
A pre-permission screen (sometimes called a soft ask or primer) is your own native screen shown before the system prompt. It works for three reasons:
1. **It preserves your one shot.** Users who decline your screen never see the system prompt, so the OS-level permission is still winnable later at a better moment.
2. **It lets you make an argument.** The system prompt is fixed text; your screen can say "we will remind you 10 minutes before your workout" instead of "App would like to send you notifications."
3. **It filters for intent.** Users who tap yes on your screen accept the system prompt at very high rates, because they already decided.
## When to ask
Ask at the moment the user creates something worth being notified about, because that is when the value of notifications is self-evident:
- Right after a goal, plan, or streak is set up: "want reminders that keep this on track?"
- After a first meaningful action: an order placed, a listing saved, a message sent.
- When enabling a feature that only works with notifications: price alerts, delivery updates, mentions.
- Never on first launch, never stacked with other permission prompts, and never as a blocking wall the user must answer to continue.
For most apps this lands somewhere in the second half of [onboarding](https://www.setgreet.com/use-cases/onboarding), after the personalization questions have produced something concrete to be reminded about. If a user declines the primer, let it go and re-ask weeks later at a different trigger moment, with different framing.
## What the primer screen should say
- **Name the specific notifications**, not the category. "Your daily lesson reminder at 8pm" beats "stay up to date."
- **Tie it to the user's own stated goal.** You asked personalization questions; use the answers.
- **Offer an honest decline.** A visible "not now" keeps trust and keeps the system prompt in reserve.
- **One screen, one decision.** No bundling with location, camera, or tracking prompts.
## Measure it like a funnel
Track primer views, primer accepts, system-prompt accepts, and opt-in rate by cohort. Then iterate on timing and copy like any other conversion surface. This is exactly the kind of change that should not wait for an app release: with [Setgreet](https://www.setgreet.com/product), [permission primer flows](https://www.setgreet.com/use-cases/permissions) are native screens you can retime, reword, and A/B test live, and the funnel for each variant is in the [analytics](https://www.setgreet.com/docs/analytics) out of the box.
## Primer copy patterns that keep working
- **The commitment callback.** "You set a goal of 3 workouts a week. Want a nudge on workout days?" The user is agreeing with their own plan, not with your marketing.
- **The concrete preview.** Show the actual notification, styled like the real thing, on the primer screen. Users say yes to a specific message and no to an abstraction.
- **The control offer.** "Choose what we send" with two or three toggles (reminders yes, news no). Granting control raises accepts and lowers later revocations.
- **The loss frame, used honestly.** "Price alerts only work with notifications on." True for alert-type features; manipulative anywhere else. Do not borrow it where it does not apply.
And a pattern to retire: the double-negative guilt screen ("No thanks, I do not want to reach my goals"). It converts a point or two better in the short term and shows up in one-star reviews forever. Notification permission is the beginning of a channel relationship; opening it with a shove sets the tone for every message after.
For users who declined the primer, keep a re-ask path alive: wait for a genuinely new trigger (a new goal, a first order, a feature that needs alerts), not a calendar interval. A re-ask that arrives with new context reads as helpful; the same screen on a timer reads as nagging. Two or three well-spaced attempts is the practical ceiling before you are farming resentment.
## Frequently asked questions
### What is a good push notification opt-in rate?
Vendor benchmarks put averages around the mid-50s percent on iOS and 60s to 90s on Android depending on the source and year measured, with wide category spread. More useful than the absolute number: your primer-accept rate should be well above your cold-prompt baseline, and trending up as you refine timing.
### Can I show the iOS notification prompt twice?
No. Once declined, the system prompt cannot be re-shown; the user must enable notifications manually in Settings. This is exactly why the pre-permission screen exists: your own screen can be shown again, the system prompt cannot.
### Does Android need a primer screen too?
Since Android 13, yes. Notifications require a runtime permission with deny behavior similar to iOS, and opt-in rates on new Android versions have dropped accordingly. The same primer pattern applies on both platforms.
---
# React Native onboarding: from swiper libraries to server-driven flows
Source: https://www.setgreet.com/blog/react-native-onboarding-flow
Published: 2026-07-29
> Three ways to build onboarding in a React Native app: hand-rolled screens, swiper libraries, or server-driven native flows. What each costs you, and why iteration speed should drive the choice.
You have three real options for onboarding in a React Native app: build the screens by hand, assemble them with an onboarding or swiper library, or drive them from a server so they update without a release. The right choice depends on one question most teams never ask up front: how often will you change this flow? If the honest answer is "every few weeks, once we see the drop-off data," the architecture matters more than the components.
## Option 1: hand-rolled screens
A stack navigator, a screen per step, some shared progress state. Full control, no dependencies, and for a two-screen flow it is genuinely the simplest answer. The costs show up later: every copy tweak, screen reorder, or new question is a code change riding your release train, and onboarding logic (progress, skips, conditional branches, analytics events) accretes into some of the gnarliest state in the app. Hand-rolling is the right call when your flow is short, stable, and deeply custom.
## Option 2: onboarding and swiper libraries
The React Native ecosystem has a pile of intro-carousel and swiper packages that get a paged walkthrough on screen in an afternoon. They are fine for what they are, but be clear about what they are: layout components. The flow itself (what screens exist, in what order, with what copy) still lives in your JavaScript bundle, so iteration still means a release. And the format they push you toward, the decorative swipe-through tour, is precisely the onboarding pattern that top apps have abandoned in favor of question-driven personalization, as the category data in [how many onboarding screens](https://www.setgreet.com/blog/how-many-onboarding-screens) shows. Maintenance is a real factor too: many of these packages are lightly maintained, and each new React Native version is a small gamble.
## Option 3: server-driven flows
The third option moves the flow definition out of the bundle: screens, order, copy, branching, and targeting live on a server, and an SDK renders them natively at runtime. This is the architecture behind most top subscription apps' onboarding, and it changes the economics of iteration completely. Reordering screens, rewording a question, moving the paywall, or running an A/B test becomes a publish, not a release. The same mechanism handles the flows you have not built yet: surveys, feature announcements, upgrade prompts.
The trade-offs are the usual ones for any service dependency: you take on a vendor (or you build and operate the system yourself, which is a quarter-scale project), you design for offline and failure fallbacks, and deeply custom interactions stay in code. For the standard onboarding vocabulary (questions, media, permissions, paywalls) a good flow system covers the ground.
## How to choose
- **Two or three stable screens, no experiments planned:** hand-roll it and move on.
- **You want a quick visual tour this week and accept a rebuild later:** a swiper library is fine scaffolding.
- **Onboarding is a growth surface you intend to measure and iterate:** server-driven is the only option where weekly iteration is actually cheap.
The pattern we see repeatedly: teams hand-roll v1, learn from the funnel that the flow needs constant change, then migrate to server-driven once the third "just reorder two screens" release ships a week late.
## What about building server-driven yourself?
Some teams look at server-driven UI and reach for the whiteboard, because the core idea (JSON in, components out) looks like a weekend project. The renderer is. The system is not. A production onboarding system also needs a visual editor non-engineers can use, draft and publish separation with rollback, per-screen analytics, targeting and A/B splits, offline caching with safe fallbacks, and versioning that survives old app binaries meeting new flow definitions. Each is unglamorous; together they are a quarter of engineering time, plus permanent maintenance, spent on infrastructure your users never see.
The build case exists: deeply custom interactions, unusual compliance constraints, or flows so core they are the product. For the standard growth vocabulary, buying the system and spending the quarter on your actual product is usually the honest math. Whichever way you go, checklist for evaluating any option: native rendering (not web views), works with your navigation setup, offline fallback behavior you can test, analytics granular to the screen, and an update path that does not care which app version a user runs.
## Server-driven onboarding with Setgreet
[Setgreet's React Native SDK](https://www.setgreet.com/docs/sdk) renders flows as real native screens, not web views, from a definition you edit in a visual editor or generate [with AI](https://www.setgreet.com/product). It supports React Native 0.60 and up with TypeScript, ships with per-screen [analytics](https://www.setgreet.com/docs/analytics), and publishes changes to live apps in seconds. The [getting started guide](https://www.setgreet.com/docs/getting-started) takes about ten minutes, and the free plan is a real free plan: build, publish, and measure included.
## Frequently asked questions
### Which React Native onboarding library should I use?
If you only need a paged intro carousel, any actively maintained swiper package will do, and the specific pick matters less than accepting its limits: it solves layout, not iteration. If you expect to change the flow based on data, evaluate server-driven options before committing to a library at all.
### Which React Native versions does server-driven onboarding support?
Setgreet's React Native SDK supports React Native 0.60 and up, in TypeScript; see the [SDK docs](https://www.setgreet.com/docs/sdk) for setup. Because flow content updates are configuration rather than shipped code, they arrive independently of your app release process.
### Will server-driven screens feel native?
Depends on the renderer. A system that renders actual native components (as Setgreet does) is indistinguishable from built-in screens; embedded web views are not, and users notice the seams in scrolling, fonts, and load flashes.
---
# How to A/B test your app's onboarding flow
Source: https://www.setgreet.com/blog/ab-test-onboarding-flow
Published: 2026-07-29
> Most mobile teams never A/B test onboarding because every variant used to mean an app release. How to structure onboarding experiments, what to test first, and how to avoid the sample-size traps.
To A/B test onboarding well you need three things: variants that differ by one meaningful variable, a success metric further downstream than completion, and infrastructure that lets you ship the next test without an app release. Most teams fail on the third, which is why most onboarding flows have never been tested at all.
## Why mobile A/B testing is structurally hard
On the web, an experiment is a deploy. In a mobile app, a hardcoded onboarding variant is a build, a review, a phased rollout, and a wait for the installed base to update, per variant. By the time results arrive, the team has moved on. This is not a discipline problem; it is a tooling problem. Testing velocity is worth engineering for: an analysis published by Adapty in 2026 found apps running 50-plus experiments had a median revenue 18.7x that of apps that ran a single one. That is correlation, not causation (teams that test a lot differ in many ways), but the direction matches what every high-performing subscription team reports about its own process.
## What to test, in order of leverage
1. **Paywall placement and framing.** End-of-onboarding vs feature-gate, trial-led vs price-led. The highest-impact structural variable; our [paywall placement guide](https://www.setgreet.com/blog/onboarding-paywall-placement) covers the baselines.
2. **Flow length and question order.** Which personalization questions stay, which go, and what order builds the strongest commitment loop.
3. **Value framing.** The first screen's promise: outcome-led vs feature-led, social proof vs plan preview.
4. **Permission prompt timing.** Where the notification primer sits, and what it says.
5. **Copy and design.** Real, but last. Button colors are the homeopathy of onboarding optimization.
One variable per test. A variant that changes the paywall, the length, and the copy at once can only tell you that something worked, which teaches you nothing reusable.
## Pick the right success metric
Completion rate is the seductive wrong answer. Cutting screens nearly always raises completion; whether it helps the business depends on what those screens did. The variant that wins on completion can lose on trial starts, and the variant that wins on trial starts can lose on Day 7 retention if it over-promises. Define the primary metric one step past the flow: activation (first core action), trial start, or early retention. Use completion as a diagnostic, not a verdict.
## Respect the sample-size math
Onboarding experiments are gated by new installs per day, and subscription events are sparse. A few honest rules:
- Size the test before you start: baseline rate, minimum effect you care about, and the installs per week you actually get. Small apps often need weeks per test; that is normal, plan around it.
- Do not peek and stop at the first significant-looking gap. Early leads flip constantly; commit to the planned sample.
- Chase effects worth detecting. With modest traffic you cannot resolve a 2% relative lift, and you do not need to: structural changes (placement, length) move numbers by tens of percent when they work.
- Segment after, not during. Slicing mid-test by platform, source, and geography until something is significant is how teams fool themselves.
## Run the loop with Setgreet
This whole discipline collapses without cheap iteration. [Setgreet](https://www.setgreet.com/product) runs [A/B experiments](https://www.setgreet.com/docs/experiments) on native flows: define variants in the editor, split traffic, and read results against flow-level [analytics](https://www.setgreet.com/docs/analytics), with no release between test and next test. Experiments are part of the [paid plans](https://www.setgreet.com/pricing), and every plan, including free, keeps full analytics, so you can see your funnel before you ever pay.
## A worked example
Say you run a meditation app doing 700 installs a week, onboarding ends in a trial-led paywall, and 8% of installs start a trial. Hypothesis: moving the paywall from screen 6 to screen 9, after the plan-preview moment, will lift trial starts. You care about a lift of at least 20% relative (8% to 9.6%); anything smaller does not justify the flow being three screens longer.
1. Sample-size math at those numbers says roughly 5,500 users per arm for a standard test, so plan for about eight weeks with a 50/50 split, and pre-commit to it in writing.
2. Primary metric: trial starts per install. Guardrails: onboarding completion and Day 7 retention, watched for damage rather than victory.
3. At week 3 variant B leads by 31%. This is exactly the moment the discipline exists for: keep running. Early leads of that size regularly decay.
4. At week 8: B shows a 24% relative lift on trial starts, completion dropped two points, Day 7 retention is flat. Ship B.
5. The next test starts from B as the new baseline: same slot, trial-led vs price-led framing.
Two things to notice. The test was worth eight weeks because placement is a structural variable with a plausible 20%-plus effect; the same eight weeks on a button-color test would have been thrown away. And the guardrail metrics did real work: a completion drop with flat retention says the extra screens filtered browsers, not buyers, which is exactly the trade you wanted.
## Frequently asked questions
### How long should an onboarding A/B test run?
Until the pre-committed sample size is reached, and at least one full week to wash out day-of-week effects. For most apps that means two to four weeks per test; below roughly a thousand installs a week, favor bigger structural changes that need smaller samples to detect.
### Can I test onboarding without a testing platform?
You can ship variant A, then variant B, and compare cohorts. This before-and-after method is vulnerable to seasonality and acquisition-mix shifts, but it is far better than nothing, and it becomes reasonably trustworthy when the effect is large and the cohorts are adjacent.
### What win rate should I expect from onboarding tests?
Expect most tests to lose or tie; published experiment win rates across the industry are well under half. The economics work anyway, because losers are cheap to kill and winners compound permanently. A team that ships one real winner a quarter transforms its funnel in a year.
---
# In-app survey questions worth asking (and when to ask them)
Source: https://www.setgreet.com/blog/in-app-survey-questions
Published: 2026-07-29
> The moment defines the question. A practical library of in-app survey questions for onboarding, activation, conversion, and churn, plus the rules that keep response rates high.
The best in-app survey question is short, single-choice, and asked at the exact moment the user has the answer in their head. Timing beats wording every time: "what almost stopped you?" gets honest answers seconds after someone finishes setup, and useless ones in an email three days later. Below is a working library of questions organized by the moment that earns them.
## The rules before the questions
- **One question per moment.** A second question halves the response quality; a form kills it.
- **Single-tap answers.** Predefined options plus an optional free-text "other". Typing is a favor users rarely owe you.
- **Ask what you will act on.** Every question should have a decision waiting on its answer.
- **Skip must be effortless.** A survey that blocks the product trades a data point for resentment.
- **Close the loop when you can.** Users who see their feedback change something answer again.
## During onboarding: segment and personalize
Onboarding questions are surveys with a second job: their answers should visibly shape the experience, as covered in our [onboarding best practices](https://www.setgreet.com/blog/mobile-app-onboarding-best-practices).
- "What brings you here?" The goal question. Drives personalization and becomes your segmentation backbone.
- "How did you hear about us?" Self-reported attribution. Imperfect, but it catches the channels attribution tools cannot see, like podcasts and word of mouth.
- "Have you used an app like this before?" Experience level. Routes beginners to guidance and experts past it.
- "What have you tried for this already?" Names the alternative you are being compared against.
## After activation: catch friction while it is fresh
- "How easy was that to set up?" A 1-to-5 tap right after the first core action. Trend it by cohort against every onboarding change.
- "Was anything confusing or almost made you give up?" The single highest-yield question in the product. Ask it once, within the first session or two.
- "What were you hoping to do that you have not figured out yet?" Surfaces discoverability gaps your funnel charts cannot.
## Around conversion: learn why people pay
- "What convinced you to upgrade?" Asked right after purchase, while the reason is articulable. This is your paywall copy, written by customers.
- "What almost stopped you from subscribing?" The objections that survived. Feed them back into [paywall framing](https://www.setgreet.com/blog/onboarding-paywall-placement).
- For users who dismissed the paywall: "not for you, or not yet?" One tap distinguishes wrong-audience from wrong-moment, which demand completely different fixes.
## At churn risk and cancellation: get the reason code
- "Why are you cancelling?" Predefined reasons: too expensive, not using it, missing a feature, found an alternative, technical problems. The distribution across these five drives completely different roadmaps.
- "What would have made you stay?" Free text, optional. Low response rate, but the answers that come are often the most specific feedback you will ever get.
- For dormant users on return: "what brought you back?" Tells you which win-back lever actually worked.
## Periodically: measure fit, sparingly
The classic product-market-fit question ("how would you feel if you could no longer use this?") and satisfaction scores belong on a slow cadence, quarterly at most, targeted at users past their first month. They measure the product, not a moment, and lose meaning when over-asked.
## Targeting mechanics: who sees what, when
A question library is only half the system; the other half is the routing. The practical setup, in order of importance:
- **Event triggers over timers.** "After third completed session" beats "on day 3". The event proves the user has the experience your question asks about.
- **Audience filters.** Segment by plan, cohort age, and behavior so churn questions never hit new users and onboarding questions never hit veterans.
- **Frequency caps and cool-downs.** A global "at most one prompt per session, two weeks between prompts" rule, enforced by the system rather than by every team remembering.
- **Sampling.** At scale, ask 10% of eligible users, not all of them. You need hundreds of answers, not tens of thousands, and the unasked users stay fresh for the next question.
- **Retirement.** Every survey gets an end condition: enough responses, or the decision it fed got made. Zombie surveys are how apps end up interviewing users about a feature that shipped two quarters ago.
Pipe the answers somewhere with the rest of your product data, not into a silo. "Users who answered *too expensive* at cancellation" is a segment you will want to target later, with different pricing or a win-back offer, and that only works when survey responses live next to behavioral events.
## Ship surveys like product, not email
Everything above depends on asking in-app, at the trigger moment, in a surface that feels native to your product. [Setgreet](https://www.setgreet.com/product) ships [native in-app surveys](https://www.setgreet.com/use-cases/surveys) you can target by event and user attributes, publish without an app release, and read alongside your flow [analytics](https://www.setgreet.com/docs/analytics), so the loop from answer to product change stays short.
## Frequently asked questions
### What response rate should an in-app survey get?
Well-timed single-question in-app surveys routinely reach response rates an order of magnitude above email surveys. If a prompt is converting under roughly one in ten viewers, suspect the moment or the length before the copy.
### How many surveys are too many?
Cap it per user, not per feature: one prompt per session at most, with cool-down windows of weeks between asks. Users forgive being asked; they do not forgive being interviewed.
### Should surveys offer rewards?
For one-tap product surveys, no; incentives distort answers and attract noise. Save rewards for genuinely costly asks like user interviews or long research surveys, and keep those out of the app.
---
# How to update your app's onboarding without waiting for App Store review
Source: https://www.setgreet.com/blog/update-app-without-app-store-review
Published: 2026-07-29
> App review is fast now; your release train is not. How server-driven native UI lets you change onboarding, paywalls, and surveys in live apps the same day, and where the compliance lines actually are.
The way to change your app's onboarding, paywalls, or surveys without shipping a build is server-driven UI: the screens are defined as data on a server, and an SDK inside your app renders that data as native components at runtime. Changing the flow means publishing new data, not new code, so nothing goes through review and every installed copy of the app updates within seconds. It is an established, guideline-compliant pattern used across top apps, and it is different in kind from over-the-air code injection.
## Review is not the bottleneck. Your release train is.
Apple's own [App Review page](https://developer.apple.com/distribute/app-review/) says 90% of submissions are reviewed in under 24 hours, and it is broadly true. The expensive part is everything wrapped around review: the code freeze, the QA pass, the phased rollout, and the long tail of users on old versions. A one-line copy change to your paywall inherits the full weight of that process, which in practice means growth changes ship monthly at best, and the installed base takes weeks to converge on them. Meanwhile the first session, where most of the win-or-lose happens, is exactly the surface you most need to iterate.
## The spectrum of remote control
1. **Feature flags and remote config.** Toggle prebuilt variations. Great for rollouts and kill switches, but every variant must already exist in the binary; you cannot flag your way to a screen you did not ship.
2. **Remote content.** Strings, images, and prices loaded from a server into fixed layouts. Copy changes get cheap; structure stays frozen.
3. **Server-driven UI.** Screen structure itself is data: which screens exist, in what order, with which components, branching, and targeting. New flows and reordered flows ship without a build. This is where onboarding and paywall iteration becomes a daily activity.
4. **OTA code push.** Shipping new executable JavaScript to installed apps. Powerful, but it carries real guideline constraints (interpreted code must not change the app's core purpose or evade review) and real operational risk; it is a deployment strategy for engineers, not a growth surface for product teams.
## Where the compliance lines are
Apple's guidelines restrict downloading executable code that changes what the app fundamentally is or does an end run around review. Server-driven UI sits comfortably inside the lines because what travels is configuration, not code: the components that render it shipped in your reviewed binary, and the app's purpose is unchanged whether onboarding has nine screens or twelve. This is the same architecture the largest apps on the store use for their own home feeds and offer screens. Two honest caveats: content you deliver remotely still has to follow the content and commerce rules (you cannot toggle in features that would themselves fail review, and purchases must stay inside the in-app purchase system where required), and if you later change the app's actual functionality, that is a binary update like always.
## What belongs on the server
The surfaces worth moving out of the binary are the ones you should be changing weekly anyway:
- [Onboarding flows](https://www.setgreet.com/use-cases/onboarding): question order, screen count, value framing.
- Paywalls and [upgrade prompts](https://www.setgreet.com/use-cases/upgrade-prompts): placement, trial framing, pricing display.
- [Surveys](https://www.setgreet.com/use-cases/surveys) and feedback prompts: triggered by events, retired when answered.
- Feature [announcements](https://www.setgreet.com/use-cases/announcements) and permission primers: timed to moments, not releases.
Core product screens, navigation, and anything deeply interactive stay in code, where they belong. The split is not about moving your app to a server; it is about moving the growth surfaces that change twenty times a year.
## What a week looks like once flows live on the server
The point of the architecture is a different operating rhythm, so here is the rhythm, concretely. Monday: the funnel shows a cliff on the goal-selection screen; you reword it and merge two questions, published by lunch, live on every installed version by lunch plus one minute. Wednesday: the cohort data shows the fix working, so you start an A/B test moving the paywall one screen later. Friday: a survey targeted at users who declined the paywall starts collecting objections for next week. Nothing shipped through review; engineering spent the week on the product.
Compare that to the same week on the release train: the Monday fix enters a branch, rides Thursday's release cut, reaches review Friday, rolls out phased next week, and hits most of the installed base the week after, at which point the Wednesday and Friday ideas are still in the backlog. Same team, same ideas, a quarter of the learning per month. Iteration speed is not a nice-to-have in growth work; it is the compounding variable, and release cadence is its ceiling.
## The guardrails that make it safe
- **Draft and publish separation.** Edits happen on a draft; live apps only see published versions.
- **Fallback behavior.** If the network is unavailable, the app proceeds without the flow. Onboarding infrastructure must never be able to brick a session.
- **Versioned rollout.** New flow versions go live atomically; a bad publish rolls back the same way.
- **An audit trail.** When conversion moves, you want to know exactly which change moved it.
This is the architecture [Setgreet](https://www.setgreet.com/product) provides out of the box: flows built in a visual editor or generated by AI, rendered as real native screens on iOS, Android, React Native, and Flutter, published to live apps in seconds, with drafts, targeting, and analytics built in. The [FAQ](https://www.setgreet.com/faq) covers the integration details; most teams are rendering their first server-driven flow the same afternoon.
## Frequently asked questions
### Is server-driven UI allowed by the App Store?
Yes. Configuration-driven native UI is standard practice; the guideline restrictions target downloaded executable code that changes the app's purpose or evades review. Your renderer ships in the reviewed binary; the server supplies data.
### How fast do changes reach users?
Seconds to minutes, depending on the SDK's fetch strategy, and crucially they reach the whole installed base at once, including users who have not updated the app in months. No binary release can do that.
### What happens if a user is offline?
A well-built SDK caches the last published flow and falls back to skipping the flow entirely if nothing is cached. The failure mode of good server-driven UI is "the user sees your app without the flow", never a broken screen.
---
# AI-generated app onboarding: what it can and cannot do in 2026
Source: https://www.setgreet.com/blog/ai-onboarding-flow-generator
Published: 2026-07-29
> An honest look at what an AI onboarding generator can actually do in 2026: grounding on your store listing, category-calibrated structure, and editable native drafts. And what still needs you: positioning, brand voice, and reading the funnel data.
An AI onboarding generator in 2026 can do real work. It can read your App Store listing so the draft is about your app, structure the flow from patterns already common in shipped apps in your category, and hand you editable native screens in your own theme within minutes. What it cannot do is make positioning calls, write in your brand voice, or look at your funnel data and decide what to change next. The honest model is simple: the AI drafts, you stay in control. Here is where the line actually sits.
## What can AI generate well today?
The useful parts of AI onboarding generation are not the flashy parts. Four capabilities have matured enough to be worth your time:
- **Grounding on your real listing.** A good generator starts by reading your App Store page: name, category, description, the value proposition you already wrote. The first draft then talks about your app instead of a generic 'welcome to our product'.
- **Category-calibrated structure.** Instead of inventing layouts, the strongest tools compose from a catalog of screen patterns already common in shipped apps: welcome, personalization questions, permission primers, progress moments, paywall.
- **An editable draft in your theme.** The output should be real screens in your app's colors and type, not a mockup image you have to rebuild by hand.
- **Iteration by chat.** Changing one screen should take a sentence, not a full regeneration.
This is how [Build with AI](https://www.setgreet.com/docs/build-with-ai) works in Setgreet. You describe the flow you want in plain language. The assistant asks a short set of intake questions, proposes a plan of screens in order, and generates only after you confirm. What comes back is a draft made of real, editable native screens in your app's own theme. Nothing goes live until you publish it.
## How does the AI know what your app does?
The biggest failure mode of generic AI design tools is fluent emptiness: polished screens that could belong to any app. The copy says 'unlock your potential', the feature list is invented, and you spend more time deleting than editing. Generation quality was never the bottleneck. Context was.
Grounding fixes most of this. When a generator reads your App Store listing, it inherits language you have already validated in public: your app's name, category, description, and the promise you chose to lead with. The first draft then starts from your actual product instead of from zero. That is the difference between editing and rewriting, and it is why grounding matters more than raw generation quality.
## Why does category calibration matter?
Most structural mistakes in onboarding come from one assumption: that there is a single right length. There is not. Directional estimates from a July 2026 snapshot of [PaywallPro's public Open Paywall Gallery dataset](https://github.com/paywallpro/paywall-gallery) of 910 top iOS subscription apps put the median onboarding plus walkthrough at 17 screens for health and fitness apps, 15 for finance, and just 8 for entertainment. Paywall framing splits the same way: roughly 77% of business apps lead with a free trial, against about 24% of entertainment apps. Those are one third party's directional estimates, not Setgreet data, and they are not gospel.
But the spread is the point. A generator following a flat 'keep it short' rule would truncate a health app's personalization sequence and bloat an entertainment app's. A tool that calibrates screen count and paywall framing to your category starts you inside the plausible range for apps like yours instead of at a generic average. For a deeper look at length specifically, see [how many onboarding screens you actually need](https://www.setgreet.com/blog/how-many-onboarding-screens).
## What still needs a human?
Positioning comes first. An AI can echo the promise in your listing, but it cannot know that you are repositioning this quarter, that a competitor just shipped something adjacent, or that your best-retaining users come from one narrow use case. Which benefit leads on screen one is a strategy decision, and it is yours.
Brand voice is second. Grounded AI copy is competent and neutral, which is exactly what a first draft should be. A voice people recognize, the phrasing that sounds like you and nobody else, still has to come from someone who knows what your brand refuses to sound like.
Interpretation is third, and it compounds. Your [flow analytics](https://www.setgreet.com/docs/analytics) can show you exactly which screen users abandon. No AI can tell you whether that drop means the screen is confusing, the ask is premature, or the audience is wrong. That call is judgment, and the honest way to test it is a controlled [A/B experiment](https://www.setgreet.com/docs/experiments), not a confident guess from a model.
## What does staying in control look like?
In practice, control means three checkpoints. The AI never talks to your users directly: it produces a draft you review. You can edit anything, and editing should be cheap: in Setgreet, chat edits target the draft, the canvas updates as you go, and editing by chat spends no AI credits. And nothing ships until you explicitly publish.
The last step closes the loop. Because a published [onboarding flow](https://www.setgreet.com/use-cases/onboarding) renders natively and updates without an App Store release, the change you decide on in the morning can reach users the same day, and the data from that change informs the next draft you ask for. The AI accelerates each cycle. You still steer every one of them.
## Frequently asked questions
### Can an AI onboarding generator replace a designer or growth team?
No, and tools that imply otherwise are overselling. AI generation compresses the production work: pattern research, first-draft screens, theme-matched styling, and copy scaffolding. The decisions that determine whether onboarding converts remain human: positioning, voice, and what to change after reading the data. Treat the AI as a fast first draft with a patient editor attached, not as an autopilot.
### How many screens should an AI generate for app onboarding?
It depends on your category, which is exactly why a flat rule fails. The third-party medians cited above range from 8 screens for entertainment apps to 17 for health and fitness. A good generator calibrates its draft to your category's norms, and you should still cut any screen that does not earn its place with a question, a permission, or a promise the user cares about.
### Are AI-generated onboarding flows real native screens?
They should be, because a static mockup you have to rebuild by hand is not much of a head start. In Setgreet, generated drafts are real native screens rendered by SDKs for iOS, Android, React Native, and Flutter. You edit them in the flow builder or by chat, and publishing pushes the update to your live app without waiting on an App Store release.
---
# What an MCP server can do for your mobile app
Source: https://www.setgreet.com/blog/mcp-server-for-mobile-apps
Published: 2026-07-29
> What can an MCP server do for your mobile app? A plain-English look at the Model Context Protocol and the loop it enables: compose an onboarding flow from a chat in Claude or Cursor, publish it, check the funnel, run an experiment, and translate it.
An MCP server lets an AI assistant do real work on your mobile app instead of just talking about it. The Model Context Protocol (MCP) is a standard way to give assistants like Claude and Cursor a catalog of tools they can call. Connect once, and the chat you already use can draft an onboarding flow, publish it to your live app, answer funnel questions in plain language, start an experiment, and translate screens into new languages. Every action runs under your own account and your role's permissions, and you direct each step.
## What is the Model Context Protocol?
MCP is an open standard that defines how AI assistants discover and call external tools. A tool is a typed function with a name, a description, and an input schema: think publish_flow, create_experiment, or execute_analytics_query. When you connect an MCP server, your assistant reads the tool catalog and picks the tool that fits whatever you typed. No copy-pasting API docs into the chat, no glue scripts to maintain.
Servers can run locally, but the convenient pattern for app teams is a hosted one. Setgreet's MCP server lives at mcp.setgreet.com: nothing to install, no local process, no API key to manage. Documented clients include Claude Desktop, Claude Code, Cursor, VS Code, Windsurf, and Codex CLI, plus any spec-compliant MCP client.
## What does the loop look like for a mobile app?
Here is the loop a connected assistant can run end to end, using Setgreet's tool catalog as the worked example. The subject is the thing your growth actually depends on: the [onboarding flow](https://www.setgreet.com/use-cases/onboarding), the paywall, the surveys. Each step is a single chat message.
1. **Compose.** Describe the flow you want, in your words. The compose_flow tool asks a short set of intake questions, proposes a plan of screens, and generates a draft made of real, editable native screens. Generating a flow spends an AI credit. Nothing is live yet.
2. **Publish.** When the draft looks right, publish_flow pushes it to your live app without an App Store release. Even a fast review is still a cycle: Apple reports [90% of submissions are reviewed in under 24 hours](https://developer.apple.com/distribute/app-review/), which still means waiting a day to fix a line of onboarding copy.
3. **Measure.** get_flow_dashboard and get_flow_paths return completion rates and drop-off by screen. For anything off the beaten path, ask in plain language: execute_analytics_query turns the question into a typed, org-scoped query, not raw SQL.
4. **Experiment.** create_experiment sets up variants, and get_experiment_results reports per-variant numbers with statistical significance.
5. **Translate.** bulk_translate_flow localizes every screen while preserving {{personalization}} variables, so "Welcome back, {{user.firstName}}" survives in every language.
The volume of iteration this unlocks is the point. An analysis published by Adapty in 2026 found that apps running 50 or more experiments had 18.7x the median revenue of apps running one. That is correlation, not causation, but the direction is hard to ignore: teams that test more learn faster, and lowering the cost of each test is exactly what a tool loop does. The compose and edit tools sit on the same engine as [Build with AI](https://www.setgreet.com/docs/build-with-ai) in the dashboard, and [experiments](https://www.setgreet.com/docs/experiments) work the same way in both places.
## How do you connect?
Add https://mcp.setgreet.com/mcp to your client's MCP settings. The first tool call opens a browser window where you sign in with your existing Setgreet account: no API key is generated, copied, or pasted anywhere. Under the hood this is OAuth 2.1 with PKCE. Access tokens last five minutes, refresh tokens rotate and expire after 30 days, and every session is listed in Settings under MCP Connections, where you can revoke any client at any time. Per-client setup guides live in the [MCP docs](https://www.setgreet.com/docs/mcp).
## What stays in your control?
Permissions come from your dashboard role, not from whatever the AI client requests. A viewer gets read access; editing and publishing require member access or above. A client cannot talk its way into scopes your role does not grant.
Destructive operations, such as deleting a flow, unpublishing, or completing an experiment, require an explicit confirmation before the server will execute them. Chat edits target the draft, never the published version, and the flow editor's canvas hot-reloads so you can watch each change land. If two edits collide, the second one returns a conflict instead of silently overwriting the first.
Costs are visible too. Generating a flow spends AI credits, editing by chat is free, and your balance shows in the flow editor and in Settings under Billing.
## When does MCP beat the dashboard?
MCP wins wherever the work is repetitive or question-shaped. Renaming a CTA across every screen, translating a flow into five languages, or rerunning last week's funnel check are one message each instead of a run of clicks. It also wins for [analytics](https://www.setgreet.com/docs/analytics) questions you would otherwise build a chart to answer: "where do users drop off?" is faster to type than to configure. And it wins for chained work: "duplicate the flow, swap the paywall screen, start a 50/50 experiment" is a single request.
The visual editor wins at visual work. Spacing, imagery, color, and the feel of a screen on an actual device are things you judge with your eyes, and a chat transcript cannot show you that. Fine-tuning a layout by describing it is slower than dragging it.
The two are not in competition. Chat edits and editor edits land in the same draft, so a common afternoon looks like: compose in chat, polish in the editor, publish, then ask the assistant how the funnel moved.
## Frequently asked questions
### Do I need to install anything to use Setgreet's MCP server?
No. It is hosted at https://mcp.setgreet.com/mcp over Streamable HTTP, so you add the URL to your client's MCP settings and sign in through the browser when prompted. Claude Desktop, Claude Code, Cursor, VS Code, Windsurf, and Codex CLI are all documented clients. ChatGPT support exists but is in beta with plan restrictions, so do not expect full parity there yet.
### Can the AI publish or delete things without me?
The assistant only acts when you send it a request, and always within the scopes your dashboard role grants. Destructive operations like deleting a flow, unpublishing, or completing an experiment require explicit confirmation before they run. Publishing executes when you ask for it, so treat a publish request as the deliberate step it is, and you can revoke any connected client from Settings under MCP Connections at any time.
### Does using the MCP server cost extra?
Generating a flow with compose_flow spends AI credits, editing by chat is free, and translation spends credits proportional to the work. Your current balance is always visible in Settings under Billing, and top-up packs are available if you run out mid-sprint. For plan details, see [pricing](https://www.setgreet.com/pricing).
---
# From Figma to live app onboarding, without the handoff
Source: https://www.setgreet.com/blog/figma-to-app-onboarding
Published: 2026-07-29
> Design handoff turns every onboarding tweak into an engineering ticket. Here is what that costs, and how a direct Figma to app import path turns your frames into native flow screens you can review, adjust, and publish without a release.
You can go from Figma design to live app onboarding without a handoff by importing your frames directly as flow screens. The Setgreet Figma plugin turns selected frames into a draft flow: frames become screens, layers become native components, and Auto Layout maps to a Flexbox layout system. You review the mapping in a panel before anything is created, adjust what detection got wrong, and publish to your live app without an App Store release. No engineering ticket, no re-implementation, no weeks of drift between what the designer shipped and what users see.
## What the handoff really costs
The standard pipeline looks like this: a designer finishes the onboarding redesign in Figma, writes a handoff doc, and opens a ticket. An engineer re-implements every screen in SwiftUI or Compose, interpreting spacing, radii, and type styles by inspecting the file. Then the work waits for the next release train, then for review, then for users to update.
Notice where the time actually goes. Review itself is fast: [Apple reports that 90% of submissions are reviewed in under 24 hours](https://developer.apple.com/distribute/app-review/). The expensive part is everything before it: the re-implementation, the QA pass on two platforms, the release schedule. And after all that, details drift anyway. The padding is 12 instead of 16. The button color is close but not the token. Nobody made a mistake; translating a design between two mediums by hand just loses information.
For a settings screen you touch once a year, that cost is tolerable. For growth screens, it compounds.
## Why onboarding pays the highest price
Your [onboarding flow](https://www.setgreet.com/use-cases/onboarding) is the part of your app you should change most often, and under a handoff model it is the part that is most expensive to change. Every reordered screen, every copy test, every new value proposition slide is a design file, a ticket, a rebuild, and a release.
So teams stop iterating. The onboarding that shipped at launch is still running eight months later, not because it won, but because touching it costs a sprint. The teams that win move the other way: an analysis published by Adapty in 2026 found that apps running 50 or more experiments had 18.7x the median revenue of apps running one. That is correlation, not causation, but the direction is hard to ignore. If you want to [A/B test your onboarding flow](https://www.setgreet.com/blog/ab-test-onboarding-flow) at any real cadence, the marginal cost of shipping a variant has to fall toward zero. Handoff keeps it high.
## How a direct Figma to app import works
With the [Setgreet Figma plugin](https://www.setgreet.com/docs/figma-plugin), you sign in with your dashboard credentials, select 1 to 20 frames, and import them as a draft [flow](https://www.setgreet.com/docs/flows). Each frame becomes a screen. Layers become components: Text, Button, Image, Container, Card, Divider, and more. Auto Layout maps to Setgreet's Flexbox layout system, so the structure that made your design responsive in Figma stays responsive in the app. Image fills and complex vector fills are exported as PNGs and uploaded to storage automatically.
Detection is transparent rather than magical. Before anything is created, a mapping panel shows what the plugin thinks each layer is, with green, yellow, and red confidence indicators, and you can override any mapping per component. If you name layers with [type] prefixes, like [button] or [image], those map with full confidence. The result is a faithful structural import: real, editable components in the flow editor, not a flat screenshot of your design.
The output is always a draft with a linear graph from start to end. Nothing reaches users until you publish, and once you do, later changes ship to your live app [without an App Store release](https://www.setgreet.com/blog/update-app-without-app-store-review).
## Where manual adjustment still happens
An honest import path is clear about what it does not do. The plugin brings in structure and content, not behavior: button actions, navigation, triggers, and branching are set up in the editor after import. Imports are linear only, and each import creates a new flow rather than updating an existing one, so treat it as the start of an iteration, not a live sync with your Figma file.
A few visual details need review too. Detection is good, not perfect; that is exactly what the mapping panel is for. Mixed text styles inside a single text layer are not supported, complex vectors fall back to PNG, and all text renders in Inter today, with custom fonts on the roadmap. So plan for a short pass in the editor after import: wire up the buttons, confirm the mapping, check spacing on a device preview. That pass is small compared to re-implementing every screen by hand, and it ends with screens you can keep editing without ever going back through a ticket.
## Meeting your app's design tokens
Imported screens should look like they belong in your app, and that comes down to design tokens. Setgreet's App Theme system syncs your app's visual identity, its colors, typography, and spacing, into the flow editor. On iOS and Android the sync happens automatically when the SDK initializes; on React Native and Flutter you call syncTheme() once. Either way, the editor holds your app's real palette: 29 semantic color roles, light plus optional dark, and a 15-style type scale.
That matters for imports because you adjust screens against your actual tokens instead of eyeballing hex values from a Figma file. Style a button with @theme tokens like @primary or @onSurface and it resolves from your app's own palette when the flow is instantiated, so every platform renders the same resolved theme. And if your designer refines colors by hand in the dashboard's Brand tab, those manual edits are never overwritten by a later SDK sync. The design file gets you the structure; the theme keeps it native.
## Frequently asked questions
### Does an imported Figma flow go live immediately?
No. Every import creates a draft flow with a linear graph from start to end, and nothing reaches users until you publish it. That gives you room to wire up button actions, set triggers, and review the component mapping first. Once you publish, updates roll out to your live app without waiting on an App Store release.
### Can the plugin update an existing flow?
Not today. Each import creates a new draft flow rather than merging changes into one that already exists. If you have redesigned a live onboarding, import the new frames as a fresh draft, rebuild the behavior in the editor, and publish it when it is ready to replace the old version.
### Will my brand fonts come through from Figma?
Text imports as editable text components, but everything renders in Inter today; custom fonts through the plugin are on the roadmap, so do not count on brand typography from the import alone. Colors are a different story: sync your App Theme and imported screens can use your app's real semantic color roles through @theme tokens.
---
# App onboarding templates: start from structure, not from scratch
Source: https://www.setgreet.com/blog/app-onboarding-templates
Published: 2026-07-29
> App onboarding templates work when they encode structure: question order, commitment loops, paywall placement. What a good onboarding flow template contains, how to adapt one to your app without breaking it, and when to deviate.
App onboarding templates save you time only when they encode structure, not styling. A good template captures the decisions that make an onboarding flow convert: which questions come first, where the user commits to a goal, and where the paywall sits. Start from a template built for your app category, rewrite the words and match the visuals to your brand, and keep the underlying logic intact. Deviate from the structure only when you have evidence your app is the exception.
## Why most onboarding templates fail you
Search for onboarding templates and you will mostly find screen kits: gradient backgrounds, illustration packs, page indicators in three tasteful dots. These solve a problem you probably do not have. Visual polish is rarely the reason an onboarding flow underperforms. Structure is.
Structure means the decisions users never notice as decisions. Do you ask about goals before or after showing features? Does the user pick something, anything, before the paywall appears? Does the notification prompt come after you have demonstrated value, or on screen two? Two flows with identical screens in a different order can perform very differently, because sequencing controls motivation. A screen kit hands you twelve pretty layouts and leaves every one of those decisions to you.
So judge a template with one question: does it tell you what happens, in what order, and why? If it only tells you what things look like, it is a UI kit wearing a template's name.
## What a good onboarding template contains
A template worth cloning encodes at least four things:
- **Question order.** Profile questions run from low-friction to personal, and each answer visibly shapes what comes next. An answer that changes nothing teaches users to stop answering honestly.
- **A commitment loop.** The user states a goal and the flow reflects it back: a plan, a projection, a named outcome. People who articulate a goal are more invested in every screen that follows.
- **Paywall placement and framing.** Where the paywall sits, what the user has seen and said before reaching it, and whether it leads with a trial. This is the most consequential structural choice in the flow, and [where the paywall goes](https://www.setgreet.com/blog/onboarding-paywall-placement) deserves its own analysis.
- **Category-appropriate length.** How many screens the flow runs before handing over the app.
That last one surprises people. Directional estimates from a July 2026 snapshot of PaywallPro's public [Open Paywall Gallery](https://github.com/paywallpro/paywall-gallery) dataset of 910 top iOS subscription apps put median onboarding and walkthrough length at 17 screens for health and fitness apps, 15 for finance, and 8 for entertainment. Treat those as one third party's estimates, not gospel, but the spread is the point: users arrive with category-shaped expectations, and [the right screen count](https://www.setgreet.com/blog/how-many-onboarding-screens) for a meditation app would be bloat in a utility. A good template already sits near its category's norm.
## How do you adapt a template without breaking it?
Three rules cover most of it.
**Rename the questions, not the structure.** If the template asks "What's your fitness goal?" and you make a language app, ask "What's your learning goal?" Keep the position, the answer-driven follow-up, and the commitment moment the question feeds. The words are yours to change; the sequencing is the part that was proven.
**Match your theme, not the template's.** A flow that arrives styled in someone else's brand tells users they have left your app. Colors, type, and spacing should be indistinguishable from the product around the flow.
**Keep the paywall logic.** The paywall's position, and its framing relative to the commitment loop, is the template's most valuable cargo. Move it last, if at all, and only with a test behind the move.
This is the model behind Setgreet's Flow Store: curated, multi-screen Flow Templates organized by app category and goal. The catalog is deliberately small and curated today and grows on that standard, so treat it as a vetted starting shelf rather than an endless template wall. "Add to editor" clones a template into a draft [onboarding flow](https://www.setgreet.com/use-cases/onboarding) for free, substituting your app's name and resolving the template's color tokens against your app's theme palette, so the screens land looking like your product rather than the template. "[Build with AI](https://www.setgreet.com/docs/build-with-ai)" spends one AI credit to re-compose the same structure with copy drafted from your app's store listing and profile. Both paths produce a draft you review and edit; nothing goes live until you publish.
## When should you deviate from the template?
Deviate when your app genuinely breaks the category pattern, not when a screen merely feels long. The same PaywallPro snapshot estimates that roughly 77% of business app paywalls lead with a free trial, while only about 24% of entertainment paywalls do. If a template assumes trial-led framing and your app monetizes with a one-time purchase, that assumption is wrong for you: change it. The same goes for permission-heavy apps that must ask early, or products whose core value shows up in seconds and needs no buildup.
When you do deviate, change one structural element at a time and [measure it as an experiment](https://www.setgreet.com/docs/experiments). A template you have modified in five places at once teaches you nothing when the numbers move.
## Treat the template as version one
A template's job is to make your first version good, not your last version final. Once the flow is live, watch where users drop off screen by screen and rework the weakest step. This is where release cycles hurt: if every copy tweak rides an app update, you iterate quarterly instead of weekly. Setgreet publishes flow changes to live apps through its native SDKs without an App Store release, so the gap between "screen four is leaking" and "screen four is fixed" is minutes, not a release cycle. The template gives you a strong starting structure; fast iteration is what makes it yours.
## Frequently asked questions
### Are app onboarding templates worth using?
Yes, when they encode structure. A template that encodes the structure strong flows in your category share gives you a considered question order, a commitment loop, and a deliberate paywall position on day one. A template that is only a visual kit saves you an afternoon of layout work and nothing else. Judge templates by the decisions they make for you, not by how the screens look.
### How do I customize an onboarding template for my app?
Rewrite the copy so every question and claim is about your product, restyle the screens to your app's colors and type so the flow feels native, and localize where needed. Leave the sequencing, the commitment loop, and the paywall position alone until live data argues otherwise. In Setgreet's Flow Store, cloning a template substitutes your app name and resolves colors from your app's theme automatically, so customization starts at the copy level instead of the pixel level.
### When should I build my onboarding from scratch instead?
Build from scratch when no close template exists for your category or business model: an unusual monetization approach, a legally constrained flow, or a product whose value cannot be shown without live data. Even then, borrow structure. Study how apps in the nearest category order their questions and place their paywalls, and treat those patterns as your draft zero. A blank canvas usually means rediscovering conventions your users already expect.
---
# Localizing app onboarding: translate the screens that pay first
Source: https://www.setgreet.com/blog/mobile-app-localization-onboarding
Published: 2026-07-29
> Most teams treat mobile app localization as a final polish step. The higher-ROI move is to localize app onboarding and paywall screens first, then ship translations to live apps without waiting on an App Store release.
Which parts of your app should you localize first? The screens that pay first: onboarding and the paywall. Most teams treat mobile app localization as a final polish pass, translating every settings page and error message before entering a market. That order is backwards. A non-English user decides whether to keep your app in the first session, long before they ever open settings. Localize the first-session screens first, adapt more than the words, plan for text expansion, and ship translations without waiting on an app release.
## Why the first session decides if localization pays
A new user opens your app seconds after install with zero context and zero commitment. If the screens explaining your value are in a language they only half understand, most will not push through. They quietly close the app, and you never get a second first session. An English settings screen is a minor annoyance for a retained user. An English onboarding screen is a lost user.
That asymmetry is why localization ROI is front-loaded. Every user sees onboarding, and they see it at the moment of lowest trust. Only your most engaged users ever reach the long tail of secondary screens. Translating your [onboarding flow](https://www.setgreet.com/use-cases/onboarding) into five languages will usually do more for international retention than translating your entire app into one.
## Onboarding and the paywall are one surface
Treat the paywall as part of onboarding, not a separate project. In most subscription apps the first session runs straight from welcome screens into a trial offer, so the experience shifts from learning to paying in a single swipe. If the screen asking for money suddenly flips to English at that moment, you break trust exactly where trust matters most.
Paywall copy also carries the highest comprehension stakes in your app. Users need to understand what a trial is, when it converts, what the renewal price is, and how to cancel. Ambiguity a native speaker would shrug off becomes a hard stop in a second language, and misunderstood trial terms turn into refunds and one-star reviews. Where the offer sits in the flow matters too; see our guide to [onboarding paywall placement](https://www.setgreet.com/blog/onboarding-paywall-placement).
## Localize more than the strings
Translation is the floor, not the finish line. Three things deserve attention beyond the words themselves:
- **Imagery and screenshots.** Product screenshots with English UI baked into the image undo the translated text around them. Review photos and illustrations for gestures, symbols, and cultural context that do not travel.
- **Formality.** Many languages force a choice English hides: tu or vous in French, du or Sie in German, distinct politeness registers in Japanese and Korean. Pick the register that matches your brand and keep it consistent from the first welcome screen through the paywall.
- **Numbers, dates, and prices.** 1,000.50 in one market is 1.000,50 in another, and currency symbols switch sides. A price formatted the wrong way on a payment screen reads as careless at best and untrustworthy at worst.
If you personalize screens with user data, formatting has to follow the language too. Setgreet's [Variable Bindings](https://www.setgreet.com/docs/personalization) support locale-aware currency and number formatting through format modifiers, so a bound value renders in the user's local conventions instead of defaulting to English ones.
## Plan for text expansion before it breaks your layout
Translated text rarely matches the length of the English original. German compounds stretch, French runs long, and a button label that fit comfortably in English can truncate, wrap awkwardly, or push your call to action off the visible screen. These bugs hide until a real user in a real locale hits them.
Pseudo-localization catches much of this early: replace your strings with padded, accented versions and watch what breaks. But it has pitfalls of its own. It only tests expansion, not meaning, so a layout can pass while the translation still reads wrong in context. It will not catch text baked into images. And it says nothing about strings concatenated in code, which can become grammatical nonsense once word order changes.
So design for elasticity: avoid fixed-width text containers, let buttons grow, and review your longest target language rather than your prettiest. Flexible layouts that reflow with content absorb routine expansion far better than pixel-perfect frames. Then open every language and actually read every screen before it goes live. No automated check replaces that pass.
## Ship translations without waiting on a release
The classic localization tax is the release cycle. Strings live in the app binary, so every new language, and every typo fix, waits on a build, a review, and users actually updating. Apple reports that [90% of submissions are reviewed in under 24 hours](https://developer.apple.com/distribute/app-review/), but review is only one step: you still cut a full release for a one-word change, and users on older versions never see it.
Server-delivered screens remove that tax. In Setgreet, you build onboarding and paywall flows once in your base language, then add any of 100+ languages from the Localization modal. [Auto-translate with AI](https://www.setgreet.com/docs/build-with-ai), which is rolling out gradually and may not be enabled on every workspace yet, drafts the missing text, shows the credit cost before it runs, and preserves binding expressions exactly; every line stays editable, so a native-speaking reviewer keeps final say. When you publish, live apps pick up the new language with no App Store release, the SDK detects the device locale and serves the matching translation, and anything untranslated falls back to your base text per property. A half-finished language shows mixed text, never a blank screen. More on this model in [updating your app without App Store review](https://www.setgreet.com/blog/update-app-without-app-store-review).
Start small. Pick your top non-English market, localize the first session end to end, and watch whether day-one retention moves before you widen the net.
## Frequently asked questions
### Which languages should you localize first?
Let your install data decide. Look at where downloads and store page views already come from, then compare first-session retention across those markets. Most apps find a shortlist of countries where demand clearly exists but non-English users bounce early. Those gaps are the highest-ROI targets because the users are already arriving; you are just losing them at hello. Localize the first session for that shortlist before adding more languages anywhere else.
### Do users need to update the app to get new translations?
Not if your first-session screens are delivered from the server. With Setgreet, publishing a translation updates live apps without an App Store release or a user-side update, so a fixed mistranslation reaches everyone at once. One detail to plan for: device locale matching is exact, so es-MX will not match a generic es translation. Add the specific locales your users actually run, not just the parent language.
### What happens to text you have not translated yet?
In Setgreet, any property without a translation falls back to the base language text, property by property. A partially translated screen shows finished lines in the target language and the rest in your base English, rather than blanks or raw keys. That makes incremental localization safe: ship the languages you have and fill gaps as you go. Keep the base English version complete and current, since it is the safety net behind every other language.
---
# Personalized app onboarding: use what users tell you
Source: https://www.setgreet.com/blog/personalized-app-onboarding
Published: 2026-07-29
> Personalized app onboarding is the gap between asking users questions and acting on the answers. Learn three levels of acting on them: reflect answers back in your copy, branch the flow on answers, and target entire flows at segments.
Personalized app onboarding means acting on what users tell you, not just collecting it. Most onboarding flows ask about goals, experience level, or use case, then show every user the same screens anyway. The gap between asking and acting is where personalization lives. There are three levels: reflect answers back in your copy, branch the flow so different users see different paths, and target entire flows at segments defined by attributes and events. Each level takes more setup and pays off differently. Here is how to build all three.
## Why asking without acting backfires
Users notice when a question goes nowhere. If screen two asks 'What brings you here?' and screen three is identical for everyone, the question was a survey for your team, not a service to the user. That is worse than not asking at all: you spent taps and attention, and you signaled that the answers do not matter.
The fix is a simple rule: **never ask a question whose answer changes nothing the user can see.** Every question in your [onboarding flow](https://www.setgreet.com/use-cases/onboarding) should alter the copy, alter the path, or alter what the user is shown later. If it does none of those, cut it or move it to an [in-app survey](https://www.setgreet.com/blog/in-app-survey-questions) after activation. The rest of this post maps the three ways an answer can change the experience, from cheapest to most structural, with the concrete mechanics behind each one.
## Level 1: reflect answers back in your copy
The cheapest personalization is reflecting what you already know into your copy. In Setgreet this is done with Variable Bindings: write {{user.firstName}} or {{user.goal}} into any string property of a screen, including text, button labels, and image URLs, and mix bindings with static text freely. The user. prefix pulls attributes set through the SDK's identifyUser call; the event. prefix reads properties of the event that triggered the flow. Dot notation reaches nested paths, and pipe modifiers format values: dates, currency, uppercase, and more, with currency and numbers formatted for the user's locale.
Two details matter in practice. First, resolution happens on the server when the flow is fetched, so the device receives finished text and a raw {{...}} token never reaches the screen. Second, set a fallback value on every visible binding. A missing attribute resolves to the fallback if you set one, and to an empty string if you did not, so 'Welcome back, {{user.firstName}}' renders as 'Welcome back, ' for a user you have not identified yet. Write copy that still reads well when the fallback shows. The [personalization docs](https://www.setgreet.com/docs/personalization) cover the full syntax.
## Level 2: branch the flow on answers
Reflected copy is nice; a different path is persuasive. Level 2 branches the flow on the answer itself. In Setgreet's flow builder, a Branch node maps each option value of a question to a different sequence of screens: a fitness app can ask 'What is your goal?' on a RadioGroup and route strength, weight loss, and mobility users to different feature tours. Branching works with the branchable components: RadioGroup, NPS, Rating, Dropdown, Checkbox, and Button.
The compounding trick is Save to Attribute. Enable it on a Branch node and the response is persisted as a user attribute, so the answer outlives this flow: future flows can bind it into copy, trigger on it, and build segments from it. One honest caveat: an answer captured mid-flow is not automatically available to later screens of the same flow, so treat Save to Attribute as an investment in every flow after this one, not as a way to echo the answer two screens later.
## Level 3: target entire flows at segments
Levels 1 and 2 personalize inside one flow. Level 3 decides who gets a flow at all. Segments are reusable, app-scoped user groups built in the condition builder, and they evaluate dynamically: when a user's attributes change, their segment membership follows. Flow Triggers then combine segments and further conditions on user attributes and event properties with AND/OR logic, using operators like equals, contains, startsWith, and greaterThan across strings, numbers, booleans, and dates. When several flows match the same user, the highest-priority flow wins, so a general onboarding can sit underneath segment-specific flows without conflicts.
This is where answers you saved at Level 2 pay rent. A goal answer saved during onboarding becomes a [segment](https://www.setgreet.com/docs/audience) you can target next month with a goal-specific announcement or a tailored upgrade prompt. And because flows publish to live apps without an App Store release, adding a segment-targeted flow is a dashboard change, not a build.
## Only personalize what users can notice
Personalization has a cost: more paths to write, translate, and maintain. It only pays when the differences are visible to users. If your strength and mobility branches differ by one adjective, you bought complexity and shipped nothing anyone can perceive. Before you branch, name the thing each segment will see that the others will not. If you cannot, use a binding instead of a branch, or skip it entirely.
Monetization is a useful test case, because expectations genuinely differ by audience. Directional estimates from a July 2026 snapshot of PaywallPro's public Open Paywall Gallery dataset of 910 top iOS subscription apps suggest about 77% of Business apps lead their paywall with a trial, versus roughly 24% of Entertainment apps. If your app serves audiences that different, how you frame the offer is a visible, consequential difference worth branching on. Then verify: treat each personalized path as a hypothesis and [A/B test it](https://www.setgreet.com/blog/ab-test-onboarding-flow) against the generic version before you scale the pattern.
## Frequently asked questions
### What happens if a user attribute is missing?
The binding resolves to its fallback value if you set one, and to an empty string if you did not. The raw {{user.firstName}} token is never shown, because bindings are resolved on the server before the screen reaches the device. The practical rule: give every user-visible binding a fallback, and write the sentence so it reads naturally when the fallback appears, for example 'Welcome, {{user.firstName}}' with a fallback of 'there'.
### Can I personalize onboarding for anonymous users?
Partially. Anonymous users cannot carry custom attributes, so attribute-based bindings and segment targeting require an identified user via the SDK's identifyUser call. What still works is in-flow branching: an anonymous user can answer a question and be routed down the matching path in the same session. To unlock the other two levels, call identifyUser as early as your signup allows so later flows can bind and target on real attributes.
### Do I need a separate flow for every segment?
No, and you usually should not start there. Begin with one flow that uses bindings for names and goals, add a Branch node where paths genuinely diverge, and reserve fully separate segment-targeted flows for cases where most of the content differs, like new users versus returning upgraders. Because the highest-priority flow wins when several match, you can keep a generic flow as the default and layer segment-specific flows above it as each one earns its maintenance cost.
---
# Flutter onboarding screens: packages, custom builds, or server-driven flows
Source: https://www.setgreet.com/blog/flutter-onboarding-screens
Published: 2026-07-29
> Three honest ways to build Flutter onboarding screens: pub.dev packages, custom widget flows, or server-driven flows you can update without a store release. What each option solves, what it doesn't, and how to pick based on how often your onboarding will change.
You have three ways to build Flutter onboarding screens: a pub.dev package like introduction_screen, a custom widget flow you write yourself, or a server-driven approach where the screens live outside your binary. Packages get you a swipeable walkthrough in an afternoon. Custom builds give you full control over design and logic. Server-driven flows let you change copy, screens, and targeting after release without shipping a new build. The right answer comes down to one question: how often will this onboarding change once real users hit it?
## Why Flutter teams build onboarding by hand
Flutter makes UI feel cheap. A PageView, a few Columns, a dot indicator, and you have a working walkthrough before lunch. That is exactly why most Flutter teams never seriously evaluate alternatives: when everything is a widget, onboarding looks like just another widget tree.
The problem is not building it. The problem is everything after. Onboarding is the highest-traffic surface in your app, the first thing every new user sees, and the screen you will want to change most often as you learn what converts. A widget tree you wrote in a day becomes a surface you rebuild and re-release every time someone wants to test a new value proposition. The initial build is the easy ten percent.
## What packages like introduction_screen actually solve
Packages such as introduction_screen, intro_slider, and smooth_page_indicator handle the mechanical parts: swipeable pages, indicators, skip and done buttons, sensible transitions. If your onboarding is a static three-page walkthrough explaining what the app does, a package is the fastest honest answer. Add your copy, wire up the done callback, ship.
Be clear about what they do not solve. The content is still compiled into your binary, so every copy tweak is a release. There is no targeting: new users, returning users, and users arriving from a specific campaign all see the same screens. There is no built-in analytics, so you instrument drop-off yourself. And there is no way to compare two versions without maintaining both code paths behind a flag.
Packages solve layout. They do not solve iteration. For a walkthrough you expect to touch twice a year, that trade is fine.
## When a custom build is the right call
Write it yourself when onboarding is part of your product's core logic, not just an intro. Branching questionnaires that configure the app, form inputs that feed your backend, animations that carry your brand: these outgrow package APIs quickly, and fighting a package's customization options is worse than owning the code.
The honest cost list: every screen is code you maintain, every analytics event is instrumentation you write, and every experiment means a feature flag plus two living variants. Teams that do this well treat onboarding as a named area of ownership, not a task someone finished in sprint three. Without that, a custom build slowly fossilizes: it works, nobody wants to touch it, and it stops improving. The same calculus applies on other cross-platform stacks; we walked through it for React Native in our [React Native onboarding guide](https://www.setgreet.com/blog/react-native-onboarding-flow).
## The real tax: every tweak ships through both stores
Here is the cost packages and custom builds share. Change one headline and the path to production is: edit the string, bump the version, build for iOS and Android, submit to both stores, wait for review, then wait days more for users to actually install the update.
Review itself is fast now. [Apple's App Review page](https://developer.apple.com/distribute/app-review/) states that 90% of submissions are reviewed in under 24 hours. But review was never the real bottleneck. The bottleneck is the batching it forces: because each change costs a full release cycle, teams queue onboarding tweaks behind the next feature release, and a copy test that should take an afternoon waits three weeks. Worse, slow iteration quietly convinces teams to stop proposing changes at all. We covered the alternatives in [how to update your app without an App Store review](https://www.setgreet.com/blog/update-app-without-app-store-review).
## Server-driven flows: the third option
A server-driven approach moves the onboarding definition out of the binary. Your Flutter app ships with a lightweight SDK; the screens, copy, order, and targeting live on a server and you edit them from a dashboard. Publish a change and the next user who opens the app sees it. No version bump, no review queue, no waiting for update adoption.
This is what Setgreet does. The [Setgreet Flutter SDK](https://www.setgreet.com/docs/sdk) supports Dart on Flutter 3.0 and up, and it renders flows with fully native UI components, SwiftUI on iOS and Jetpack Compose on Android, with no web views or iframes, using flexbox layout that adapts to any screen size. You identify users with identifyUser, track behavior with trackEvent, and trigger flows off events and user attributes, so a returning user can get a different [onboarding flow](https://www.setgreet.com/use-cases/onboarding) than a brand-new signup. Note that flows are fetched over the network, so this approach assumes a connection at display time.
The fit test is simple. If onboarding is a settled, static walkthrough, a package stays cheaper. If it is a growth surface you plan to tune monthly, paying the integration cost once beats paying the release tax forever.
## How to choose
- Static walkthrough that rarely changes: use a package and move on.
- Onboarding is core product logic, deeply wired into your app's state: build it yourself and staff its ownership.
- Onboarding is a conversion surface you want to iterate on weekly or monthly: go server-driven so changes skip the release cycle.
These options also combine. Plenty of teams keep account creation and permissions in custom widgets, then hand the educational and promotional screens to a server-driven flow where fast iteration matters most.
## Frequently asked questions
### How many onboarding screens should a Flutter app have?
Category matters more than framework. Directional estimates from a July 2026 snapshot of [PaywallPro's public Open Paywall Gallery dataset](https://github.com/paywallpro/paywall-gallery) of 910 top iOS subscription apps put the median onboarding plus walkthrough length at 17 screens for health and fitness apps but only 8 for entertainment apps. Those are that third party's estimates, not Setgreet data, and top apps skew long because they front-load personalization questions. Start near your category's norm and let your own completion data decide; our [guide to onboarding length](https://www.setgreet.com/blog/how-many-onboarding-screens) goes deeper.
### Can I change Flutter onboarding without releasing a new app version?
Not if the screens are compiled into your binary, which is true for both packages and custom builds: every change goes through a build, two store submissions, and user update adoption. Server-driven flows avoid this because the screens are remotely managed content rather than code, so publishing an edit changes what users see the next time the app fetches the flow, with no store release involved.
### How do I A/B test onboarding in Flutter?
It depends on where the screens live. With compiled onboarding, you maintain both variants in code behind a feature flag and wire up your own exposure and conversion tracking. With server-driven onboarding, variants are content changes rather than code changes, which removes the release cycle from each test. Either way, decide your success metric before the test ships; our [guide to A/B testing onboarding flows](https://www.setgreet.com/blog/ab-test-onboarding-flow) covers experiment design and common pitfalls.
---
# User segmentation for mobile apps: segments that change what users see
Source: https://www.setgreet.com/blog/mobile-user-segmentation
Published: 2026-07-29
> Mobile app user segmentation only pays off when segments change what users see. The five segments that matter for a mobile app, the data each one needs, and the rule that keeps them useful: every segment gets an experience consequence.
The segments that matter for a mobile app are the ones that change what a user sees: new vs returning, free vs trial vs paid, activated vs not, feature-discovered vs not, and lapsed. A segment used only as a dashboard filter is trivia. A segment used as a targeting rule decides which onboarding flow runs, which offer appears, and which message gets suppressed. That is the operating rule for this whole topic: every segment you build must have at least one experience consequence, or it is not worth maintaining.
## Why dashboard filters are not segmentation
Every analytics tool lets you slice a chart by user properties. That is filtering, and it is useful for diagnosis: it tells you that trial users churn more, or that Android users skip a setup step. But a filter never changes anyone's experience. The user who was about to churn sees exactly the same app whether or not you noticed them in a chart.
A segment becomes powerful the moment it is attached to a consequence. Show this flow to this group. Suppress that message for that group. Change the offer for this group. So apply one test before building anything: name the flow shown, the message suppressed, or the offer changed. If you cannot name a consequence, you do not need the segment.
## Which segments actually matter for a mobile app?
These five cover most experience decisions a mobile app has to make. Each is defined by data you either already have or can start collecting this week.
### New vs returning
The most basic split, and still the most commonly botched. New users need a full [onboarding flow](https://www.setgreet.com/use-cases/onboarding): a permission ask with context, a setup step, a fast first win. Returning users need none of that, and showing it again teaches people to skip everything you present. Data needed: a first-seen timestamp or session count, both simple user attributes. Consequence: onboarding targets new users only, and returning users see a what's new screen after a major release, or nothing at all.
### Free vs trial vs paid
Subscription state should drive more targeting than any other attribute. Paid users should never see an [upgrade prompt](https://www.setgreet.com/use-cases/upgrade-prompts): it is noise at best and insulting at worst. Trial users are racing a clock. RevenueCat's [State of Subscription Apps 2026](https://www.revenuecat.com/state-of-subscription-apps), based on over 115,000 apps, found that 55% of 3-day trial cancellations happen on Day 0, so trial targeting should push users to value immediately, not on day two. Data needed: a plan attribute synced from your billing system. Consequence: upgrade prompts target free users only, trial users get a first-day path to the core feature, and paid users get quiet.
### Activated vs not
Activation is the moment a user first gets real value: the first workout logged, the first invoice sent, the first project shared. Users who have not reached it are one distraction away from deleting the app. Data needed: one activation event, tracked at the moment it happens. Consequence: non-activated users get a flow that removes whatever stands between them and that moment. Activated users never see it.
### Feature-discovered vs not
Feature announcements usually broadcast to everyone, which means people already using the feature get interrupted and people who ignored it once ignore it again. Data needed: a usage event for each feature you care about. Consequence: the announcement targets only users who have never fired that event, and it stops appearing for a user the moment they do.
### Lapsed
A user who has not opened your app in two weeks is a different audience from one who opened it yesterday, and what counts as lapsed depends on your category's natural usage rhythm. Our [retention benchmarks post](https://www.setgreet.com/blog/mobile-app-retention-benchmarks) covers how to pick that threshold. Data needed: a last-active timestamp updated each session. Consequence: when a lapsed user returns, they get a welcome-back flow or a win-back offer instead of a home screen that assumes daily context.
## What data does each segment need?
Every segment above reduces to one of two data shapes. User attributes are facts about who the user is: plan, signup date, role, goal. In the Setgreet SDK you set them with identifyUser, which merges new attributes into what is already stored, so you can add facts over time without resending everything. Events are facts about what the user did: workout_completed, invoice_sent, feature_opened, sent with trackEvent. New vs returning and free vs trial vs paid are attribute segments. Activated, feature-discovered, and lapsed are behavioral, built on events.
Two catches. First, anonymous users cannot carry custom attributes, so call identifyUser as early as your auth flow allows. Second, some attributes live in no system you own: the user's goal, their role, why they downloaded the app. Ask for those in the app. A well-placed [in-app survey question](https://www.setgreet.com/blog/in-app-survey-questions) with the answer saved back as an attribute turns a one-time response into a permanent targeting input. In Setgreet, Save to Attribute on a Branch node does exactly that: the response becomes a user attribute you can use in segments, triggers, and variable bindings from then on.
## How do you turn a segment into a targeting rule?
This is where the operating rule becomes mechanical instead of aspirational. In Setgreet, [segments](https://www.setgreet.com/docs/audience) are reusable, app-scoped groups built in the condition builder from user attributes and event conditions. They evaluate dynamically: a user who upgrades leaves the free segment on their own, with no list to maintain. You reference a segment directly in a flow trigger and combine it with other conditions or segments using AND/OR grouping, so "lapsed AND free" is one rule, not a spreadsheet export.
When several flows match the same user, the highest-priority flow wins, so overlaps resolve predictably instead of stacking three prompts on one session. Inside the flow, [variable bindings](https://www.setgreet.com/docs/personalization) reuse the same attributes to personalize copy, so the segment that decides who sees a screen also shapes what the screen says. And because flows publish to your live app without an App Store release, changing who sees what is an edit, not a release cycle.
## Frequently asked questions
### How many segments does a mobile app actually need?
Fewer than you think. The five here, new vs returning, free vs trial vs paid, activated, feature-discovered, and lapsed, cover most experience decisions. The constraint is not tooling, it is consequences: every segment needs at least one flow, message, or offer wired to it, and each of those needs upkeep. Start with lifecycle and monetization segments, and add feature-level ones only when a specific announcement or prompt needs an audience.
### What is the difference between a segment and a trigger condition?
A trigger condition lives on one flow. A segment is a named, reusable group you can reference from many flows. If a rule appears once, write it as a trigger condition and move on. The moment two flows need the same audience, promote it to a segment so the definition lives in one place, and every flow referencing it follows automatically when the definition changes.
### Can you segment anonymous users?
Partially. Events are tracked against an anonymous ID before sign-in, so event-triggered flows can reach users who have not identified yet. Custom attributes require an identified user, so attribute segments like free vs paid only apply after identification. The practical rule: identify users as early as possible, and lean on event-based segments for anything that must work before sign-up.
---
# In-app announcements users do not instantly dismiss
Source: https://www.setgreet.com/blog/in-app-announcement-examples
Published: 2026-07-29
> Most in-app announcements are dismissed on reflex because they are untargeted noise. The fix is discipline: announce to the segment the feature serves, at a relevant moment, with one action, then measure follow-through and retire on schedule.
In-app announcements get dismissed instantly when they are broadcast noise: one untargeted modal, shown to everyone, about a feature most viewers will never use. The fix is discipline, not louder design. Announce only to the segment the feature serves. Show the announcement at a moment when it is relevant, never on first launch. Give it exactly one action. Cap how often any single user sees announcements, measure whether the audience follows through, and retire every announcement on a schedule. This post covers the formats, the targeting rules, and the frequency hygiene that make announcements worth reading.
## Why most announcements get dismissed on reflex
The typical feature announcement is a modal shown to the entire user base about something most of that base will never touch. A free user sees a spotlight for an enterprise admin console. A runner opens the app to start a workout and gets interrupted by news about a feature she adopted three weeks ago. Every irrelevant interruption teaches users that your announcements can be closed without reading. By the time you ship the one announcement that genuinely matters to a given user, the dismiss reflex is already trained.
The dismissal itself is not the real cost. The real cost is that you spent attention and got nothing back, and every later prompt, including a [notification permission request](https://www.setgreet.com/blog/push-notification-permission-best-practices) or a review ask, inherits that earned skepticism.
## Full-screen flow, banner, or badge: which format fits?
Match the format to the size of the change and the share of your users it affects. There are three workable tiers.
- **Full-screen announcement flow**: for changes that alter how a segment of users works day to day. A short multi-screen [announcement flow](https://www.setgreet.com/use-cases/announcements) can show the feature in context, explain what changed, and end with one button that takes the user straight there. Reserve this tier for the handful of announcements per year that justify a takeover.
- **Banner or inline card**: for useful but optional features. It sits inside the UI without blocking anything, so users act on their own schedule. This is the sensible default for most launches.
- **Badge or dot**: for changes discoverable inside an existing menu. Lowest cost and lowest reach. Works well as a lingering secondary cue after a banner retires.
If you reach for a full-screen takeover more than a few times a quarter, the problem is usually your targeting, not your format.
## Who should see the announcement?
The highest-leverage rule in this whole discipline: announce to the segment the feature serves, and to nobody else. A new export scheduler goes to users who have exported a report. A team permissions feature goes to workspace admins on a paid plan. Everyone else should never know the announcement existed. Just as important, exclude people who already found the feature on their own; congratulating an existing user on a launch reads as noise too.
This is where Setgreet earns its place in the workflow. Announcement flows are targeted with the same condition builder as onboarding: user attributes, event properties, and reusable [segments](https://www.setgreet.com/docs/audience), combined with AND/OR logic. You can require that plan equals pro, trigger off the related event with property filters, and exclude users whose attributes show they have already adopted the feature. Each announcement's audience becomes exactly the group with a reason to care.
## When should it appear, and how often?
Relevance has a time dimension. The best moment to announce the export scheduler is right after someone finishes an export, because the announcement answers a problem the user just had. Trigger announcements off related events and screens rather than app open, and the same message reads as help instead of marketing.
Then apply frequency hygiene. Never announce on first launch: to a new user everything is new, and the announcement only competes with your [onboarding](https://www.setgreet.com/use-cases/onboarding). Show at most one announcement per session. Put a cool-down of several days between announcements so back-to-back launches do not stack. Cap total impressions per user; if someone has dismissed an announcement twice, a third impression will not convert them, it will annoy them. And when several announcements are eligible at once, show only the highest-priority one.
## Measure reach and follow-through, then retire it
An announcement has exactly two numbers that matter. Reach: what share of the target segment actually saw it. Follow-through: what share of those viewers took the one action and then used the feature. A high dismissal rate with low follow-through means the targeting or the moment is wrong; low reach means the trigger is too narrow or the audience was smaller than you assumed. In Setgreet, each announcement flow reports what happened in [flow analytics](https://www.setgreet.com/docs/analytics), including how users dismissed it, so you judge the announcement on adoption rather than on gut feel.
Retirement is the step almost everyone skips. Decide the end date when you publish, not later: an announcement still running two months after launch is noise by definition. This is much easier when publishing is not coupled to app releases. [Apple reviews 90% of submissions in under 24 hours](https://developer.apple.com/distribute/app-review/), but a release still means building, submitting, and waiting for users to update. Because Setgreet [publishes flow changes to live apps without an App Store release](https://www.setgreet.com/blog/update-app-without-app-store-review), an announcement can go live the day the feature ships and disappear the day it stops earning its impressions.
## Frequently asked questions
### What is the difference between an in-app announcement and a push notification?
A push notification interrupts someone outside your app; an in-app announcement reaches someone who already chose to open it. That makes in-app the right channel for feature news: intent is already present, and you can target by in-app behavior at the exact moment of relevance. Reserve push for genuinely time-sensitive messages that justify an interruption, and let feature announcements wait for the next session.
### How long should a feature announcement stay live?
Set the retirement date at publish time and treat extensions as the exception. A practical rule: run a full-screen announcement until reach of the target segment plateaus in your analytics, which for most apps happens within a few weeks, then retire it and let a badge or a changelog entry carry the long tail. An announcement without an end date slowly turns into the broadcast noise you were trying to avoid.
### Should new users ever see feature announcements?
Not in their first sessions. A new user has no baseline, so a feature announcement is indistinguishable from the rest of the product and only competes with onboarding for attention. Gate announcements on account age or a lifecycle attribute so they start appearing once a user has an established routine that the feature actually changes. Anything truly essential for new users belongs in the onboarding flow itself, not in an announcement.
---
# You vibe-coded an app. Now give it onboarding that converts
Source: https://www.setgreet.com/blog/add-onboarding-to-ai-built-app
Published: 2026-07-29
> Thousands of builders are shipping vibe coded apps that work but don't convert. Here is the minimum first session an AI-built app needs: value framing, a few personalization questions, a permission primer, and a paywall placed on purpose.
Your vibe-coded app needs four things before its first session can convert: a short set of screens that frame the value, two or three questions that personalize the experience, a primer before any permission prompt, and a paywall placed deliberately rather than wherever the template put it. None of that is hard to design. The trap is hand-coding it, because onboarding is the part of your app that needs the most iteration, and native release cycles make iteration slow. Here is the minimum first session, and the fastest honest way to ship it.
## Why working apps still lose their first users
Vibe coding got you a real app. Cursor, Claude Code, or an Expo starter turned an idea into something on the store in days. But AI coding tools optimize for making features work, not for making strangers care. So most vibe-coded apps open cold: the user lands on the home screen with no context, gets hit with an OS notification prompt within seconds, and never hears why the paid tier exists. Then they leave, and you have no idea where or why, because nothing in the first session is instrumented. The product is not the problem. The first sixty seconds are. And unlike your core features, this part was probably never in your prompt.
## What the first session needs at minimum
You do not need a long guided tour. You need four jobs done, in roughly this order. This is the skeleton behind almost every well-built [onboarding flow](https://www.setgreet.com/use-cases/onboarding) in consumer apps:
1. **Value framing.** One to three screens that state the outcome your app delivers, in the user's words, before any real UI appears. Not features. The result.
2. **Two or three personalization questions.** Ask what the user is here for, then visibly use the answer. Even one question makes the product feel intentional, and the responses become audience data you can target with later.
3. **A permission primer.** Never let the OS dialog be the first mention of notifications or tracking. Show one screen explaining what the user gets, then trigger the prompt. Declines on the native dialog are expensive because you rarely get a second chance, so [prime before you ask](https://www.setgreet.com/use-cases/permissions).
4. **A paywall with deliberate placement.** Decide where payment enters the story. After value framing and before the home screen is the most common pattern for subscription apps, but the point is that it is a decision, not a default.
## Where should the paywall go?
Placement matters more than most builders expect. RevenueCat's [State of Subscription Apps 2026](https://www.revenuecat.com/state-of-subscription-apps), based on over 115,000 apps, found that hard paywalls convert about 5x better than freemium: a 10.7% median versus 2.1%. That does not mean you should wall off your app tomorrow. It means the gap between paywall strategies is large enough to swamp most other optimizations, so shipping whatever your starter template happened to do is leaving real money on the table. Start with a paywall at the end of onboarding, after the value framing has done its work, and treat everything about it as testable: position, framing, trial or no trial. For a deeper breakdown of the options, see [where the paywall belongs in onboarding](https://www.setgreet.com/blog/onboarding-paywall-placement).
## Why hand-coding onboarding is a solo-dev trap
The whole advantage of vibe coding is iteration speed. Hand-coded onboarding takes that away. Every copy tweak, reordered screen, or paywall experiment becomes a native build, a store submission, and a wait for users to actually update. Apple now reviews [90% of submissions in under 24 hours](https://developer.apple.com/distribute/app-review/), but review was never the real bottleneck: rollout and update adoption are. A one-line headline change can take a week to reach most of your users, which means you will run one experiment a month at best, and in practice you will run zero. Onboarding is precisely the surface that needs dozens of small, data-judged iterations. Building it as static native screens locks your most change-hungry code behind your slowest release path.
## The fastest honest path: generate, publish, measure
This is the problem Setgreet is built for. You install a native SDK once, with support for [iOS, Android, React Native, and Flutter](https://www.setgreet.com/docs/sdk); screens render as fully native UI, SwiftUI on iOS and Jetpack Compose on Android, with no web views. Then [Build with AI](https://www.setgreet.com/docs/build-with-ai) drafts the flow: describe what you want, answer a short set of intake questions, and the assistant, which can pull context from your App Store listing, proposes a screen plan before generating anything. Only after you confirm does it produce a draft with real, editable native screens. You stay in control the whole way: nothing goes live until you publish, and every screen is editable in the visual flow builder. From then on, onboarding changes publish to your live app without an App Store release, so your iteration loop finally matches the speed you built the app with. The free tier covers the full loop: build flows, publish them, and measure how users move through them. And [pricing](https://www.setgreet.com/pricing) is flat by monthly active users, never a percentage of your revenue.
## Frequently asked questions
### How many onboarding screens should a vibe-coded app have?
Enough to do the four jobs above and no more. In practice the right length varies a lot by category: health, fitness, and finance apps typically run much longer onboardings than utilities or entertainment apps, because personalization questions carry more of the value. Do not copy a screen count from another app. Decide which questions genuinely change the user's first session, cover value framing and permission priming, and cut everything else. For how screen counts differ across categories, see [how many onboarding screens you actually need](https://www.setgreet.com/blog/how-many-onboarding-screens).
### Do I need a paywall from day one?
If you ever plan to charge, yes. A paywall in the first session is not just revenue; it is the cleanest signal you can get about whether strangers value what you built. Even a soft paywall with a dismiss option gives you a conversion baseline, and you cannot improve a number you never measured. Adding one later is also harder than it sounds, because your early users will have anchored on free.
### Can I update onboarding without an App Store release?
Yes, if the screens are delivered remotely instead of compiled into your binary. With Setgreet, the SDK ships in your app once; after that, flows are created, edited, and published from the dashboard, and changes appear in the live app without a new store submission. The initial SDK install is the only change that requires a release, and drafts never reach users until you explicitly publish them.
---
# Your App Doesn't Need Onboarding
Source: https://www.setgreet.com/blog/your-app-doesn-t-need-onboarding
Published: 2025-10-02
> The real question isn't whether your app should have onboarding. It's whether you're building the kind of product that earns the right to skip it.
Walk into any product meeting and mention onboarding and you'll start a fight. Half the room will cite Instagram's seamless "no tutorial needed" experience. The other half will point to Notion's complex workspace setup that requires guidance. Both sides are convinced they're right.
Here's the truth nobody wants to admit: **they both are.**
The real question isn't whether your app should have onboarding. It's whether you're building the kind of product that earns the right to skip it or whether you're in the dangerous middle ground where bad onboarding is worse than no onboarding at all.
## The Apps That Get Away With No Onboarding
Let's start with the obvious winners, apps so intuitive that explaining them would actually be insulting:
**Single-purpose utilities** like flashlight apps, tip calculators or simple timers. If your value proposition fits in five words and your interface has three buttons, congratulations. You don't need onboarding. You need good visual design.
**Familiar patterns** like Instagram or TikTok lean heavily on gestures and behaviors users already know from other social apps. Swipe, tap, hold… these are the lingua franca of mobile. When you're working within established conventions, onboarding becomes redundant.
**Instant gratification apps** that deliver value in under 10 seconds (Shazam for example) succeed on the dopamine hit alone. The reward is so immediate that users will tolerate a bit of confusion to get there.
If you're in one of these categories, skip the carousel. Your app already speaks the user's language.
## The Apps That Die Without Onboarding
But here's where it gets interesting. Some apps are complex by necessity and pretending otherwise is design arrogance.
**Multi-feature platforms** like productivity tools, finance apps or marketplaces have legitimate complexity. Users need to understand account setup, privacy settings, payment flows and feature hierarchies. Throwing them into the deep end doesn't make you "intuitive", it makes you negligent.
**Behavioral change apps** in health, finance or education are asking users to form new habits. These apps need to establish why someone should invest time before showing how to use features. Without context, users lack motivation to push through the learning curve.
**Novel interaction patterns** like gesture-based navigation, AR features or unconventional UI paradigms require demonstration. If you've invented a new way to interact with content, you can't assume users will accidentally discover it.
**Regulated or sensitive apps** dealing with healthcare, finance or data privacy must communicate crucial information upfront. This isn't optional: it's compliance, safety and trust-building.
The brutal reality? **Most apps fall into these categories.** And that's why most apps need onboarding.
## The Dangerous Middle Ground
Here's where most product teams fail: they know their app is complex enough to need some explanation, but they're terrified of friction. So they compromise and create the worst of both worlds.
**Generic carousel slides** that explain features nobody understands yet. ("Collaborate with your team!" What team? I just downloaded this.)
**Skippable tutorials** that users skip, then get confused and uninstall. You've annoyed them and left them helpless.
**All-at-once feature dumps** that overwhelm users with information they won't retain. The human brain can hold about 3-4 new concepts at a time. Your 8-screen tutorial just wasted everyone's time.
This middle ground is where onboarding gets its bad reputation. And frankly, it deserves it.
## What Actually Works: The Hybrid Approach
The best apps don't choose between "no onboarding" and "tutorial hell." They use **contextual, progressive onboarding** that meets users where they are:
**Smart first-run experiences** that explain only what's needed to get started: typically account creation, permissions and the core value proposition. Three screens max.
**Just-in-time tooltips** that appear when users encounter a feature for the first time. Not before. Not after. Exactly when needed.
**Empty state coaching** that shows users what their experience will look like once they add content, with clear CTAs to get started.
**Progressive feature discovery** that introduces advanced capabilities only after users have mastered the basics. You can't teach calculus before arithmetic.
**Personalized paths** that adapt based on user type, use case or stated goals. A team admin sees different guidance than an individual user.
## Does Your App Need Onboarding?
Ask yourself these questions:
**Can a user complete your core action in under 30 seconds without guidance?** If yes, skip onboarding. If no, you need it.
**Are you asking users to connect accounts, invite contacts or configure settings before experiencing value?** That's setup and it requires hand-holding.
**Do users need to understand a concept, not just an interface?** If your app requires mental model shifts (like understanding blockchain or how task dependencies work), education is mandatory.
**What happens if a user taps the wrong thing?** If the answer is "catastrophic data loss" or "confusing state they can't escape," you need guardrails and explanations.
**Are you launching to a cold audience or an existing user base?** New markets need more context than users who already understand your category.
## The Uncomfortable Truth
Here's what nobody wants to hear: **if your app is genuinely complex, onboarding isn't a nice-to-have; it's the difference between retention and deletion.**
The narrative that "good design needs no explanation" is Silicon Valley mythology. It works for photo-sharing and music streaming. It fails spectacularly for everything else.
Your fintech app managing people's retirement savings? It needs onboarding. Your project management tool coordinating distributed teams? It needs onboarding. Your healthcare app tracking medications? It absolutely needs onboarding.
Skipping it doesn't make you minimalist. It makes you irresponsible.
## Here's the Reality
The apps that succeed without onboarding are the exception, not the rule. They've earned that privilege through obsessive simplification, familiar patterns and single-minded focus.
For everyone else, which is most of us, the question isn't "Should we have onboarding?" It's "How do we build onboarding that's so good, users don't realize they're being onboarded?"
Because the real goal isn't to eliminate onboarding. It's to eliminate confusion. And sometimes, the fastest path to clarity is a well-crafted welcome.
*Great onboarding feels invisible. Bad onboarding feels like homework. No onboarding, when you need it, feels like abandonment.*
---
# The 75% Drop-Off Crisis
Source: https://www.setgreet.com/blog/the-75-drop-off-crisis-why-your-mobile-app-is-bleeding-users-at-the-worst-possible-moment
Published: 2025-09-25
> 75% of potential users abandon apps when required to sign up before experiencing any value. Most companies don't realize this is happening until it's too late.
*And what industry leaders are doing about it…*
Here's a stat that should make every product manager lose sleep: "75% of potential users abandon apps when they're required to sign up before experiencing any value." And that's just the beginning of the hemorrhage.
But here's what's even more troubling, most companies don't realize this is happening until it's too late.
## The Hidden Hemorrhage
Picture this scenario: A mobile app gets featured, downloads surge, store ratings look solid, but something doesn't add up. The gap between "app installs" and "active users" tells a devastating story that most product teams recognize all too well.
The industry data paints a stark picture:
- **Day 1**: Only 22-24% of users who download an app actually return the next day
- **Day 7**: This drops to just 8-11% across most categories
- **Day 30**: A mere 3-6% of users are still active after a month
That's a 94-97% user loss rate within the first month and it gets worse, research shows that "77% of users abandon an app within the first 3 days of installation."
Sound familiar?
## Where Users Actually Drop Off
Recent industry research reveals the most critical friction points in a typical onboarding funnel:
**Screen 1: Welcome/Tutorial (15% drop-off)** Users want to experience value immediately, not sit through lengthy explanations of features they haven't tried yet.
**Screen 2: Account Creation/Sign-up Wall (60% drop-off of remaining users)** The killer. This is where the massive hemorrhage happens. Users are essentially saying "prove you're worth my email address first."
**Screen 3: Initial Setup/Preferences (25% drop-off of remaining users)** Even users who create accounts often bail when faced with complex setup processes or overwhelming customization options.
**Screen 4: First Core Action (20% drop-off of remaining users)** The final test. Can users quickly accomplish what they downloaded your app to do?
*Note: These are sequential drop-offs - each percentage represents users lost from the remaining pool at that step.*
## The Psychology Behind the Drop
Why do users abandon apps so readily? It comes down to three factors:
**Cognitive Load**: Every additional field, button, or decision increases mental effort. Users are already skeptical about new apps, don't make them work harder.
**Trust Deficit**: New users have zero emotional investment in your product. They're looking for reasons to leave, not stay.
**Value Uncertainty**: Users haven't experienced your app's benefit yet, so every friction point feels disproportionately expensive.
## What Industry Leaders Are Doing Differently
The companies winning the onboarding battle are taking a radically different approach:
**Progressive Disclosure**: Instead of front-loading all requirements, they reveal information requests gradually, only when needed for specific features.
**Value-First Onboarding**: Users experience core functionality before being asked for personal information. Think of how Spotify lets you browse and play music before requiring an account.
**Personalized Onboarding Flows:** Companies are leveraging platforms like Setgreet to create dynamic onboarding experiences that adapt to user behavior in real time, even for anonymous users.
**Friction Analysis**: Regular heatmap and user session analysis to identify exactly where users hesitate or abandon flows.
## Three Immediate Actions You Can Take
1. **Audit Your First Five Screens**: Map out every tap, swipe and input field. Ask yourself: "Is this absolutely essential for the user to experience our core value?"
2. **Implement Guest Mode**: Let users explore key features without commitment. Data shows users who experience value first are 3x more likely to complete registration later.
3. **A/B Test Your Ask Sequence**: Try reducing form fields by 50%. Test social login options. Experiment with delayed permission requests.
## The Compound Effect
Here's what's fascinating: reducing onboarding drop-off by just 10% can increase your active user base by 30-50% within six months. Why? Because better onboarding doesn't just help individual users, it improves your app store rankings, reduces customer acquisition costs and increases word-of-mouth referrals.
## The Bottom Line
Your app's success isn't determined by how many people download it, it's determined by how many people successfully complete their first meaningful action within it.
Every screen in your onboarding flow should pass the "pizza test": If someone was distracted by a pizza delivery arriving, could they easily resume and complete this step? If not, you've found your next optimization priority.
**Resources:** Statistics sourced from AppsFlyer, Statista, Business of Apps, VWO and UXCam mobile analytics reports (2024-2025).
---
# What the Numbers Actually Say About Mobile App Onboarding
Source: https://www.setgreet.com/blog/what-the-numbers-actually-say-about-mobile-app-onboarding-(and-what-to-track)
Published: 2025-09-17
> Good onboarding can meaningfully increase retention and activation, while poor or lengthy onboarding causes high early drop-off. Here are the benchmarks.
Onboarding represents a critical intersection between design principles and measurable business outcomes. Good onboarding can meaningfully increase retention and activation, while poor or lengthy onboarding causes high early drop-off.
## Key Metrics from Research
The data paints a clear picture of what's at stake:
- **Retention improvements:** Effective onboarding boosts retention by approximately 50% in certain scenarios
- **Abandonment rates:** Between 21–72% of users drop off during onboarding when friction is high
- **Standard retention benchmarks:** Approximately 26% day-1, 13% day-7, and 7% day-30 retention across typical apps
- **Campaign performance:** Apps running onboarding campaigns saw 20% next-day returns versus 16% baseline
- **Activation gains:** Case studies demonstrate activation increases up to 75% within 10 days following optimization
## Strategic Implications
Even small retention improvements are highly profitable for SaaS and apps. Personalized onboarding reportedly increases conversions by up to 200% in specific experiments.
The math is simple: if you spend $5 to acquire a user and your Day-30 retention is 7%, your effective cost per retained user is over $70. Improving retention to 14% cuts that cost in half — and the compounding effect on LTV is even more dramatic.
## Recommended Metrics to Track
If you're serious about optimizing your onboarding, these are the metrics that matter:
- Onboarding start and completion rates
- Step-by-step drop-off analysis
- Time to activation measurement
- Day 1/7 retention by cohort
- Conversion and monetization tracking
- Permissions acceptance rates
## Testing Recommendations
Start with low-cost A/B tests that can yield significant results:
- Removing unnecessary steps from the flow
- Deferring permission requests until contextually relevant
- Comparing tutorial formats (video vs. interactive vs. none)
- Implementing segmented flows based on user intent
- Testing the impact of social proof elements at key friction points
The key insight is that onboarding optimization is not a one-time project. It's an ongoing practice that compounds over time, much like product iteration itself.
---
# The Empty Restaurant Test
Source: https://www.setgreet.com/blog/the-empty-restaurant-test-a-framework-for-digital-social-proof
Published: 2025-09-10
> Walk into an empty restaurant on Friday night and your instinct says leave. Many founders build excellent products that feel empty to new users. Here's the framework to fix it.
Walk into an empty restaurant on Friday night. Your instinct doesn't say "great, no wait!" It says "something's wrong here." You leave. This isn't rational. The food might be excellent. But empty signals risk, not exclusivity.
Many founders build excellent products that nonetheless feel empty to new users whose skepticism naturally heightens in unfamiliar digital environments.
## The Core Problem
Most founders misunderstand social proof deployment. Rather than strategic placement, they sprinkle it randomly, hoping something sticks. The restaurant owner analogy continues: frantically shouting about great reviews from across an empty room simply doesn't persuade.
Building trust requires sequencing, not scattered tactics.
## The Social Proof Ladder
The solution involves three sequential rungs:
### Rung 1: Safety (Proof of the Crowd)
New users subconsciously ask: "Am I the only one making this bet?" The answer requires broad, momentum-based evidence:
- "Join 1,500+ teams who signed up this month!"
- "45,000+ projects launched on our platform."
The objective centers on neutralizing skepticism through big, simple numbers signaling legitimacy and activity — positioning the space as validated rather than desolate.
### Rung 2: Validation (Proof of the Expert)
Once safety emerges, the emotional question shifts to: "Am I smart for being here?" This phase showcases expert endorsement:
- Industry leader testimonials
- Company logos ("As seen in Forbes")
The key principle here is the **Principle of Proximity**: social proof effectiveness decays exponentially with distance from the moment of doubt. A footer logo whispers; contextual validation at friction points shouts louder.
### Rung 3: Belonging (Proof of the User)
The final need transcends safety and validation: "Do I belong here?" This tier creates emotional bonds through peer activity:
- Activity feeds showing peers completing steps
- Purchase notifications from similar roles
- User-generated content inspiring action
This represents sophisticated social proof, transforming cold tools into living ecosystems fostering community and FOMO.
## Implementation Strategy
The complete playbook sequences as follows:
1. Establish **SAFETY** first through crowd proof
2. Deliver **VALIDATION** via expert proof, timed strategically
3. Foster **BELONGING** through user proof
## The Challenge
Audit your onboarding against this framework. Are you trying to create belonging before establishing safety? Are you scattering social proof randomly instead of sequencing it strategically?
The empty restaurant doesn't need better food. It needs visible diners. Your product doesn't need more features. It needs visible users.
---
# Pricing Poker
Source: https://www.setgreet.com/blog/pricing-poker-blindly-find-your-best-price-with-a-24-hour-test
Published: 2025-08-27
> Pricing is not a math problem with a single right answer. It is a strategic game. This guide gives you a framework to read the board and a one day test to make your next move.
You are staring at the pricing page draft, completely frozen. Is this number too high? Is it too low? The fear of getting it wrong is paralyzing. Pricing is not a math problem with a single right answer. It is a strategic game. This guide gives you a framework to read the board and a one day test to make your next move with confidence.
## Step 1: Your Hand and Stack
In poker, your strategy depends on the cards you hold and the chips you have. This is your product strength and your available resources.
A truly unique product is a royal flush. It lets you bet aggressively and set premium prices. A strong product might support a price that delivers a fast customer acquisition cost (CAC) payback period, perhaps under 6 months.
A common utility app is a weaker hand. It requires caution, smarter bets, and a focus on a specific market niche. Your funding is your chip stack. A large stack allows you to play the long game. A small stack means every bet must count, demanding a pricing strategy that delivers revenue now.
## Step 2: Read the Table
Next, you must read the table. This means analyzing the market context by watching signals from competitors and users.
Competitor pricing is a public bet. A low price can be a bluff from a weak player or an aggressive raise from a well funded one. Look for their tells. High customer churn or reviews complaining about value are signals their price is too high for the hand they hold. Your own users provide the clearest tells.
If your trial to paid conversion rate is below 4 percent, the market is calling your bluff. The price does not match the perceived value. This is a critical signal to reevaluate your bet.
## Step 3: Place Your Bets
Your pricing model is how you place your bet. Each comes with a clear set of trade offs:
### Freemium
- **When to use:** Your product has network effects or can serve as its own marketing engine.
- **Advantage:** Maximizes top of funnel user acquisition.
- **Disadvantage:** Can attract users who will never pay.
- **Risk to watch:** High infrastructure costs that are not offset by a strong conversion rate from free to paid.
### Tiered Pricing
- **When to use:** You serve distinct customer segments with different needs and budgets.
- **Advantage:** Allows you to capture value from the entire market.
- **Disadvantage:** Can create choice paralysis if tiers are not distinct.
- **Risk to watch:** Customers consistently choosing the lowest tier, indicating poor value differentiation.
### Usage Based Pricing
- **When to use:** Your product's value is directly tied to a measurable unit (e.g., API calls, data stored).
- **Advantage:** Aligns cost directly with value for the customer.
- **Disadvantage:** Creates unpredictable revenue for you and unpredictable costs for the customer.
- **Risk to watch:** High value customers churning due to budget uncertainty as their usage grows.
## Step 4: Your Opening Move
Your first bet is not the price itself. It is the user onboarding experience. This is your opening move, and its success is measured by your activation rate, the percentage of users who experience your product's core value.
A clear, compelling onboarding process is the single biggest lever you can pull to improve this metric and, consequently, your conversion rate.
In poker you will understand if the player sitting next to you is a good player in the first round. You analyze and understand your potential rivals in the first minute. That's why it's important to nail your first impression.
## Step 5: Run a 24 Hour Price Test
Stop guessing and start testing. Here is a concrete experiment you can run this week to gather real data.
1. **Choose One Variable:** Do not test everything at once. Pick one thing: the introductory price on your main plan, the names of your tiers, a new decoy tier, or the discount percentage for your annual anchor.
2. **Define Success Metrics:** Decide what a "win" looks like before you start. Key metrics include trial to paid conversion, average revenue per user (ARPU), or day 7 refund rate.
3. **Set a Limited Audience:** Use A/B testing software to show the variant to a segment of new traffic. You can split traffic 50/50 or limit the test to a specific geographic region.
4. **Implement and Ship:** Update the copy on your pricing page and paywall modals. Push the changes live.
5. **Measure and Log:** Let the test run for a defined period, even just 24 to 48 hours for a high traffic site. Log the results meticulously.
6. **Decide:** Based on the data, you have three choices. Raise the bet by rolling the change out to everyone. Hold by continuing the test for more data. Or revert to the original if it performed poorly.
## Key Takeaways
- Your product strength (hand) and resources (stack) determine if your pricing strategy should be aggressive or cautious.
- Analyze user and competitor signals (tells) like churn and conversion rates to understand the market context.
- Choose a pricing model (Freemium, Tiered, Usage Based) that aligns with your product and customer, and be aware of its specific risks.
- The onboarding experience is your most critical opening bet, directly impacting your user activation and conversion rate.
- You can move from guessing to knowing by running a focused, 24 hour price experiment with clear success metrics.
---
# The Elevator Is Moving And You Have Only 15 Seconds
Source: https://www.setgreet.com/blog/the-elevator-is-moving-and-you-have-only-15-seconds
Published: 2025-08-19
> The average product loses 77% of daily users within 72 hours. Companies spend $92 to acquire a customer for every $1 spent converting them.
### Why 77% of new users disappear and how a solid onboarding turns that free fall into loyalty
## The gut-fluttering moment you know too well
The elevator closes. You have fifteen seconds to ignite curiosity before the doors reopen. One glance at a watch tells you the spark is fading. Most apps face similar judgment daily: the average product loses 77 percent of its daily users within seventy-two hours of install.
## When first impressions misfire, money leaks
Brands lavish ad budgets on downloads but invest a sliver in conversion. Research indicates companies spend $92 to acquire a customer for every $1 spent converting them. That mismatch pushes Customer Acquisition Cost skyward while Lifetime Value slides, stretching payback periods until growth stalls.
## The watch-glance problem
In an elevator you can adjust your pitch the instant doubt appears. Inside an app you watch dashboard footage days later. The chart tells you that seventy percent bailed on screen three but never reveals the moment of hesitation. Product teams iterate blindly and pay tuition with churned users.
## A Smarter Way to Onboard
Instead of treating all users the same, you can capture the context of where they came from and match that to a tailored experience inside your app.
Rather than guessing, you can test different approaches with precision. Would a minimalist, quick-draw flow work best for users arriving from a TikTok ad? Or does a more detailed, feature-rich flow resonate with those who found you through a technical blog post? The goal is to turn onboarding from a static monologue into a dynamic, responsive conversation.
## How to do it in three beats
1. **Tag Your Links:** Marketing tags every inbound link with the specific promise it makes to the user.
2. **Build Your Scenarios:** Pair that promise with a customized onboarding flow you've designed.
3. **Analyze and iterate:** Use real-time data to see which paths keep users engaged, double down on what works, and fix what doesn't, ideally without waiting for another app store release.
## Power and responsibility
- **Risk:** Over-personalization can fracture your brand voice. **Mitigation:** Precision storytelling lifts metrics, yet find the middle ground.
- **Risk:** A viral traffic surge creates a budget spike. **Mitigation:** Use flexible guardrails to keep budgets and performance stable.
- **Risk:** A sloppy setup creates integration conflicts. **Mitigation:** Invest time up front to align your team and process for smooth deployment.
## Quick recap
- Apps face the same fifteen second scrutiny as elevator pitches.
- Weak onboarding drains ad spend: 77% attrition in three days; a 92-to-1 spend ratio on acquisition vs. conversion.
- **Results:** Faster payback, higher LTV, happier users.
- **Method:** Precise user-path design, rapid iteration, and a native, professional user experience.
Focus on building greatness.
---
# The 30-Second Make-or-Break
Source: https://www.setgreet.com/blog/why-your-app-s-first-impression-is-everything
Published: 2025-08-12
> Users form immediate judgments upon opening an app. Every millisecond of lag screams amateur hour. Here's how the discovery method shapes first impressions.
Think about the last time you walked into a restaurant you'd never been to before. Within seconds, you had already formed an opinion. The lighting, the noise level, whether the host greeted you warmly or barely looked up from their phone — all of these micro-signals told you everything you needed to know before you even looked at the menu.
Your app works exactly the same way.
## The Restaurant Parallel: More Than Just a Metaphor
How users discover your app creates a trust hierarchy that directly impacts their tolerance for friction:
1. **Friend recommendation** — highest trust, maximum forgiveness
2. **Influencer endorsement** — high trust, slight skepticism
3. **Active search** — moderate expectations, comparison-oriented
4. **Random discovery** — zero expectations, harsh judgment
This hierarchy directly parallels app discovery dynamics. A user who finds you through a trusted friend will forgive a slightly clunky onboarding flow. A user who stumbles across your ad on Instagram will not.
## The Brutal Reality: You Have 30 Seconds
Users evaluate apps based on four factors:
- **Visual first impressions** — design currency. Does this look modern, trustworthy, professional?
- **Speed and responsiveness** — technical polish. Every millisecond of lag screams amateur hour.
- **Onboarding flow** — balance of guidance. Too much feels patronizing, too little feels abandoning.
- **Overall "vibe"** — tone and trustworthiness. Does this feel like it was made for me?
## The Onboarding Paradox: Where Most Apps Fail
This section identifies the core onboarding failures:
**The 47-screen problem:** Excessive feature explanations that try to showcase everything before the user has context for any of it.
**The figure-it-yourself approach:** No guidance at all, assuming the interface is "intuitive enough." Spoiler: it rarely is.
**The interrogation method:** Asking for too much personal information before delivering any value. Name, email, phone, birthday, preferences — all before the user has seen what the app actually does.
**The generic flow:** Treating every user identically, regardless of how they discovered the app or what they need.
The uncomfortable truth: features are irrelevant if users abandon before discovering them.
## The Adaptation Challenge
Testing and iterating on onboarding is harder than it should be:
- A/B testing complexity and resource intensity
- Native implementation requirements for changes
- User loss during problem identification
- One-size-fits-all implementations missing segmentation needs
## The Solution Space
Specialized onboarding tools offer a way out of this cycle:
- Rapid iteration without app store updates
- Data-driven optimization with real-time analytics
- Native performance quality that matches your app
- User segmentation capabilities for personalized experiences
The trade-offs include service dependency, team learning curves, potential disconnection from core experience, and cost considerations.
## The Strategic Imperative
You don't get a second chance at a first impression. In today's competitive app marketplace, where alternatives are a tap away and user patience is measured in seconds, the quality of your onboarding isn't just a nice-to-have.
Users who have a positive first experience are significantly more likely to become long-term, paying customers. Users who don't will simply move on to the next option in their search results.
## Your Next Move
Treat onboarding as what it is: the most critical touchpoint in your entire user journey. Whether you build it internally or leverage specialized tools, the message is the same — poor onboarding in today's market isn't just a missed opportunity. It's a death sentence for user retention.
Your app has 30 seconds. Make them count.