---
title: "Building deep-links in Electron application"
description: "Building deep-links in Electron application"
canonical_url: "https://www.bigbinary.com/blog/deep-link-electron-app"
markdown_url: "https://www.bigbinary.com/blog/deep-link-electron-app.md"
---

# Building deep-links in Electron application

Building deep-links in Electron application

- Author: Farhan CK
- Published: November 26, 2024
- Categories: Electron

_Recently, we built [NeetoRecord](https://neetorecord.com/neetorecord/), a loom
alternative. The desktop application was built using Electron. In a series of
blogs, we capture how we built the desktop application and the challenges we ran
into. This blog is part 6 of the blog series. You can also read about
[part 1](https://www.bigbinary.com/blog/sync-store-main-renderer-electron),
[part 2](https://www.bigbinary.com/blog/publish-electron-application),
[part 3](https://www.bigbinary.com/blog/video-background-removal),
[part 4](https://www.bigbinary.com/blog/electron-multiple-browser-windows),
[part 5](https://www.bigbinary.com/blog/code-sign-notorize-mac-desktop-app),
[part 7](https://www.bigbinary.com/blog/request-camera-micophone-permission-electron)
[part 8](https://www.bigbinary.com/blog/native-modules-electron) and
[part 9](https://www.bigbinary.com/blog/ev-code-sign-windows-application-ssl-com)
._

When developing a desktop application, including every feature directly within
the app is often unnecessary. Instead, we can offload some tasks such as
login/signup to a web application. And from the web app, create deep-links to
the desktop app. We can also create shareable links that opens specific content
on the app.

In this blog, we are going to discuss how to create deep-links in our
[Electron](https://electronjs.org/) application.

## Register a custom protocol

A protocol is a custom URL scheme that an application can handle, similar to how
browsers handle protocols like `http`, `https`, `mailto`, or `ftp`. Every
operating system supports the handling of custom protocols. We can register an
application as a default handler for a custom protocol with the operating
system. Electron provides a simple API to register a default protocol client.

```js
if (process.defaultApp) {
  if (process.argv.length >= 2) {
    app.setAsDefaultProtocolClient("my-app", process.execPath, [
      path.resolve(process.argv[1]),
    ]);
  }
} else {
  app.setAsDefaultProtocolClient("my-app");
}
```

The code snippet above is a simple example of registering a default protocol
client. In the example, we registered a custom protocol named `my-app` with the
operating system. It's important to note that the application must be properly
packaged and installed so that the operating system can correctly handle and
launch the registered app.

We used `process.defaultApp` instead of `app.isPackaged` because Electron allows
us to run a packaged app using the `electron .` command. In such cases, we need
to provide the execution path for the system to recognize the app. However, if
the app is running in normal mode, simply calling
`app.setAsDefaultProtocolClient` is sufficient.

## Handling custom protocol

Though registering the protocol is simple and the same for every platform, there
are some differences when it comes to handling it.

**For macOS**, we need to listen to the `open-url` event.

```js
const handleCustomUrl = url => {
  // Handle url
};

app.on("open-url", (event, url) => {
  handleCustomUrl(url);
});
```

**On both Windows and Linux**, if the application is up and running, a
`second-instance` event is emitted instead of `open-url` when a protocol request
is received. This means a new instance of our app will be created. If we want to
avoid this behavior and notify the existing instance instead, we'll need to
ensure the app runs as a single instance. We can achieve this by using
`requestSingleInstanceLock`.

```js
const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
  app.quit();
}
```

The above code ensures that our Windows or Linux app runs as a single instance.
If a user attempts to open a new instance, that instance will try to acquire the
single instance lock. If it fails, the newly created instance will simply quit.

```js
const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
  app.quit();
} else {
  app.on("second-instance", (event, commands, workingDir) => {
    handleCustomUrl(commands.pop());
  });
}
```

If we successfully acquire the single instance lock, it means we're running the
first instance of the app. When a user attempts to open another instance,
Electron emits a `second-instance` event to the existing instance. The second
argument of this event contains an array of command-line arguments, and we can
retrieve the custom URL from the last item in this array.

We've addressed the case where the app is already running, but what happens if
the app is completely closed and a protocol request is made? On **macOS**,
there's nothing extra to do; the app will launch, and the `open-url` event will
be triggered automatically.

However, for **Windows and Linux**, the behavior is different. The
`second-instance` event won't be triggered since the app is starting for the
first time. Instead, we can retrieve the custom URL from `process.argv`, as the
app will start with the protocol URL passed as one of its parameters. To handle
this case, we need to check `process.argv` when the app is started.

```js
const handleCustomUrl = url => {
  // Handle url
};

app.whenReady().then(() => {
  const customUrl = process.argv.find(item => item.startsWith("my-app://"));
  if (customUrl) {
    handleCustomUrl(customUrl);
  }
});
```

Here, when the app is ready, we look for an item in `process.argv` that starts
with our URL scheme (`my-app://`), if yes we can confirm that this instance is
started when a protocol request is received.

Great! Here's the complete solution that works across all platforms, whether the
app is already running or not.

```js
if (process.defaultApp) {
  if (process.argv.length >= 2) {
    app.setAsDefaultProtocolClient("my-app", process.execPath, [
      path.resolve(process.argv[1]),
    ]);
  }
} else {
  app.setAsDefaultProtocolClient("my-app");
}

const handleCustomUrl = url => {
  // Handle url
};

app.on("open-url", (event, url) => {
  handleCustomUrl(url);
});

const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
  app.quit();
} else {
  app.on("second-instance", (event, commands, workingDir) => {
    handleCustomUrl(commands.pop());
  });
}

app.whenReady().then(() => {
  const customUrl = process.argv.find(item => item.startsWith("my-app://"));
  if (customUrl) {
    handleCustomUrl(customUrl);
  }
});
```

## Packaging

On macOS and Linux, this feature is only functional when our app is packaged; it
won't work during development when launching from the command line. To ensure
proper functionality, we must update the macOS `Info.plist` file and the Linux
`.desktop` file to include the new protocol handler when packaging our app. This
allows the operating system to recognize and handle the custom URLs correctly.

`electron-builder` handles this internally when packaging the app; We just need
to configure the `electron-builder` accordingly. To learn more about how to
package our app using `electron-builder`, check out
[this blog](https://www.bigbinary.com/blog/publish-electron-application).

```json {4-9}
"build": {
    "productName": "NeetoRecord",
    "appId": "com.neeto.neetoRecord",
    "protocols": {
      "name": "my-app-protocol",
      "schemes": [
        "my-app"
      ]
    },
    "win": {...},
    "linux": {...},
    "mac": {...}
}

```

We can use the `protocols` field for that, give a name, then pass an array of
URL schemes the app supports, and the rest `electron-builder` will handle it.

## Links

- [Human page](https://www.bigbinary.com/blog/deep-link-electron-app)
