Electron: How do I open a url from <a> on default OS browser?

Created on 1 Apr 2015  ·  26Comments  ·  Source: electron/electron

Most helpful comment

I found this code snippet on S.O.:

    var shell = require('electron').shell;
    //open links externally by default
    $(document).on('click', 'a[href^="http"]', function(event) {
        event.preventDefault();
        shell.openExternal(this.href);
    });

Dropped it in my main index file, it seems to be working as far as I can tell, even for dynamically generated links. I'm too noob at electron to know if there are any drawbacks to this I should watch out for. Thoughts?

All 26 comments

On my setup, I tried both shell.openExternal('http://example.com') and shell.openItem('http://example.com') and both opened the website in the background.

I ended up using child_process.execSync('start http://example.com') on Win32 and child_process.execSync('open http://example.com') on Darwin so the browser actually pops up and gets focus.

On Linux, you can use xdg-open:
child_process.execSync('xdg-open http://example.com')

I have actually decided to use node-open. You should give it a try if you don't want to mess with escaping and such.

Guys, but your solutions are great, but I'm wondering how to intercept attempt to open url like "http://someurl.com" and then open in with shell.openExternal? Service worker allows to catch only requests to the files on the same domain. Is there ability to do this? /cc @maxogden @AlicanC

Same question here as @havenchyk, is there a way to tell electron to open links external by default?

If your app only uses one window, and you can guarantee that every external link in your app opens in a new window (via eg. target="_blank"), you can do something like:

webContents.on('new-window', function(event, url){
  event.preventDefault();
  open(url);
});

Where webContents is your main BrowserWindow's webContents and open is a function that opens the url in your browser (I use node-open as recommended by AlicanC).

It'd be nice if there was an event fired when _any_ link is clicked, so the app could decide if it should open in the browser, but I haven't found such an event if it exists.

I found this code snippet on S.O.:

    var shell = require('electron').shell;
    //open links externally by default
    $(document).on('click', 'a[href^="http"]', function(event) {
        event.preventDefault();
        shell.openExternal(this.href);
    });

Dropped it in my main index file, it seems to be working as far as I can tell, even for dynamically generated links. I'm too noob at electron to know if there are any drawbacks to this I should watch out for. Thoughts?

I'm using this piece of code:

var handleRedirect = (e, url) => {
  if(url != webContents.getURL()) {
    e.preventDefault()
    require('electron').shell.openExternal(url)
  }
}

webContents.on('will-navigate', handleRedirect)
webContents.on('new-window', handleRedirect)

First declare a spawn proccess

`app.controller('GuideCtrl', ['$scope', ($scope)=>{
const spawn = require('child_process').spawn;

  $scope.openBrowser=(url)=>{
     let exec = spawn('explore', [url], {});
     exec.stdout.on('data', (data)=> {
        console.log('stdout: ' + data)
     });
  }

}])`

and before call the method

<a ng-click="openBrowser('https://google.com')">Goto google</a>

Does shell.openExternal have any security issues? For example if links come off the net then raw net data is being passed to shell.openExternal or any of the other functions above. Does shell.openExternal make sure nothing bad's going to happen? Do I need to filter for schemas?

Base on the code from @rubencodes , i used :

const shell = require('electron').shell; $('.open-in-browser').click((event) => { event.preventDefault(); shell.openExternal(event.target.href); });

Then you just have to drop the 'open-in-browser' class to each elements you want to open in the browser.

Here's one that doesn't require JQuery, in case anyone else is hunting for it. It will automatically open any link that starts with 'http' in the external browser.

Put this in your renderer process:

// Open all links in external browser
let shell = require('electron').shell
document.addEventListener('click', function (event) {
  if (event.target.tagName === 'A' && event.target.href.startsWith('http')) {
    event.preventDefault()
    shell.openExternal(event.target.href)
  }
})

If you want ALL <a> tags to open in the default browser, try this in your main.ts:

const shell = require('electron').shell;

mainWin.webContents.on('will-navigate', (event, url) => {
  event.preventDefault()
  shell.openExternal(url)
});

This assumes your have a single page app like me. If not, you'll need to do some extra sanitizing.

I just want to reiterate that using this with user content without a whitelist is probably a gaping security hole. I don't know what all various schemes do and what their inputs are but just as a simple example a link like this

 <a href="imessage:hello">click me</a>

Will prompt the user in Chrome and Safari on macOS but with the code above Electron will just open the app.

It almost feels like Electron itself should default to being more secure here rather than leave it to every individual programmer to figure out how to make it secure on their own.

At a minimum you probably want something like

function isSafeishURL(url) {
  return url.startsWith('http:') || url.startsWith('https:');
}

mainWin.webContents.on('will-navigate', (event, url) => {
  event.preventDefault();
  if (isSafeishURL(url)) {
    shell.openExternal(url);
  }
});

@greggman See the open external permission type in https://electronjs.org/docs/api/session#sessetpermissionrequesthandlerhandler

You can block those 👍

Hi, I am using vue.js and solve this problem inspired by above discussion, in case some one like me using vue also have the same problem, I paste my code here.

<template>
<div class="board-item" v-for="element in list" :key="element.id">
<span><a class='board-item-a' :href='element.url' target='_blank'>{{element.title}}</a></span>
</div>
</template>

<script>
mounted () {
this.$el.querySelectorAll('.board-item-a').forEach(a => {
a.addEventListener('click', (e) => {
e.preventDefault()
require('electron').shell.openExternal(e.target.href)
})
})
},
</script>

Based on comment of @alangrainger - ts version:

const {app, shell, BrowserWindow} = require('electron');

...
mainWindow.webContents.on('new-window', function(event, url){
   event.preventDefault();
   shell.openExternal(url);
});

What about data URLs?

Angular 7 version (with live reloads):

        const openExternalLinksInOSBrowser = (event, url) => {
            if (url.match(/.*localhost.*/gi) === null && (url.startsWith('http:') || url.startsWith('https:'))) {
                event.preventDefault();
                shell.openExternal(url);
            }
        };
        win.webContents.on('new-window', openExternalLinksInOSBrowser);
        win.webContents.on('will-navigate', openExternalLinksInOSBrowser);

The url.match(/.*localhost.*/gi) === null part is necessary because otherwise when you change something in your angular application it will open new window/tab in your OS browser instead of reloading it in the electron app.

All the methods work fine but only on non root electron app, what can I use to open a external url on default OS browser on a root elevated process?

Just wanted to post an answer for other Vue.js users.

<template>
  <div>
    <a href="https://google.com" target="_blank" @click.prevent="openExternalBrowser">Google.com Status</a>
  </div>
</template>

<script>
const { remote } = require('electron');

export default {
  name: 'HelloWorld',
  methods: {
    openExternalBrowser(e) {
      remote.shell.openExternal(e.target.href);
    },
  },
};
</script>

Just wanted to post an answer for other Vue.js users.

<template>
  <div>
    <a href="https://google.com" target="_blank" @click.prevent="openExternalBrowser">Google.com Status</a>
  </div>
</template>

<script>
const { remote } = require('electron');

export default {
  name: 'HelloWorld',
  methods: {
    openExternalBrowser(e) {
      remote.shell.openExternal(e.target.href);
    },
  },
};
</script>

Thank you.

On main.js:

app.on('web-contents-created', (e, contents) => {
    contents.on('new-window', (e, url) => {
      e.preventDefault();
      require('open')(url);
    });
    contents.on('will-navigate', (e, url) => {
      if (url !== contents.getURL()) e.preventDefault(), require('open')(url);
    });
});

You need to install open package:

npm i open --save

Just wanted to post an answer for other Vue.js users.

@travis5491811 This opens up another Electron window with the right page. Is this the expected behavior?

Was this page helpful?
0 / 5 - 0 ratings