DEV Community

Cover image for Hands on Web Share API

Hands on Web Share API

Hello world, welcome to my first post!
In this one, I will explain what the Web Share API is and what you can do with it. If you didn't work it yet, maybe you give it a try in your current or next project.

What is the Web Share API?

Let's say, you want to share some page specific data from your website to some social media platforms and maybe messengers.
The Web Share API give web developers the power to use the native share mechanisms, that we all know from native applications (e.g. if you click the share button in Safari on the bottom center). It's one of these cool new APIs, that give the web more capability and push the keyword "Progressive Web App" a little more. If you haven't got any idea what the hell I'm talking about, here is a picture for you:

Web Share API - native dialog on iOS after clicking the buttonWeb Share API - native dialog on iOS after clicking the button


Why should I use the Web Share API?

You may ask yourself why you should use this API, because you already have enough share possibilities like WhatsApp, Facebook, Twitter, LinkedIn etc. within your application. That's fine, but think about the Web Share API as the cool new kid on the block, which also makes it much easier for you to implement these share possibilities.

Without the Web Share API

In this case, we should have to provide a link/button for every social media/messenger platform. That means, we have to take care of each link separately. That also implies to maintain these links, because they could change in the future.

With the Web Share API

In this case, we will just have one button to click on. After clicking this button, the native dialog will be shown. One advantage of this native feature is, that it is known by the users. Another advantage (I think a bigger one) is, that if there is a new social media platform or native share feature (in the OS) it's directly implemented! All done by the native mechanism! Yeah! 🎉


How can I use the Web Share API?

Like any other new cool Browser API out there, it's asynchronous. That means we have to work with Promises (if you are not familiar with Promises, I'm sure you will find a good article out there). In this case, we will call our navigator.share() function, which will return a promise:

const sharePromise = navigator.share(shareData);
Enter fullscreen mode Exit fullscreen mode

Okay, maybe we need some more context to have a good example.

Let's start with the share() function, that will take the data object (shareData) as a parameter. The result of this call, will be the native share dialog with some share targets, depending on the data that was thrown in. A share target is a possible option, that is displayed to the user in the dialog. This could be a contact (via WhatsApp, Telegram etc.), native applications or built-in service (e.g. "Copy to clipboard"). To make it clear here, you can't decide which share targets should be shown to the user, they were provided by the user agent.

So let's start with the most secret part ... the shareData. Honestly, it's just an object that can contain the following members:

  • title
  • text
  • url
  • files

In the future there could be some more members, but this is the current state. It's important that not all data members have to be provided, but at least one member should be inside.

The user agent will take care of the shareData and converts these in a suitable format for the different targets. This could include merging or discarding some members. But this magic is done behind the scene, we can just take a drink and enjoy the beautiful interface. 🍹
The share targets that you will see on the native dialog depends on the shareData payload. Anyway, let's dive into the different members of the shareData object.

Title member

This member is a string and contains the title of the document that is shared. In my tests, I couldn't find it anyway. So in most cases, this member is not being displayed or used on most targets.

Url member

The url member is just a simple pure string URL that refers to a resource that should be shared. This could be an absolute or relative URL. If you provide a relative URL it will automatically update the url like a href attribute.

Pro Tip: If you just provide an empty string as url, then it will automatically reffers to the current page.

Text member

The text member is also a string option, that allows you to provide a body of the message for the shared data. This member is often use by the share targets (e.g. email body text).

Everything in action

You might think, why didn't we cover the files member. Please keep cool and take a breath, we will cover it in a couple of minutes. But I think now it's time for some code. Finally 🎉

Let's say we have a fancy button on our page, where we just want to share some data related to the current page. The JS code would look something like this:

// our share button in the markup
const shareButton = document.getElementById('share');

// create share data object
const shareData = {
  title: 'Web Share API',
  text: 'The best way to share your data.',
  url: '' // reffers to the current page
};

// click event handler is necessary to call navigator.share()
shareButton.addEventListener('click', async () => {
  // does the browser supports the feature?
  if(navigator.share) {
    try {
      await navigator.share(shareData);
      console.log('🥳 Shared via API.');
    } catch(error) {
      console.log(`😢 ${error}`);
    }
  } else {
    // you could do a fallback solution here ... 
    console.log('😢 Your browser does not support the web share api.')
  }
})
Enter fullscreen mode Exit fullscreen mode

Now let's go through the code step by step. At first, we create an object and add some members to it, in this case title, text and url. Then we add an event listener to our shareButton. In the event handler, we check for navigator.share, because we want to check if the browser supports the Web Share API. If not, it will return undefined and the else branch will be executed. There could be a fallback solution for all browsers that are not supporting this functionality. At least this would be the best, if we think about progressive enhancement. But in this post we want to concentrate on the if branch.

First, we will open the try block and call the navigator.share(shareData) inside. Now the native dialog will open up with all possible share targets. With await we will wait until the promise is fulfilled. That means, until the user cancel the sharing or chose a share target. On cancel, the code above will run the catch block. If the user finally shares the data via a share target, then the promise will be resolved. Then we successfully shared some data with the Web Share API 🎉

ℹ️ Important note: Even if the sharing was successful, we didn't get any detailed information. The promise itself will just return undefined. There is no way to get the chosen share target of the user. I know that's a bit disappointing, but there is a good reason.

There is a requirement to not allow the website to learn which apps are installed, or which app was chosen from share(), because this information could be used for fingerprinting, as well as leaking details about the user's device. https://www.w3.org/TR/web-share/

Files member

Now it's time to talk about the files member. This one contains all shared files as an array. By adding this member to you shareData, we have to handle binary data and the code gets a little more complex. But don't be afraid, we will go through it together.

So time for some imagination 💭 again...
Imagine you want to share a super cool looking image, if the user clicks on our share button. We need to load the file like this:

const image = await fetch('./super-cool-looking.jpeg');
const blob = await image.blob();
const file = new File([blob], 'image.jpg', { type: 'image/jpeg' });
const filesArray = [file];

// add it to the shareData
shareData.files = filesArray;
Enter fullscreen mode Exit fullscreen mode

In these lines of code we fetch the image, convert the raw data to BLOB (binary large object) and create a new File with the File interface. Then we just put it into an array. Of course, it's possible for you to add their more than one file.
In the end, we just add the files property to the shareData object and initialize it with the filesArray.

Now you can share images via the Web Share API, but be aware that this feature is not supported in all browsers. There is also an opportunity to check, if file sharing is possible in the browser. You can use the navigator.canShare() function for this. It could look like this for our example:

if(navigator.canShare && navigator.canShare({files: filesArray})) {
  await navigator.share(shareData);
} else {
  console.log('File sharing is not supported.');
}
Enter fullscreen mode Exit fullscreen mode

The function has one parameter like the share() function and will return true or false, if the browser can share the object (shareData) you put in.
But there is a big disadvantage for this approach, because this functionality is experimental and is not supported everywhere. Sadly, file sharing isn't that easy to handle on every device and browser. But as always, do it progressive (like above) and support modern browsers.


When to use?

One important note, the navigator.share() function will just work on a user interaction (e.g. click event handler). That means, you can not run the code on page load. Which is good, because otherwise we would have the next stupid thing like cookie banners or push notification permission request.


Which Browsers support the Web Share API?

I don't want to talk about it in detail, because it could be outdated. So here is the link to Web Share API support.


Conclusion

As shown above, the modern way of sharing data in JS makes the code quiet easy and understandable. Also it's easy to maintain the code and we get the native share mechanism, which will also support future social platforms automatically. I'm a real fan of this approach and would recommend to give it a try in your next project. If you already made some experiences with this API, please share your thoughts in the comment section below. 👇


My sources

Discussion (10)

Collapse
mojitane profile image
Maximilian Henrich

Do you know any best practices for length of shared text or the size of shared images – or if they are different for different devices? Maybe I can find a preview generator for this to test.

Collapse
fezi32 profile image
Felix Jordan Author • Edited

Hey again, what also comes in my mind (regarding the question of a preview generator) is that you couldn‘t have any global preview generator, that works for every share taget. What you could do is a preview generator for one or multiple share targets, but a global preview generator is not possible. Because every share target (application) behaves different.

Collapse
fezi32 profile image
Felix Jordan Author

Good question! I couldn't find any limitations rather for length of shared text or size of shared images. But I think there are no real limitations out there. Maybe the share targets have the possibility to reject large files or long texts. This would mean that they will not show up in the dialog (maybe testing twitter with a long text would be interesting). But I think that there are no limitations.
But there could be the case, that maybe the share target will do something special with the text (truncating or whatever) or reduce the file size by reduce the quality.

I don't think that it is possible to find a preview generator. Anyways it couldn't be 100% correct, because the user agent/device has the power. But if you find one, let me know (I would be interested as well) :)

Collapse
creativemacmac profile image
creativemacmac

Super useful❤️

Collapse
fezi32 profile image
Felix Jordan Author

Thank you 😊

Collapse
maxprogramming profile image
Max Programming

This was super useful!

Collapse
fezi32 profile image
Felix Jordan Author

Thanks ☺️

Collapse
ryanguitar profile image
Ryan Els

Excellent. Works well. Thanks for sharing.

Collapse
fezi32 profile image
Felix Jordan Author

Thanks Ryan :)

Some comments have been hidden by the post's author - find out more