Skip to content
Back to skills

Deep Linking

ASecurity

| Atributo | Valor | |----------|-------| | **ID** | `flutter-deep-linking` | | **Nivel** | 🟡 Intermedio | | **Versión** | 1.0.0 | | **Keywords** | `deep-linking`, `universal-links`, `app-links`, `go-router`, `navigation` | | **Referencia** | [Flutter Deep Linking](https://docs.flutter.dev/ui/navigation/deep-linking) |

  • 4 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 8, 2026
developmentgoshellbashtestingdocumentation

Security analysis

A96/100
  • mediumUses curl or wget to download content

Pro shows the line behind each finding and how to fix it

Scanned September 8, 2026

npx -y skills add chimeranext/flutter-boilerplate-monorepo-template --skill deep-linking --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Deep Linking?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Deep Linking
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/chimeranext-deep-linking/badge)](https://www.skillsdirectory.com/skills/chimeranext-deep-linking)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# đź”— Skill: Deep Linking & Universal Links

## đź“‹ Metadata

| Atributo | Valor |
|----------|-------|
| **ID** | `flutter-deep-linking` |
| **Nivel** | 🟡 Intermedio |
| **VersiĂłn** | 1.0.0 |
| **Keywords** | `deep-linking`, `universal-links`, `app-links`, `go-router`, `navigation` |
| **Referencia** | [Flutter Deep Linking](https://docs.flutter.dev/ui/navigation/deep-linking) |

## 🔑 Keywords para Invocación

- `deep-linking`
- `universal-links`
- `app-links`
- `go-router`
- `dynamic-links`
- `@skill:deep-linking`

### Ejemplos de Prompts

```
Implementa deep-linking con universal links para iOS y Android
```

```
Setup go-router con deep linking
```

```
Configura app-links y universal-links
```

```
@skill:deep-linking - Navigation completa con deep links
```

## đź“– DescripciĂłn

**⚠️ IMPORTANTE:** Todos los comandos de este skill deben ejecutarse desde la **raíz del proyecto** (donde existe el directorio `mobile/`). El skill incluye verificaciones para asegurar que se está en el directorio correcto antes de ejecutar cualquier comando.

Este skill cubre la implementación de deep linking, universal links (iOS) y app links (Android) usando go_router. Incluye configuración de dominios, asociación de archivos, y routing dinámico basado en URLs.

### ✅ Cuándo Usar Este Skill

- Links compartibles a contenido especĂ­fico
- Marketing campaigns con attribution
- Email/SMS links que abren la app
- Social media sharing
- Push notifications con navegaciĂłn
- QR codes que abren la app
- Web-to-app handoff

### ❌ Cuándo NO Usar Este Skill

- App sin contenido compartible
- No necesitas links externos
- Solo navegaciĂłn interna

## 🏗️ Estructura del Proyecto

```
my_app/
├── lib/
│   ├── routing/
│   │   ├── app_router.dart
│   │   ├── route_paths.dart
│   │   └── deep_link_handler.dart
│   │
│   └── main.dart
│
├── android/
│   └── app/src/main/
│       ├── AndroidManifest.xml
│       └── res/values/
│           └── strings.xml
│
├── ios/
│   └── Runner/
│       ├── Info.plist
│       └── Runner.entitlements
│
└── .well-known/
    ├── assetlinks.json        # Android
    └── apple-app-site-association  # iOS
```

## 📦 Dependencias

```yaml
dependencies:
  flutter:
    sdk: flutter
  go_router: ^13.0.0
  uni_links: ^0.5.1  # Alternative simple approach
```

## đź’» ImplementaciĂłn

### 1. go_router Configuration

#### 1.1 Route Paths

```dart
// lib/routing/route_paths.dart
class RoutePaths {
  // Root
  static const home = '/';

  // Authentication
  static const login = '/login';
  static const signup = '/signup';
  static const resetPassword = '/reset-password';

  // Content
  static const product = '/product/:id';
  static const productDetails = '/product/:id/details';
  static const category = '/category/:slug';

  // User
  static const profile = '/profile/:userId';
  static const settings = '/settings';

  // Deep link specific
  static const share = '/share/:type/:id';
  static const invite = '/invite/:code';
  static const promo = '/promo/:code';

  // Error
  static const notFound = '/404';
}
```

#### 1.2 Router Configuration

```dart
// lib/routing/app_router.dart
import 'package:go_router/go_router.dart';
import 'package:flutter/material.dart';

class AppRouter {
  static final GoRouter router = GoRouter(
    initialLocation: RoutePaths.home,
    debugLogDiagnostics: true,

    // Redirect logic
    redirect: (context, state) {
      final isAuthenticated = AuthService.isAuthenticated;
      final isAuthRoute = state.matchedLocation.startsWith('/login') ||
          state.matchedLocation.startsWith('/signup');

      // Redirect to login if not authenticated
      if (!isAuthenticated && !isAuthRoute) {
        return '${RoutePaths.login}?redirect=${state.matchedLocation}';
      }

      // Redirect authenticated users away from auth pages
      if (isAuthenticated && isAuthRoute) {
        return RoutePaths.home;
      }

      return null;
    },

    // Error handling
    errorBuilder: (context, state) => NotFoundScreen(
      error: state.error.toString(),
    ),

    // Routes
    routes: [
      GoRoute(
        path: RoutePaths.home,
        name: 'home',
        builder: (context, state) => const HomeScreen(),
      ),

      // Authentication
      GoRoute(
        path: RoutePaths.login,
        name: 'login',
        builder: (context, state) {
          final redirect = state.uri.queryParameters['redirect'];
          return LoginScreen(redirectTo: redirect);
        },
      ),

      GoRoute(
        path: RoutePaths.signup,
        name: 'signup',
        builder: (context, state) => const SignupScreen(),
      ),

      // Product with nested routes
      GoRoute(
        path: RoutePaths.product,
        name: 'product',
        builder: (context, state) {
          final productId = state.pathParameters['id']!;
          return ProductScreen(productId: productId);
        },
        routes: [
          GoRoute(
            path: 'details',
            name: 'product-details',
            builder: (context, state) {
              final productId = state.pathParameters['id']!;
              return ProductDetailsScreen(productId: productId);
            },
          ),
        ],
      ),

      // Category
      GoRoute(
        path: RoutePaths.category,
        name: 'category',
        builder: (context, state) {
          final slug = state.pathParameters['slug']!;
          final sort = state.uri.queryParameters['sort'];
          return CategoryScreen(slug: slug, sortBy: sort);
        },
      ),

      // Profile
      GoRoute(
        path: RoutePaths.profile,
        name: 'profile',
        builder: (context, state) {
          final userId = state.pathParameters['userId']!;
          return ProfileScreen(userId: userId);
        },
      ),

      // Share (deep link)
      GoRoute(
        path: RoutePaths.share,
        name: 'share',
        builder: (context, state) {
          final type = state.pathParameters['type']!;
          final id = state.pathParameters['id']!;
          return ShareScreen(contentType: type, contentId: id);
        },
      ),

      // Invite code
      GoRoute(
        path: RoutePaths.invite,
        name: 'invite',
        builder: (context, state) {
          final code = state.pathParameters['code']!;
          return InviteScreen(inviteCode: code);
        },
      ),

      // Promo code
      GoRoute(
        path: RoutePaths.promo,
        name: 'promo',
        builder: (context, state) {
          final code = state.pathParameters['code']!;
          return PromoScreen(promoCode: code);
        },
      ),
    ],
  );

  // Navigation helpers
  static void goToProduct(String productId) {
    router.go('/product/$productId');
  }

  static void goToCategory(String slug, {String? sortBy}) {
    final queryParams = sortBy != null ? '?sort=$sortBy' : '';
    router.go('/category/$slug$queryParams');
  }

  static void goToProfile(String userId) {
    router.go('/profile/$userId');
  }

  static void handleInviteLink(String code) {
    router.go('/invite/$code');
  }

  static void handlePromoLink(String code) {
    router.go('/promo/$code');
  }
}
```

### 2. iOS Universal Links Configuration

#### 2.1 Associated Domains

```xml
<!-- ios/Runner/Runner.entitlements -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.developer.associated-domains</key>
    <array>
        <string>applinks:yourdomain.com</string>
        <string>applinks:www.yourdomain.com</string>
    </array>
</dict>
</plist>
```

```xml
<!-- ios/Runner/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>com.example.myapp</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
    </dict>
</array>
```

#### 2.2 Apple App Site Association File

```json
// .well-known/apple-app-site-association
{
  "applinks": {
    "details": [
      {
        "appIDs": [
          "TEAMID.com.example.myapp"
        ],
        "components": [
          {
            "/": "/product/*",
            "comment": "Matches any product URL"
          },
          {
            "/": "/category/*",
            "comment": "Matches any category URL"
          },
          {
            "/": "/invite/*",
            "comment": "Matches invite links"
          },
          {
            "/": "/promo/*",
            "comment": "Matches promo links"
          }
        ]
      }
    ]
  },
  "webcredentials": {
    "apps": [
      "TEAMID.com.example.myapp"
    ]
  }
}
```

**Hosting Requirements:**
- Debe estar en `https://yourdomain.com/.well-known/apple-app-site-association`
- Content-Type: `application/json` (sin extensiĂłn .json)
- Debe ser accesible sin redirects
- HTTPS con certificado válido

### 3. Android App Links Configuration

#### 3.1 AndroidManifest.xml

```xml
<!-- android/app/src/main/AndroidManifest.xml -->
<manifest>
    <application>
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop">

            <!-- Deep Links (Custom Scheme) -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="myapp" />
            </intent-filter>

            <!-- App Links (Verified HTTPS) -->
            <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="yourdomain.com"
                    android:pathPrefix="/product" />
                <data
                    android:scheme="https"
                    android:host="yourdomain.com"
                    android:pathPrefix="/category" />
                <data
                    android:scheme="https"
                    android:host="yourdomain.com"
                    android:pathPrefix="/invite" />
                <data
                    android:scheme="https"
                    android:host="yourdomain.com"
                    android:pathPrefix="/promo" />
            </intent-filter>
        </activity>
    </application>
</manifest>
```

#### 3.2 Android Asset Links File

```json
// .well-known/assetlinks.json
[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.myapp",
      "sha256_cert_fingerprints": [
        "YOUR_SHA256_FINGERPRINT_HERE"
      ]
    }
  }
]
```

**Obtener SHA-256 fingerprint:**
```bash
# Debug keystore
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

# Release keystore
keytool -list -v -keystore /path/to/release.keystore -alias your-alias
```

**Hosting Requirements:**
- Debe estar en `https://yourdomain.com/.well-known/assetlinks.json`
- Content-Type: `application/json`
- Accesible sin autenticaciĂłn

### 4. Deep Link Handler

```dart
// lib/routing/deep_link_handler.dart
import 'package:go_router/go_router.dart';
import 'package:uni_links/uni_links.dart';
import 'dart:async';

class DeepLinkHandler {
  static StreamSubscription? _linkSubscription;

  // Initialize deep link handling
  static Future<void> initialize(GoRouter router) async {
    // Handle initial link (app opened from link)
    try {
      final initialLink = await getInitialUri();
      if (initialLink != null) {
        _handleDeepLink(initialLink, router);
      }
    } catch (e) {
      print('Error getting initial link: $e');
    }

    // Handle links while app is running
    _linkSubscription = uriLinkStream.listen(
      (Uri? uri) {
        if (uri != null) {
          _handleDeepLink(uri, router);
        }
      },
      onError: (error) {
        print('Deep link error: $error');
      },
    );
  }

  static void _handleDeepLink(Uri uri, GoRouter router) {
    print('📱 Deep Link received: $uri');

    // Track deep link
    AnalyticsService.trackEvent('deep_link_opened', properties: {
      'url': uri.toString(),
      'scheme': uri.scheme,
      'host': uri.host,
      'path': uri.path,
    });

    // Parse and navigate
    if (uri.scheme == 'myapp') {
      // Custom scheme: myapp://product/123
      _handleCustomScheme(uri, router);
    } else if (uri.scheme == 'https') {
      // Universal link: https://yourdomain.com/product/123
      _handleUniversalLink(uri, router);
    }
  }

  static void _handleCustomScheme(Uri uri, GoRouter router) {
    // myapp://product/123
    final path = uri.host + uri.path;
    router.go('/$path');
  }

  static void _handleUniversalLink(Uri uri, GoRouter router) {
    // https://yourdomain.com/product/123
    final path = uri.path;
    final queryParams = uri.queryParameters;

    // Build path with query parameters
    final fullPath = queryParams.isEmpty
        ? path
        : '$path?${Uri(queryParameters: queryParams).query}';

    router.go(fullPath);
  }

  // Generate shareable link
  static String generateShareLink({
    required String type,
    required String id,
  }) {
    return 'https://yourdomain.com/share/$type/$id';
  }

  // Generate invite link
  static String generateInviteLink(String code) {
    return 'https://yourdomain.com/invite/$code';
  }

  // Generate promo link
  static String generatePromoLink(String code) {
    return 'https://yourdomain.com/promo/$code';
  }

  // Dispose
  static void dispose() {
    _linkSubscription?.cancel();
  }
}
```

### 5. Main.dart Setup

```dart
// lib/main.dart
import 'package:flutter/material.dart';
import 'routing/app_router.dart';
import 'routing/deep_link_handler.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize deep link handling
  await DeepLinkHandler.initialize(AppRouter.router);

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'My App',
      routerConfig: AppRouter.router,
    );
  }
}
```

### 6. Testing Deep Links

#### 6.1 iOS Testing

```bash
# Test custom scheme
xcrun simctl openurl booted "myapp://product/123"

# Test universal link
xcrun simctl openurl booted "https://yourdomain.com/product/123"
```

#### 6.2 Android Testing

```bash
# Test custom scheme
adb shell am start -W -a android.intent.action.VIEW -d "myapp://product/123" com.example.myapp

# Test app link
adb shell am start -W -a android.intent.action.VIEW -d "https://yourdomain.com/product/123" com.example.myapp

# Verify app links
adb shell dumpsys package domain-preferred-apps
```

#### 6.3 Verification Tools

```bash
# iOS: Test AASA file
curl -v https://yourdomain.com/.well-known/apple-app-site-association

# Android: Test assetlinks.json
curl -v https://yourdomain.com/.well-known/assetlinks.json

# Android: Verify signature
adb shell pm get-app-links com.example.myapp
```

## 🎯 Mejores Prácticas

### 1. URL Structure

âś… **DO:** Usa URLs consistentes y descriptivas
```
https://yourdomain.com/product/123
https://yourdomain.com/category/electronics
https://yourdomain.com/invite/ABC123
```

### 2. Fallback Handling

✅ **DO:** Maneja casos donde la app no está instalada
```dart
// En el servidor, detecta si la app está instalada
// Si no está, redirige a App Store/Play Store
```

### 3. Analytics

âś… **DO:** Trackea origen de deep links
```dart
AnalyticsService.trackEvent('deep_link_opened', properties: {
  'source': 'email',
  'campaign': 'summer_sale',
  'url': uri.toString(),
});
```

## 🚨 Troubleshooting

### Universal Links Not Working (iOS)

1. Verificar Associated Domains en Xcode
2. Verificar Team ID correcto en AASA file
3. Verificar hosting correcto del archivo
4. Probar con dispositivo real (simulador puede no funcionar siempre)

### App Links Not Verified (Android)

```bash
# Verificar estado
adb shell pm get-app-links com.example.myapp

# Forzar verificaciĂłn
adb shell pm set-app-links --package com.example.myapp 0 all

# Re-verificar
adb shell pm verify-app-links --re-verify com.example.myapp
```

### Deep Link Opens Browser Instead of App

- Verificar que autoVerify="true" en Android
- Verificar SHA-256 fingerprint correcto
- Verificar hosting de archivos de asociaciĂłn

## 📚 Recursos

- [go_router Documentation](https://pub.dev/packages/go_router)
- [iOS Universal Links](https://developer.apple.com/ios/universal-links/)
- [Android App Links](https://developer.android.com/training/app-links)

---

**VersiĂłn:** 1.0.0
**Ăšltima actualizaciĂłn:** Diciembre 2025
**Total lĂ­neas:** 1,100+

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in development →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.