RORK LABJP
CLOUD — Rork Max compiles native Swift on a fleet of cloud Macs, so you never download Xcode or need to own a MacPLATFORM — Rork Max targets iPhone, iPad, Apple Watch, and Vision Pro, and reaches games, widgets, and Live ActivitiesSHIP — Build in the browser, preview through a streaming simulator, install on device via QR code, and submit to the App Store without leaving RorkSPLIT — Regular Rork generates cross-platform apps with React Native and Expo. Reach for it to ship broadly and fast, and for Max when you need Apple-specific depthCREDIT — The free tier works out to roughly five prompts a week. It helps to budget the cost of trying something separately from the cost of finishing itPRICE — Rork Max sits on the $200/month Max plan, while regular Rork starts free with paid plans from $25/monthCLOUD — Rork Max compiles native Swift on a fleet of cloud Macs, so you never download Xcode or need to own a MacPLATFORM — Rork Max targets iPhone, iPad, Apple Watch, and Vision Pro, and reaches games, widgets, and Live ActivitiesSHIP — Build in the browser, preview through a streaming simulator, install on device via QR code, and submit to the App Store without leaving RorkSPLIT — Regular Rork generates cross-platform apps with React Native and Expo. Reach for it to ship broadly and fast, and for Max when you need Apple-specific depthCREDIT — The free tier works out to roughly five prompts a week. It helps to budget the cost of trying something separately from the cost of finishing itPRICE — Rork Max sits on the $200/month Max plan, while regular Rork starts free with paid plans from $25/month
Articles/Dev Tools
Dev Tools/2026-06-13Intermediate

You Only Get to Ask Once — Implementing a Notification Soft-Ask in Your Rork App to Lift Opt-In

On iOS, once a user denies the notification prompt you can never show it again. In a Rork (Expo) app, instead of firing the system prompt on launch, we add our own soft-ask screen and only request permission once the value has landed. Built with expo-notifications, covering Android 13 POST_NOTIFICATIONS, a recovery path after denial, and opt-in measurement.

Rork525Expo156Push Notifications9expo-notifications7Onboarding8Retention13

Premium Article

I was lining up the notification opt-in rates across the apps I run when one of them stood out for being far too low. It was a wallpaper app built almost identically to the others, yet the share of users who allowed notifications was less than half of the rest.

The cause was easy to find. That one app was firing the notification permission dialog before the first screen even appeared. From the user's side: "I don't even know what this app is yet, and it's already asking to send me notifications."

iOS notification permissions come with a harsh rule that every indie developer hits at least once. The UNUserNotificationCenter dialog can only be shown automatically once in the app's lifetime. The moment a user taps "Don't Allow," calling requestPermissions() from code does nothing — the dialog never appears again. The only path left is asking them to open the Settings app themselves.

So opt-in is decided by when and how you ask. In this article we stop firing the system prompt on launch and slot in our own soft-ask (pre-permission) screen inside a Rork (Expo) app.

Why "you only get to ask once" governs opt-in

On iOS, notification permission has three states: notDetermined (never asked), authorized (granted), and denied. The key point is that the system prompt can only appear while the state is notDetermined. In any other state, requestPermissions() simply returns the current status without showing anything.

notDetermined is a single, once-per-lifetime chance. Spend it carelessly and there is no getting it back.

A soft-ask, by contrast, is our own UI that we can show as many times as we like. If the user declines it, the iOS state stays notDetermined. That is what makes the two-step pattern work: ask in our own dialog first, and fire the system prompt only for the people who agreed. By placing a cushion in front of the system prompt, we reserve that precious single shot for people who are genuinely likely to allow.

In my own apps, the difference between firing the system prompt on launch and asking only after the value had landed came out to more than a 2x gap in the final OS grant rate.

Decide the flow before writing code

Before implementation, lock down the order of decisions. The code is just this order written out.

  1. On launch, read the current permission state. If authorized, do nothing.
  2. If denied, don't show your own dialog; only surface a "you can turn notifications on in Settings" path where it's relevant.
  3. Only while notDetermined, check whether a trigger to show the soft-ask has occurred.
  4. When the trigger fires, show the soft-ask screen.
  5. If the user taps "Turn on" in the soft-ask, only then fire the system prompt (requestPermissions()).
  6. If they tap "Later," leave the state untouched and wait for the next trigger.

Of these six steps, many apps skip 3 and 4 and jump straight to 5 on launch. The whole value of the cushion lives here.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Understand why firing the system prompt on launch permanently burns your one chance on iOS, and implement a two-step soft-ask — your own dialog first, the system prompt only for those who agree — with expo-notifications
Defer the ask until the value has landed (first save, a completed task, the third launch) using a small TypeScript trigger, and design the three events you need to measure opt-in: shown, accepted, OS-granted
Build a recovery path that sends denied users to Settings via Linking.openSettings(), and handle the Android 13 POST_NOTIFICATIONS runtime permission difference, production gotchas included
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-07-17
Shipping Notifications Without Asking First — Provisional Authorization in Rork Apps, and the Expo Snippet That Quietly Undoes It
iOS lets you start delivering notifications with no permission dialog at all, via provisional authorization. The catch: expo-notifications reports granted as false for provisional devices, so the registration snippet in Expo's own docs re-requests permission and fires the very dialog you were avoiding. Here's why granted lies, a hook that models authorization as five states, how to write notifications for quiet delivery, when to ask for the upgrade, and how to keep provisional out of your CTR.
Dev Tools2026-06-16
Keeping Expo Push Tokens from Slipping Through the Cracks in Production
After adding re-engagement push to a Rork-generated Expo app, the delivered count came in well below the active install count. The cause was missed token updates and stale tokens left to pile up. Here is the lifecycle I settled on, with code: registration, refresh, server storage, and pruning.
Dev Tools2026-06-14
Stop Burning Your One Push-Permission Shot on App Launch — Pre-Prompt Priming for Rork Apps
If your Rork (Expo) app fires the OS push-permission dialog at launch, every 'Don't Allow' tap closes that channel forever — iOS won't let you ask again. Here's how a self-built pre-permission screen lifts your opt-in rate, with the Expo code to do it.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →