Check that Android App Links and iOS Universal Links agree across your app configuration and website association files—before you publish them.
Android Studio and hosted validators help check files already served by a
public domain. app-link-contract checks the local release inputs in one
offline command: Android intent filters against assetlinks.json, and iOS
associated-domain entitlements against Apple App Site Association (AASA)
files. It also checks signing fingerprints and optional example URLs, so CI
can catch configuration drift before device testing.
With Go 1.22 or later:
go run ./cmd/app-link-contract verify --config testdata/valid-config.jsonThe checked-in fixture passes and demonstrates both platforms. The app reads local files only; it does not make requests to the domains in the config.
Install the latest release with Go:
go install github.com/thisbejim/app-link-contract/cmd/app-link-contract@latestOr download a macOS, Linux, or Windows archive from GitHub Releases.
Create app-links.json in your repository. Paths are relative to that file.
{
"version": 1,
"android": {
"manifest": "android/app/src/main/AndroidManifest.xml",
"application_id": "com.example.shop",
"certificate_fingerprints": [
"01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF"
],
"assetlinks": {
"shop.example.com": "website/public/.well-known/assetlinks.json"
}
},
"ios": {
"entitlements": "ios/App/App.entitlements",
"application_identifier": "ABCDE12345.com.example.shop",
"aasa": {
"shop.example.com": "website/public/.well-known/apple-app-site-association"
}
},
"test_urls": [
"https://shop.example.com/shop/123?campaign=summer"
]
}Then run:
app-link-contract verify --config app-links.jsonReplace the example fingerprint with the real app-signing fingerprint. If a
build flavor changes hosts or application IDs, point manifest at that
variant's merged release manifest instead of the source manifest shown above.
Use the SHA-256 fingerprint of the certificate that signs the app users install. With Play App Signing, that is the app-signing certificate shown in Play Console, not necessarily your upload or local build certificate. Add multiple fingerprints when the domain intentionally trusts more than one app signing certificate. Fingerprints can be upper- or lowercase and may include colons.
The iOS application_identifier is the app identifier prefix followed by the
bundle identifier (for example, ABCDE12345.com.example.shop). It may differ
from the Apple team ID. If the entitlements plist also contains
application-identifier, the checker compares it with this value.
For an Android wildcard manifest host such as *.example.com, list an
assetlinks.json mapping for each concrete host you serve and test. For an iOS
wildcard entitlement applinks:*.example.com, map the root domain
example.com to its AASA file, as Apple specifies.
PASS app-link-contract: 0 error(s), 0 warning(s)
NOTE ANDROID_ASSETLINKS_OK android website/public/.well-known/assetlinks.json
assetlinks for "shop.example.com" grants com.example.shop using a configured signing fingerprint
NOTE IOS_AASA_OK ios website/public/.well-known/apple-app-site-association
AASA file for "shop.example.com" lists ABCDE12345.com.example.shop
NOTE ANDROID_TEST_URL_MATCHES android android/app/build/intermediates/merged_manifest/...
sample URL matches an HTTPS web intent filter
NOTE IOS_TEST_URL_MATCHES ios website/public/.well-known/apple-app-site-association
sample URL is admitted by this app's AASA rules
Diagnostics have stable codes. Errors return exit code 1; malformed
arguments, config, or input files return 2. Warnings pass by default; use
--strict to make them fail. CI tools can read JSON or SARIF:
app-link-contract verify --config app-links.json --format json
app-link-contract verify --config app-links.json --format sarifSample URL reports omit query strings and fragments. Their values are still used for AASA matching locally but are not copied into reports or CI logs.
- Android VIEW, DEFAULT, and BROWSABLE HTTPS intent filters, including
android:autoVerify, host declarations, and common path matchers. - Each concrete Android host's local Digital Asset Links statement: app ID,
delegate_permission/common.handle_all_urls, and configured SHA-256 signing certificate fingerprints. - iOS
applinks:domains from XML entitlements, the configured app identifier, and each domain's local AASA app entry. - Sample URLs against Android
path,pathPrefix,pathSuffix, and simplepathPatternfilters, and against current AASAcomponentsor legacypathsrules. - Text, versioned JSON, and SARIF 2.1.0 output.
This is a local source/configuration check, not a device or server emulator. It cannot prove that a deployed file is reachable over HTTPS, redirects are absent, Apple or Android caches have refreshed, or the app's runtime router handles the URL. Verify the public endpoint and test on devices before release.
The Android input is an XML manifest; use a merged release manifest when build
variants or manifest placeholders affect the final values. Android 15+
dynamic_app_link_components are reported as a boundary and are not applied
to sample URL decisions. pathAdvancedPattern rules are reported as
unverifiable. The iOS input is an XML entitlements plist; binary plists are not
supported. Unknown AASA component keys do not produce a claimed URL match.
go test ./...
go test -race ./...
go vet ./...
go build ./...The runtime has no third-party dependencies. See product specification and opportunity research. Licensed under MIT; see LICENSE.