Skip to content

Links

A link to a place in your app opens the app on a phone that has it, and a web page or the app store on one that does not: from an email, a QR code on a table, a text message. Each app has its own links, and its test app its own again.

A link is your app’s path under its link base:

  • on PurrCRM‘s own host: https://app.purrcrm.com/l/<your app's link name>/<path>, which needs nothing but the settings below; or
  • on a host of your own, such as https://go.northwind.example/<path>, whose DNS points at PurrCRM.

/offers/42 in your app is then <link base>/offers/42. PurrCRM‘s own pages are in the same space, such as <link base>/messages/tickets/<id>.

On Settings › Channels › Links (an owner or admin):

  • Name: your app’s part of PurrCRM‘s host (northwind), or a host of its own. A host of your own needs a DNS record (a CNAME to PurrCRM‘s host); the tab checks it, and your PurrCRM administrator then adds the host.
  • iOS apps: <Apple team id>.<bundle id>, such as ABCDE12345.com.northwind.app.
  • Android package and Android signing SHA-256: your applicationId, and the SHA-256 of every certificate the app is signed with (from Play Console › App integrity, plus your upload and debug keys: keytool -list -v -keystore <file>).
  • Optionally a web page for a phone without the app ({path} in it is the link’s path), and your App Store and Google Play pages. Without them, a phone without the app gets a page in your brand with “Open in Northwind” and the stores.
  • Use these links in emails, once most phones run a build with step 2.

PurrCRM then serves the files phones read to trust your app with the links (/.well-known/apple-app-site-association and /.well-known/assetlinks.json). Check what phones read fetches both as a phone does. The tab also shows the snippets below, filled in for your app.

Android, in the activity that opens links (AndroidManifest.xml):

<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="crm.example.com" android:pathPrefix="/l/northwind/" />
</intent-filter>

Use your PurrCRM host as android:host (or your own host, with no pathPrefix). On iOS, add the Associated Domains capability (Runner.entitlements):

<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:crm.example.com</string>
</array>

A debug build may write applinks:crm.example.com?mode=developer, with developer mode on in the phone’s settings, so iOS reads the file afresh. Associated Domains needs a paid Apple team.

Before your own handling, give the facade every link the phone hands you. handleLink answers whether the link was PurrCRM‘s; it then goes where it names once a user is signed in and ready, as a tapped push does. With the app_links package:

final appLinks = AppLinks();
Future<void> openLink(Uri link) async {
if (await crm.handleLink(link)) return;
// Your own links (a product, a referral...), as before.
}
final initial = await appLinks.getInitialLink();
if (initial != null) unawaited(openLink(initial));
appLinks.uriLinkStream.listen(openLink);

An email’s link the app opened counts as the email’s click, and every link is recorded as a link_opened event for segments and goals.

Declare the places a link may go, so your team picks them in PurrCRM‘s link picker instead of typing paths:

CrmHost(
followable: (link) =>
link.startsWith('/offers') || link == '/wallet' ? link : null,
openInApp: router.go,
links: const [
CrmLink(name: 'Offer page', path: '/offers/{offer_id}', screen: 'offer'),
CrmLink(name: 'All offers', path: '/offers?category={category}'),
CrmLink(name: 'Wallet', path: '/wallet', screen: 'wallet'),
],
)

A part in braces is filled in by whoever writes the link: a value, or an attribute such as {{first_name}}. webUrl is the place’s page on the web. The app sends the list once per build, and your team finds it under Settings › Channels › Links › Destinations. Declaring a place opens nothing: followable still decides.

Call crm.screen('wallet') as each screen shows (the same name every time; never a path or an id). Your team can then show an in-app message only on some screens. With go_router:

router.routerDelegate.addListener(() {
final name = screenNameOf(router.routerDelegate.currentConfiguration.uri.path);
if (name != null) crm.screen(name);
});