How AppFlowy Handles Deep Links for Login, Payment, and Invitation Callbacks

AppFlowy implements a plug-in-style deep link framework using an abstract DeepLinkHandler class, a singleton DeepLinkHandlerRegistry, and platform-specific listeners to route login, payment, and invitation URIs to specialized handlers.

AppFlowy leverages a modular deep link architecture to manage external callbacks for authentication, billing, and workspace invitations. According to the AppFlowy-IO/AppFlowy source code, the system delegates URI processing to discrete handlers registered during application startup, enabling clean separation between deep link detection and business logic.

Core Architecture Components

Three fundamental building blocks power AppFlowy's deep link handling:

  • DeepLinkHandler<T>: An abstract base class defined in frontend/appflowy_flutter/lib/startup/tasks/deeplink/deeplink_handler.dart that declares canHandle(Uri) and handle(...) methods. Each concrete implementation decides whether it can process a URI and executes the associated logic.

  • DeepLinkHandlerRegistry: A singleton registry that maintains a list of all active handlers. When processDeepLink is invoked, the registry iterates through registered handlers and dispatches the URI to the first handler returning true from canHandle.

  • AppFlowyCloudDeepLink: The bootstrap component in frontend/appflowy_flutter/lib/startup/tasks/appflowy_cloud_task.dart that initializes system-wide listeners and forwards incoming URIs to the registry. It also exposes a ValueNotifier so the UI can react to loading states.

Handler Registration During Startup

During application initialization, the AppFlowyCloudDeepLink constructor instantiates the registry and registers concrete handlers for each supported flow:

_deepLinkHandlerRegistry = DeepLinkHandlerRegistry.instance
  ..register(LoginDeepLinkHandler())
  ..register(PaymentDeepLinkHandler())
  ..register(InvitationDeepLinkHandler())
  ..register(ExpireLoginDeepLinkHandler())
  ..register(OpenAppDeepLinkHandler());

This registration pattern appears in frontend/appflowy_flutter/lib/startup/tasks/appflowy_cloud_task.dart, ensuring all handlers are available before the first URI arrives.

Platform-Specific URI Listening

The system adapts to platform conventions for intercepting external links:

  • Mobile: Uses the AppLinks library to emit a Uri? for every custom-scheme launch.
  • Windows: Registers the appflowy-flutter:// protocol via registerProtocolHandler(appflowyDeepLinkSchema).

The listener forwards captured URIs to _handleUri, which delegates processing to the registry:

await _deepLinkHandlerRegistry.processDeepLink(
  uri: uri,
  onStateChange: ...,
  onResult: ...,
  onError: ...,
);

Each specialized handler resides in frontend/appflowy_flutter/lib/startup/tasks/deeplink/ and implements specific business logic:

LoginDeepLinkHandler (login_deeplink_handler.dart) Detects URIs where uri.fragment contains access_token (e.g., appflowy-flutter://login-callback#access_token=...). Upon handling, it dispatches a UserEventOauthSignIn to the backend, updates the loading state, and returns the signed-in UserProfilePB.

InvitationDeepLinkHandler (invitation_deeplink_handler.dart) Matches hosts equal to invitation-callback with query parameters workspace_id and email. It extracts these values and pushes a WorkspaceNotifyValue to openWorkspaceNotifier, triggering the UI to open the invited workspace.

PaymentDeepLinkHandler (payment_deeplink_handler.dart) Handles appflowy-flutter://payment-success?... URIs by reading the plan query parameter and notifying SubscriptionSuccessListenable that the purchase succeeded.

OpenAppDeepLinkHandler (open_app_deeplink_handler.dart) Serves as a fallback for any appflowy-flutter:// URI that does not match specific handlers. It invokes runAppFlowy() to bring the application to the foreground without additional processing.

ExpireLoginDeepLinkHandler (expire_login_deeplink_handler.dart) Processes token-expiry callbacks and surfaces error toasts to inform users of authentication failures.

State Management and UI Feedback

The framework propagates execution progress through typed callbacks:

  • DeepLinkState: An enum with values none, loading, finish, and error emitted via onStateChange.
  • ValueNotifier: AppFlowyCloudDeepLink exposes subscribeDeepLinkLoadingState, which UI components use to display loading indicators during login flows (the only flow requiring explicit UI feedback).

Final results arrive through onResult (success or failure for recognized handlers) or onError (for malformed or unknown URIs).

Developers can extend the system by subclassing DeepLinkHandler:

class MyCustomHandler extends DeepLinkHandler<void> {
  @override
  bool canHandle(Uri uri) => uri.host == 'my-custom';

  @override
  Future<FlowyResult<void, FlowyError>> handle({
    required Uri uri,
    required DeepLinkStateHandler onStateChange,
  }) async {
    // Custom logic here
    return FlowyResult.success(null);
  }
}

// Registration during startup
DeepLinkHandlerRegistry.instance.register(MyCustomHandler());

Summary

  • AppFlowy uses a registry pattern (DeepLinkHandlerRegistry) to route URIs to the first capable handler.
  • Five concrete handlers manage login, payment, invitation, token expiry, and generic app-opening deep links.
  • Platform listeners in AppFlowyCloudDeepLink capture URIs on mobile and Windows, forwarding them to processDeepLink.
  • State changes propagate through callbacks and ValueNotifier instances, enabling UI feedback during asynchronous operations like OAuth sign-in.
  • Unit tests in frontend/appflowy_flutter/test/unit_test/deeplink/deeplink_test.dart verify handler routing and error handling.
  • The architecture supports extensibility via the abstract DeepLinkHandler base class.

Frequently Asked Questions

Each handler implements canHandle(Uri) with specific matching logic. For example, LoginDeepLinkHandler checks for access_token in the fragment, while PaymentDeepLinkHandler verifies the host equals payment-success. The registry iterates through registered handlers in order and selects the first returning true.

The AppFlowyCloudDeepLink class in frontend/appflowy_flutter/lib/startup/tasks/appflowy_cloud_task.dart initializes platform-specific listeners. It uses the AppLinks library for mobile and url_protocol with registerProtocolHandler(appflowyDeepLinkSchema) for Windows to capture appflowy-flutter:// scheme launches.

Yes. Create a class extending DeepLinkHandler<T> in frontend/appflowy_flutter/lib/startup/tasks/deeplink/, implement canHandle to match your URI pattern, and register the instance via DeepLinkHandlerRegistry.instance.register() during application startup.

The AppFlowyCloudDeepLink class exposes subscribeDeepLinkLoadingState, a ValueNotifier that emits DeepLinkResult objects containing the current state (loading, finish, or error). UI widgets listen to this notifier to display progress indicators or error messages during the OAuth handshake.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →