Quickstart
Integrate Grovs end to end in about 30 minutes.
Create a project, install and initialize the SDK, handle a deep link, and test it with a real link from the dashboard.
Using a coding agent? Build with AI has a prompt per platform that does steps 2 to 4 for you. New to Grovs? Read the Introduction.
Guided setup in the dashboard
Your first project opens a four-step guided setup that follows this page. It saves progress: reopen it from Finish setup under the project switcher.
| Step | What you do | How it's checked |
|---|---|---|
| Link domain | Pick a Grovs subdomain, which is ready right away, or use your own domain (paid plans). | Saved when you continue. |
| Connect your app | For each platform (iOS, Android, Web), register the app, add the SDK and initialize it, then run the app. | Live: see below. |
| Links | Choose where links go on each platform when the app isn't installed, and the share preview. | Done once a fallback is set. |
| Try it | Create your first link, scan its QR code with your phone, and open it. | Done once the link exists. It also shows Opened on your phone, live, once the link opens your app. |
If someone else writes the code, Send to your developer invites them with a link to the connect step.
Is my SDK connected?
On Run your app, the last part of Connect your app, the dashboard shows Waiting for your app to open. It refreshes every few seconds until the SDK reports in, then shows Connected and how long ago.
| Platform | Connected when |
|---|---|
| iOS, Android | A device running the SDK was seen in the last 30 days. The app must start with the SDK configured; opening a link in a browser doesn't count. |
| Web | The Web SDK authenticated from one of your registered domains. |
The check covers production and test, so a debug build with useTestEnvironment: true counts. Once done, the step stays done.
If it keeps waiting:
- Check the API key is the project's key, and the environment flag matches the build.
- Turn on debug logging (
Grovs.setDebug(level: .info)on iOS) and look for an authentication error. - On the web, check the page's origin is registered exactly, including the port on
localhost. See Registering domains. - See SDK configuration issues.
The Try it check uses the newest open of that link: an app open, install, reinstall or reactivation through it on a device running the SDK.
Sign up at app.grovs.io and create a project. The guided setup starts on its own for your first project.
Copy your key from API Key in the dashboard sidebar. Each environment (test, production) has its own key: copy the one you build against.
Then set your redirect rules, domain and social preview. Redirect rules need a fallback URL before they save.
Swift Package Manager
- In Xcode, go to File → Add Package Dependencies…
- Enter the repository URL:
https://github.com/grovs-io/grovs-iOS.git - Choose Up to Next Major Version starting at
3.1.0 - Click Next, then Finish
CocoaPods
Add to your Podfile, then run pod install:
pod 'Grovs', '~> 3.1'Add the dependency to your app-level build.gradle:
dependencies {
implementation("io.grovs:Grovs:3.1.0")
}Add to pubspec.yaml, then run flutter pub get:
dependencies:
grovs_flutter_plugin: ^3.1.1Install the wrapper package:
# Using npm
npm install react-native-grovs-wrapper
# Using yarn
yarn add react-native-grovs-wrapperIn a bare React Native project, add the Grovs Android SDK to android/app/build.gradle at the wrapper's version. The Expo config plugin does this for you:
dependencies {
implementation 'io.grovs:Grovs:3.1.0'
}pod install adds the iOS SDK.
npm install grovs --saveInitialize the SDK in your AppDelegate:
import Grovs
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Grovs.configure(APIKey: "your-api-key", useTestEnvironment: false, delegate: self) { success in
if success {
print("Grovs SDK is ready")
}
}
return true
}Without a SceneDelegate, forward these calls:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
return Grovs.handleAppDelegate(continue: userActivity, restorationHandler: restorationHandler)
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return Grovs.handleAppDelegate(open: url, options: options)
}SceneDelegate apps forward different calls. See the iOS Quick Start for that variant, debug logging and user identity.
Initialize Grovs in your Application class:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Grovs.configure(this, "your-api-key", useTestEnvironment = false)
}
}Forward lifecycle events in your launcher activity:
class MainActivity : AppCompatActivity() {
override fun onStart() {
super.onStart()
Grovs.onStart(this)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Grovs.onNewIntent(intent, this)
}
}Links also need intent filters on the launcher activity. Copy them from the Android Quick Start.
Flutter is configured in native files; there's no configure call in Dart. On Android, add the key and environment inside <application> in AndroidManifest.xml:
<application>
<meta-data
android:name="grovs_api_key"
android:value="YOUR_API_KEY" />
<meta-data
android:name="grovs_use_test_environment"
android:value="true" />
</application>On iOS, add them to Info.plist:
<key>GrovsApiKey</key>
<string>YOUR_API_KEY</string>
<key>GrovsUseTestEnvironment</key>
<true/>Optional keys: a self-hosted base URL (GrovsBaseURL / grovs_base_url), extra clipboard link domains (GrovsClipboardDomains / grovs_clipboard_domains), and starting disabled until consent (GrovsEnabled / grovs_enabled). Set FlutterDeepLinkingEnabled (Info.plist) and flutter_deeplinking_enabled (Android manifest) to false, or Flutter's own deep linking also handles Grovs links. You also need Android intent filters, an iOS URL scheme and Associated Domains. See the Flutter Quick Start.
On Android, initialize in MainApplication:
import io.grovs.Grovs
import com.grovswrapper.GrovsConsent
override fun onCreate() {
super.onCreate()
Grovs.configure(
this, "your-api-key", useTestEnvironment = false, baseURL = null,
autoTrackScreenViews = false, clipboardDomains = null,
enabled = GrovsConsent.isEnabled(this)
)
}On iOS, initialize in AppDelegate.swift:
import Grovs
import react_native_grovs_wrapper
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Grovs.configure(
APIKey: "your-api-key", useTestEnvironment: false,
autoTrackScreenViews: false, clipboardDomains: nil,
enabled: GrovsWrapperSwift.isSDKEnabled(), delegate: nil
)
Grovs.setDebug(level: .info)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}- Pass
nilas the delegate. The wrapper registers itself and forwards links to your JS listener. - Keep
autoTrackScreenViewsoff. The native trackers only see the React Native host screen. - Pass the wrapper's stored consent value as
enabled, sosetSDK(false)survives a cold start.
Both platforms need more native wiring: link forwarding, intent filters, Associated Domains. See the React Native Quick Start. With Expo and a development build, the config plugin automates it (Swift and Kotlin templates only).
Import and configure the SDK:
import Grovs from 'grovs';
await Grovs.configure({
apiKey: 'your-api-key',
testEnvironment: false,
onDeeplink: (payload) => {
// Called when the page was opened from a Grovs link
console.log('Link payload:', payload);
},
});Match useTestEnvironment (testEnvironment on the web) to the key: test key → true, production key → false. A link from the other environment won't resolve its payload.
Register a handler. The SDK passes it the link and its payload whenever a Grovs link opens your app.
Conform to GrovsDelegate:
class YourViewController: UIViewController, GrovsDelegate {
override func viewDidLoad() {
super.viewDidLoad()
Grovs.delegate = self
}
func grovsReceivedPayloadFromDeeplink(link: String?, payload: [String: Any]?, tracking: [String: Any]?) {
// Route the user based on payload data
if let screen = payload?["screen"] as? String {
navigateTo(screen)
}
}
}More options (past payloads, link details) in iOS Deep Linking.
Register a listener in your launcher activity:
Grovs.setOnDeeplinkReceivedListener(this) { deeplinkDetails ->
val link = deeplinkDetails.link
val payload = deeplinkDetails.data
val tracking = deeplinkDetails.tracking
Log.d("Grovs", "Opened from: $link, payload: $payload")
}More options (Kotlin Flow, link details) in Android Deep Linking.
Subscribe to onDeeplinkReceived in a widget that stays mounted, such as your root widget. Links that arrive earlier, including the launch link, are buffered for the first listener:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:grovs_flutter_plugin/grovs.dart';
import 'package:grovs_flutter_plugin/models/grovs_link.dart';
class _AppState extends State<App> {
final _grovs = Grovs();
StreamSubscription<DeeplinkDetails>? _subscription;
@override
void initState() {
super.initState();
_subscription = _grovs.onDeeplinkReceived.listen((details) {
final link = details.link;
final payload = details.data;
debugPrint('Opened from: $link, payload: $payload');
// Route the user based on payload
if (payload?['screen'] == 'product') {
navigateToProduct(payload?['productId']);
}
});
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
}More details in Flutter Deep Linking.
Use the onDeeplinkReceived callback:
import Grovs from 'react-native-grovs-wrapper';
const listener = Grovs.onDeeplinkReceived((response) => {
console.log('Link:', response.link);
console.log('Data:', response.data);
// Route the user based on payload
if (response.data?.screen === 'product') {
navigation.navigate('Product', { id: response.data.productId });
}
});
// When you no longer need the listener
listener.remove();Set it up early, in your root component or app entry point. See React Native Deep Linking.
The onDeeplink callback you passed to configure receives the link's payload:
await Grovs.configure({
apiKey: 'your-api-key',
onDeeplink: (payload) => {
if (payload.screen === 'product_detail') {
showProduct(payload.productId);
}
},
});More details in Web Deep Linking.
- Select the environment (Production or Test) in the dashboard.
- Go to Links → Dynamic Links and click Add Link. In the guided setup, the Try it step makes this link and shows its QR code.
- Enter a custom path (for example
/summer-sale) and a Link Name for internal use. - Add key-value pairs under Data, for example
screen: productorcoupon: 25OFF. - Click Create.
Link types, social previews, redirect overrides and tracking parameters are in How to Create a Link.
Open the link on a device:
| Device | Result |
|---|---|
| App installed | The app opens and your handler receives the link and payload. |
| App not installed | The store or fallback URL, per your redirect rules. After install, the payload still reaches your handler (deferred deep linking). |
| Desktop | By default, a Grovs page with a QR code and download links. |
Where to next
- Full platform guides: iOS, Android, Flutter, React Native, Unity, Web
- How-to guides: setup values and configuration
- Links: Create a link, Project setup, Smart banners
- Engagement: Push campaigns and in-app messages
- Build with AI: Agent prompts and the MCP server
- REST API: Generate and manage links server-side