diff --git a/bin/meson.build b/bin/meson.build
index 8604b408549ae78432cd1add5e49d14255049c71..d0d1975479c3430cf7e72ca3a5fd81be96de28d9 100644
--- a/bin/meson.build
+++ b/bin/meson.build
@@ -61,12 +61,23 @@ executable(
   install_rpath : bin_rpath,
 )
 
+executable(
+  'steam-runtime-urlopen',
+  'urlopen.c',
+  dependencies : [glib, gio_unix, libglnx_dep, libsteamrt_static_dep],
+  install : true,
+  # Use GLib from the adjacent libdir, ignoring LD_LIBRARY_PATH
+  build_rpath : bin_rpath,
+  install_rpath : bin_rpath,
+)
+
 if get_option('man')
   foreach bin_name : [
     'check-requirements',
     'input-monitor',
     'steam-remote',
     'system-info',
+    'urlopen',
   ]
     custom_target(
       'steam-runtime-' + bin_name + '.1',
diff --git a/bin/urlopen.c b/bin/urlopen.c
new file mode 100644
index 0000000000000000000000000000000000000000..f9297d2557c538324e7fd7e086d450eecd89bf9b
--- /dev/null
+++ b/bin/urlopen.c
@@ -0,0 +1,217 @@
+/*
+ * Copyright © 2017 Red Hat, Inc
+ * Copyright © 2021 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Florian Müllner <fmuellner@gnome.org>
+ *       Matthias Clasen <mclasen@redhat.com>
+ */
+
+/*
+ * Alternative executable to the canonical 'xdg-open' with a better handling
+ * of Steam's URLs.
+ * Loosely based on the xdg-open implementation of flatpak-xdg-utils.
+ */
+
+#include <libglnx.h>
+
+#include "steam-runtime-tools/glib-backports-internal.h"
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gunixfdlist.h>
+
+#include <steam-runtime-tools/utils-internal.h>
+
+typedef GUnixFDList AutoUnixFDList;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoUnixFDList, g_object_unref)
+
+static gchar **uris = NULL;
+static gboolean opt_print_help = FALSE;
+static gboolean opt_print_version = FALSE;
+
+static const GOptionEntry option_entries[] =
+{
+  /* Imitate the allowed options of the real 'xdg-open' */
+  { "manual", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_print_help,
+    NULL, NULL },
+  { "version", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_print_version,
+    "Print version number and exit", NULL },
+  { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY,
+    &uris, NULL, NULL },
+  { NULL }
+};
+
+static gboolean
+open_with_portal (const char *uri_or_filename,
+                  GError **error)
+{
+  g_autoptr(GDBusConnection) connection = NULL;
+
+  connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+
+  if (connection != NULL)
+    {
+      const gchar *portal_bus_name = "org.freedesktop.portal.Desktop";
+      const gchar *portal_object_path = "/org/freedesktop/portal/desktop";
+      const gchar *portal_iface_name = "org.freedesktop.portal.OpenURI";
+      const gchar *method_name = NULL;
+      GVariant *arguments = NULL;   /* floating */
+      GVariantBuilder opt_builder;
+      g_autoptr(AutoUnixFDList) fd_list = NULL;
+      g_autoptr(GFile) file = NULL;
+      g_autoptr(GVariant) result = NULL;
+
+      g_debug ("Trying the D-Bus desktop portal");
+
+      g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
+      file = g_file_new_for_commandline_arg (uri_or_filename);
+
+      if (g_file_is_native (file))
+        {
+          /* The canonical 'xdg-open' also handles paths. We try to replicate
+           * that too, but it might not always work because the container
+           * inside and outside filesystem structure might be different. */
+          g_autofree gchar *path = NULL;
+          int fd;
+
+          path = g_file_get_path (file);
+          fd = open (path, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY);
+          if (fd < 0)
+            {
+              return glnx_throw_errno_prefix (error, "Failed to open '%s'", path);
+            }
+
+          fd_list = g_unix_fd_list_new_from_array (&fd, 1); /* adopts the fd */
+
+          arguments = g_variant_new ("(sh@a{sv})",
+                                     "", 0,
+                                     g_variant_builder_end (&opt_builder));
+          method_name = "OpenFile";
+        }
+      else
+        {
+          arguments = g_variant_new ("(ss@a{sv})",
+                                     "", uri_or_filename,
+                                     g_variant_builder_end (&opt_builder));
+          method_name = "OpenURI";
+        }
+
+      result = g_dbus_connection_call_with_unix_fd_list_sync (connection,
+                                                              portal_bus_name,
+                                                              portal_object_path,
+                                                              portal_iface_name,
+                                                              method_name,
+                                                              /* sinks floating reference */
+                                                              g_steal_pointer (&arguments),
+                                                              NULL,
+                                                              G_DBUS_CALL_FLAGS_NONE,
+                                                              -1,
+                                                              fd_list,
+                                                              NULL,
+                                                              NULL,
+                                                              error);
+
+      if (result == NULL)
+        return glnx_prefix_error (error,
+                                  "Unable to open URL with xdg-desktop-portal");
+      else
+        return TRUE;
+    }
+  else
+    {
+      return glnx_prefix_error (error,
+                                "Unable to connect to D-Bus session bus");
+    }
+}
+
+int
+main (int argc,
+      char **argv)
+{
+  const gchar *uri;
+  g_autofree gchar *scheme = NULL;
+  g_autoptr(GOptionContext) option_context = NULL;
+  g_autoptr(GError) portal_error = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_set_prgname ("steam-runtime-urlopen");
+
+  option_context = g_option_context_new ("{ file | URL }");
+  g_option_context_add_main_entries (option_context, option_entries, NULL);
+
+  if (!g_option_context_parse (option_context, &argc, &argv, &error))
+    {
+      g_printerr ("%s: %s\n", g_get_prgname (), error->message);
+      return 1;
+    }
+
+  if (opt_print_version)
+    {
+      /* Simply print the version number, similarly to the real xdg-open */
+      g_print ("%s\n", VERSION);
+      return EXIT_SUCCESS;
+    }
+
+  if (uris == NULL || g_strv_length (uris) > 1)
+    {
+      g_autofree gchar *help = g_option_context_get_help (option_context, TRUE, NULL);
+      g_print ("%s\n", help);
+      return 1;
+    }
+
+  /* In reality this could also be a path, but we call it "uri" for simplicity */
+  uri = uris[0];
+
+  scheme = g_uri_parse_scheme (uri);
+
+  if (scheme != NULL && (g_ascii_strcasecmp (scheme, "steamlink") == 0
+                         || g_ascii_strcasecmp (scheme, "steam") == 0))
+    {
+      g_debug ("Passing the URL '%s' to the Steam pipe", uri);
+      if (!_srt_steam_command_via_pipe (&uri, 1, &error))
+        {
+          g_printerr ("%s: %s\n", g_get_prgname (), error->message);
+          return 4;
+        }
+
+      return EXIT_SUCCESS;
+    }
+
+  if (open_with_portal (uri, &portal_error))
+    return EXIT_SUCCESS;
+
+  if (scheme != NULL && (g_ascii_strcasecmp (scheme, "http") == 0
+                         || g_ascii_strcasecmp (scheme, "https") == 0))
+    {
+      g_autofree gchar *steam_url = NULL;
+      steam_url = g_strjoin ("/", "steam://openurl", uri, NULL);
+
+      g_debug ("Passing the URL '%s' to the Steam pipe", steam_url);
+      if (!_srt_steam_command_via_pipe ((const gchar **) &steam_url, 1, &error))
+        {
+          g_printerr ("%s: %s\n", g_get_prgname (), error->message);
+          return 4;
+        }
+
+      return EXIT_SUCCESS;
+    }
+
+  g_printerr ("%s: %s\n", g_get_prgname (), portal_error->message);
+  return 4;
+}
diff --git a/bin/urlopen.md b/bin/urlopen.md
new file mode 100644
index 0000000000000000000000000000000000000000..3cf8ab92317d731aa3904627f64e3331db7abe5e
--- /dev/null
+++ b/bin/urlopen.md
@@ -0,0 +1,44 @@
+---
+title: steam-runtime-urlopen
+section: 1
+...
+
+# NAME
+
+steam-runtime-urlopen - Alternative xdg-open executable
+
+# SYNOPSIS
+
+**steam-runtime-urlopen** [*OPTIONS*]... {*file* | *URL*}
+
+# DESCRIPTION
+
+**steam-runtime-urlopen** is an alternative xdg-open executable that provides
+better handling for Steam URLs. This tool is expected to be executed from
+inside a Steam Runtime container.
+
+# OPTIONS
+
+**--version**
+:   Instead of opening the file/URL, write in output the version number.
+
+# OUTPUT
+
+On success, the output will be empty.
+
+On error, a human-readable message is shown on standard error.
+
+# EXIT STATUS
+
+The exit status is intended to be the same as for **xdg-open**(1):
+
+0
+:   Success.
+
+1
+:   Error in command line syntax.
+
+4
+:   The action failed.
+
+<!-- vim:set sw=4 sts=4 et: -->
diff --git a/debian/steam-runtime-tools-bin.install b/debian/steam-runtime-tools-bin.install
index 6a6434d18d43c22e0612150172a254dac2430d44..173b84854220c33a3597e955353e2c075c233231 100644
--- a/debian/steam-runtime-tools-bin.install
+++ b/debian/steam-runtime-tools-bin.install
@@ -1,5 +1,2 @@
-usr/bin/steam-runtime-check-requirements
-usr/bin/steam-runtime-input-monitor
-usr/bin/steam-runtime-steam-remote
-usr/bin/steam-runtime-system-info
+usr/bin/steam-runtime-*
 usr/share/man/man1/steam-runtime-*