Docs

Quickstart

Go from zero to a short link that opens the right screen in your Flutter app, in about ten minutes.

By the end of this page you will have a Lander project, your iOS and Android apps registered, the SDK running in your app, and a short link that opens a specific screen on a real device.

You need a Flutter app (Flutter 3.38 or newer, iOS 15+, Android API 24+) with a router such as go_router, and an Apple Developer account for Universal Links on iOS.

Let an AI agent do the app side

Using Claude Code, Cursor or Codex? Paste this prompt into the agent from your app's folder. It adds the package, initializes it, routes links and sets up iOS and Android, and asks you for anything it cannot read from the project.

Setup prompt
Set up Lander deep linking in this Flutter app.Lander (https://golander.link) gives the app short links that open the right screen on iOS and Android, including right after an install. The SDK is lander_flutter on pub.dev. Its README is the reference for everything below: https://pub.dev/packages/lander_flutterProject details (when a value says "ask me", ask before you use it):- Link domain: LANDER_DOMAIN, <ask me: my link domain, for example acme.golander.link>- Publishable key: <ask me: my pk_... key from API keys in the Lander dashboard>- iOS bundle ID: read it from the Xcode project- Android package name: read it from android/app/build.gradle(.kts)- Custom URL scheme: none (skip the custom scheme steps)Before changing anything, read pubspec.yaml, lib/main.dart, the router setup, ios/Runner/Info.plist, ios/Runner/*.entitlements and android/app/src/main/AndroidManifest.xml. Tell me which router the app uses and what you plan to change.1. Add the package: `flutter pub add lander_flutter`. It needs Flutter 3.38 or newer, iOS 15+ and Android API 24+. If the iOS deployment target or minSdk is lower, raise it and tell me.2. Initialize before runApp. Make main() async, call WidgetsFlutterBinding.ensureInitialized() if anything else runs first, then:   await Lander.initialize(apiKey: 'pk_...', enableLogging: kDebugMode);   Use the import 'package:lander_flutter/lander_flutter.dart'. Publishable keys (pk_test_ or pk_live_) are safe to ship in the app. Never use a secret key (sk_), the SDK rejects it. If the project already reads config from --dart-define or an env file, put the key there instead of hardcoding it.3. Route every link. Subscribe to Lander.onDeepLink once, where the router exists (for example initState of the root widget), navigate to link.route and pass the LanderLink along (with go_router: router.go(link.route, extra: link)). Cancel the subscription in dispose. Cold starts, warm starts and deferred links all arrive on this one stream, buffered until you listen, so do not also call getInitialLink unless the app needs the launch link to pick its first screen. When link.isResolved is false or the route matches no screen, ignore it instead of showing an error page.4. iOS:   - Add the Associated Domains entitlement "applinks:LANDER_DOMAIN" to the Runner target. Create or update ios/Runner/Runner.entitlements and make sure CODE_SIGN_ENTITLEMENTS points to it for the Debug, Release and Profile configurations in project.pbxproj.   - In ios/Runner/Info.plist set FlutterDeepLinkingEnabled to false, so Flutter's own handler does not navigate a second time.5. Android, in android/app/src/main/AndroidManifest.xml inside the main activity:   - Add <meta-data android:name="flutter_deeplinking_enabled" android:value="false" />.   - Add an intent-filter with android:autoVerify="true", the android.intent.action.VIEW action, the DEFAULT and BROWSABLE categories and <data android:scheme="https" android:host="LANDER_DOMAIN" />.   - Keep android:launchMode="singleTop".6. Deferred deep links need no extra code. Links with link.confidence == LanderConfidence.probabilistic are a best guess: it is fine to open the screen, but never sign the user in, apply a credit or skip a payment based on one.7. Optional, ask me first: if the app already records sign ups or purchases, also call Lander.trackEvent('purchase', properties: {...}, linkId: link.linkId) there so conversions show up in Lander. If my backend creates Lander handoffs, call Lander.claimHandoff(user.id) right after sign in and navigate to the returned link when it is not null.8. Check your work: run flutter analyze and fix what you introduced. Then give me the commands to test a link without tapping one:   xcrun simctl openurl booted "https://LANDER_DOMAIN/<slug>"   adb shell am start -a android.intent.action.VIEW -d "https://LANDER_DOMAIN/<slug>" <package name>Finish with a short list of the files you changed, and remind me what is left in the Lander dashboard: under Setup, then Platforms, add the Apple Team ID and bundle ID, plus the Android package name and the SHA-256 fingerprints of every signing key (debug, upload and the Play App Signing key), so Lander can serve apple-app-site-association and assetlinks.json for LANDER_DOMAIN.

The SDK page in the dashboard has the same prompt with your project's link domain, key prefix, bundle ID and package name already filled in. Copy it from there when you can.

The agent handles the code in your app (steps 4 to 7 below). You still create the project, register your apps and create a key in the dashboard yourself.

Step by step

Create a project

Sign up at app.golander.link. The first screen asks for an Organization (your team) and a First project (one app, with its own links, keys and analytics). Click Create workspace.

Your project gets a hosted link domain right away, in the form <project>-<org>.golander.link. The form shows the exact hostname before you create it. You will use it in the platform setup below.

Add your iOS and Android apps

Open Platforms in the sidebar (under Build).

  • iOS app: enter your Team ID and Bundle ID. Add the App Store ID so people without the app go to your App Store page, and a URI scheme if your app registers one. Click Save iOS app.
  • Android app: enter the Package name and the SHA-256 certificate fingerprints of every key that signs your app (debug, upload and the Play App Signing key; ./gradlew signingReport prints them). Add the Play Store URL if you like. Click Save Android app.

Lander now generates and hosts apple-app-site-association and assetlinks.json on your link domain. The preview next to each form shows the file and whether it is complete.

Create a publishable key

Go to Settings > API keys. The dashboard starts in test mode (the Test / Live switch at the top), which is what you want for now.

Click Create key, choose Publishable, and copy the key. It starts with pk_test_. Lander only stores a hash, so this is the only time you see the full key.

Publishable keys are safe to ship in your app: they can resolve links and send events, nothing more. See Environments and keys.

Install the SDK

Add lander_flutter from pub.dev:

flutter pub add lander_flutter

Set up iOS and Android

Each platform needs to know that your link domain belongs to your app, and Flutter's built in deep link handling should be turned off so links are not handled twice. Replace acme.golander.link with your own link domain.

In Xcode, add the Associated Domains capability to the Runner target with applinks:acme.golander.link. Then turn off Flutter's deep linking in ios/Runner/Info.plist:

ios/Runner/Info.plist
<key>FlutterDeepLinkingEnabled</key>
<false/>

Full details, including custom schemes and the privacy manifest: iOS setup.

Initialize Lander

Call Lander.initialize once, before runApp, with your publishable key. It returns quickly: the launch report runs in the background and never blocks startup on the network.

lib/main.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:lander_flutter/lander_flutter.dart';

Future<void> main() async {
  await Lander.initialize(
    apiKey: 'pk_test_...', // publishable key from the dashboard
    enableLogging: kDebugMode,
  );
  runApp(const MyApp());
}

Secret keys (sk_...) are rejected, because anything shipped in an app is public.

Subscribe to Lander.onDeepLink once your router exists and navigate to link.route. Cold starts, warm starts and deferred links all arrive on this one stream, and links that arrive before you listen are buffered, so nothing is lost during startup.

lib/app.dart
class _MyAppState extends State<MyApp> {
  final _router = GoRouter(routes: [/* your routes */]);
  StreamSubscription<LanderLink>? _links;

  @override
  void initState() {
    super.initState();
    _links = Lander.onDeepLink.listen((link) {
      _router.go(link.route, extra: link);
    });
  }

  @override
  void dispose() {
    _links?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => MaterialApp.router(routerConfig: _router);
}

link.params holds the link's parameters, and link.isResolved tells you whether Lander recognized the link. More in Receiving links.

In the dashboard, open Links and click Create link (or press C).

  • Opens in your app at: the route your router receives, for example /product/123.
  • Short link: keep the random slug or type your own.
  • When the app is not installed: optional fallback URLs for iOS, Android and web.

Click Create link and copy the short link. Make sure the dashboard is still in test mode: a pk_test_ key only resolves test links.

Test it on a device

Install a build of your app on a real device, send yourself the short link (in Notes, Messages or an email) and tap it. The app should open on the route you set.

To open a link without tapping one:

# Android device or emulator
adb shell am start -a android.intent.action.VIEW -d "https://acme.golander.link/your-slug" com.acme.shop

# iOS simulator
xcrun simctl openurl booted "https://acme.golander.link/your-slug"

Nothing happening? Paste the link into the Debugger in the dashboard. It checks the link, your app configuration, the .well-known files on the edge and whether your app has reported in the last 24 hours, and links each failure to its fix. Testing and Troubleshooting cover the rest.

Next steps

On this page