{"id":3136,"date":"2024-07-31T09:22:50","date_gmt":"2024-07-31T14:22:50","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=3136"},"modified":"2024-08-23T11:58:40","modified_gmt":"2024-08-23T16:58:40","slug":"reading-from-the-clipboard-in-javascript","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/reading-from-the-clipboard-in-javascript\/","title":{"rendered":"Reading from the Clipboard in JavaScript"},"content":{"rendered":"\n<p>Browsers have excellent support for reading and writing the user&#8217;s clipboard, and this opens up possibilities for better, and more <em>native like<\/em> experiences on the web. On websites that use these APIs for helpful features, it feels natural to the user. On sites where it&nbsp;<em>isn&#8217;t<\/em>&nbsp;supported, it almost feels like a bug. In this series of articles, I&#8217;m going to demonstrate how to work with the clipboard.<\/p>\n\n\n<div class=\"box article-series\">\n  <header>\n    <h3 class=\"article-series-header\">Article Series<\/h3>\n  <\/header>\n  <div class=\"box-content\">\n            <ol>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/reading-from-the-clipboard-in-javascript\/\">Reading from the Clipboard in JavaScript<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/writing-to-the-clipboard-in-javascript\/\">Writing to the Clipboard in JavaScript<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/handling-paste-events-in-javascript\/\">Handling Paste Events in JavaScript<\/a>\n            <\/li>\n                  <\/ol>\n        <\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"before-we-begin\">Before We Begin&#8230;<\/h2>\n\n\n\n<p>Clipboard functionality on the web requires a &#8220;secure context&#8221;. So if you&#8217;re running an&nbsp;<code>http<\/code>&nbsp;site (as opposed to an&nbsp;<code>https<\/code>&nbsp;site), these features will not work. I\u2019d highly encourage you to get your site on&nbsp;<code>https<\/code>. That being said, these features, and others like them that require secure contexts,&nbsp;<em>will<\/em>&nbsp;still work on&nbsp;<code>http:\/\/localhost<\/code>. There&#8217;s no need to set up a temporary certificate when doing local testing.<\/p>\n\n\n\n<p>Also note that some browsers will <em>manipulate<\/em> the content of the clipboard when read. This is done for security reasons and you&nbsp;<em>can<\/em>&nbsp;disable it. I&#8217;ll demonstrate this later in the article.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-clipboard-api\">The Clipboard API<\/h2>\n\n\n\n<p>The&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Clipboard\">Clipboard API<\/a>&nbsp;is the top-level object (<code>navigator.clipboard<\/code>) containing the methods to work with the clipboard. According to MDN, support is pretty much across the board:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"788\" height=\"975\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721580997100_shot1.png?resize=788%2C975&#038;ssl=1\" alt=\"\" class=\"wp-image-3159\" style=\"width:531px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721580997100_shot1.png?w=788&amp;ssl=1 788w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721580997100_shot1.png?resize=242%2C300&amp;ssl=1 242w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721580997100_shot1.png?resize=768%2C950&amp;ssl=1 768w\" sizes=\"auto, (max-width: 788px) 100vw, 788px\" \/><\/figure>\n<\/div>\n\n\n<p>In fact, the only outlier relates to being able to disable the security aspects mentioned above in Firefox and Safari. Outside of that, support is great.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Reading the Clipboard<\/h3>\n\n\n\n<p>Reading from the clipboard is handled by two methods:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>read<\/code><\/li>\n\n\n\n<li><code>readText<\/code><\/li>\n<\/ul>\n\n\n\n<p>Which you use will depend on your particular use case. In theory,&nbsp;<code>read<\/code>&nbsp;would be the most flexible, handling <em>any<\/em> content, but if you know for a fact you only need to support text input from the clipboard, you should probably use the more specific&nbsp;<code>readText<\/code>&nbsp;format. I&#8217;m a fan of code that helps reduce my chance of mistakes so that&#8217;s what I&#8217;d recommend. Let&#8217;s start with an example of that.<\/p>\n\n\n\n<p>First, I&#8217;ll use a bit of HTML:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"readCB\"<\/span>&gt;<\/span>Read Clipboard<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"log\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The button will be used to kick off the code to read from the clipboard and I&#8217;ll use the&nbsp;<code>&lt;div&gt;<\/code>&nbsp;to show the results. Now the JavaScript:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> $log = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">'#log'<\/span>);\n\n<span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">'#readCB'<\/span>).addEventListener(<span class=\"hljs-string\">'click'<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  <span class=\"hljs-keyword\">let<\/span> contents = <span class=\"hljs-keyword\">await<\/span> navigator.clipboard.readText();\n  <span class=\"hljs-built_in\">console<\/span>.log(contents);\n  $log.innerText += <span class=\"hljs-string\">`From clipboard: <span class=\"hljs-subst\">${contents}<\/span>`<\/span>;\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When the button is clicked, I try to read text from the clip. Note I said&nbsp;<em>try<\/em>. The first time the button is clicked, the browser prompts for permission:<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"461\" height=\"178\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721581019360_shot2.png?resize=461%2C178&#038;ssl=1\" alt=\"\" class=\"wp-image-3160\" style=\"width:410px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721581019360_shot2.png?w=461&amp;ssl=1 461w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721581019360_shot2.png?resize=300%2C116&amp;ssl=1 300w\" sizes=\"auto, (max-width: 461px) 100vw, 461px\" \/><figcaption class=\"wp-element-caption\">Chrome<\/figcaption><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"588\" height=\"582\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot-2024-07-25-at-12.50.37%E2%80%AFPM.png?resize=588%2C582&#038;ssl=1\" alt=\"\" class=\"wp-image-3172\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot-2024-07-25-at-12.50.37%E2%80%AFPM.png?w=588&amp;ssl=1 588w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/Screenshot-2024-07-25-at-12.50.37%E2%80%AFPM.png?resize=300%2C297&amp;ssl=1 300w\" sizes=\"auto, (max-width: 588px) 100vw, 588px\" \/><figcaption class=\"wp-element-caption\">Arc<\/figcaption><\/figure>\n<\/div>\n<\/div>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>If you need to revoke or change permissions&#8230;<\/summary>\n<p>There should be some UI in the browser bar area to do this. On Chrome it&#8217;s the little control icon which opens this:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"898\" height=\"792\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-29-at-09.23.27%402x.png?resize=898%2C792&#038;ssl=1\" alt=\"Chrome controls panel for a website showing secure connection, and also the Clipboard access which can be turned off or reset. \" class=\"wp-image-3274\" style=\"width:443px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-29-at-09.23.27%402x.png?w=898&amp;ssl=1 898w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-29-at-09.23.27%402x.png?resize=300%2C265&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/CleanShot-2024-07-29-at-09.23.27%402x.png?resize=768%2C677&amp;ssl=1 768w\" sizes=\"auto, (max-width: 898px) 100vw, 898px\" \/><\/figure>\n<\/div><\/details>\n\n\n\n<p>The process of reading from the clipboard is async, so I&#8217;m using&nbsp;<code>await<\/code>&nbsp;to make that a bit simpler. Then, I log the contents to the console and write it out to the DOM.<\/p>\n\n\n\n<p>Here&#8217;s a live demo, but<strong> <em>please note that it may not work for you<\/em><\/strong>, because of the permissions prompts mentioned above. <a href=\"https:\/\/cdpn.io\/pen\/debug\/OJeXgOL\">You can view this demo here<\/a> and that will allow you to approve the permissions to make it work. It&#8217;s tricky with <code>&lt;iframe&gt;<\/code> demos \u2014 both domains need the permissions approved, but won&#8217;t prompt for them, and even then it doesn&#8217;t always want to work.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_OJeXgOL\" src=\"\/\/codepen.io\/anon\/embed\/OJeXgOL?height=450&amp;theme-id=47434&amp;slug-hash=OJeXgOL&amp;default-tab=js,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed OJeXgOL\" title=\"CodePen Embed OJeXgOL\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">How well does it work?<\/h3>\n\n\n\n<p>If you have nothing on the clipboard, the code runs just fine, but the contents are an empty string. It was actually a bit difficult to figure out&nbsp;<em>how<\/em>&nbsp;to empty my clipboard. I initially tested by putting my cursor on an empty line in my editor and hitting CTRL+C, but that felt like a hack. This&nbsp;<a href=\"https:\/\/www.windowscentral.com\/how-clear-clipboard-data-shortcut-windows-10\">random article<\/a>&nbsp;showed a command line prompt I could run on Windows that seemed to be more &#8220;official&#8221;. I used the suggested command and got the exact same result. No error, just an empty string.<\/p>\n\n\n\n<p>Copying text works as expected, but note that if you copy HTML, and insert it into the DOM, it will be rendered as HTML. In my case, I&#8217;m using <code>innerText<\/code> so that&#8217;s not an issue.<\/p>\n\n\n\n<p>Next, I tested &#8220;rendered&#8221; text, and by that I mean random text from a web page, ensuring I got styled text as well. As expected, I got just the text of the selection from the HTML. So for example:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"856\" height=\"301\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721581031893_shot3.png?resize=856%2C301&#038;ssl=1\" alt=\"\" class=\"wp-image-3161\" style=\"width:776px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721581031893_shot3.png?w=856&amp;ssl=1 856w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721581031893_shot3.png?resize=300%2C105&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721581031893_shot3.png?resize=768%2C270&amp;ssl=1 768w\" sizes=\"auto, (max-width: 856px) 100vw, 856px\" \/><\/figure>\n<\/div>\n\n\n<p>Which ended up as:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">CSS<br>MIXINS<br>STYLE QUERIES<br>Style Queries are Almost Like Mixins (But Mixins Would Be Better)<br>By CHRIS COYIER on July 12, 2024<br>Having a named block of styles to apply in CSS can be useful, and newfangled Style Queries are pretty close to that. We look at one use case here, how Sass did mixins better, and hope for a native solution.<\/pre>\n\n\n\n<p>I tried copying from Word and PDF and got the same type of result.<\/p>\n\n\n\n<p>Cool \u2014 now let&#8217;s kick it up a notch!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"reading-multimedia-content-from-the-clipboard\">Reading Multimedia Content from the Clipboard<\/h2>\n\n\n\n<p>As explained earlier, the&nbsp;<code>read<\/code>&nbsp;method can support any content, not just text. Switching to it is as simple as:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> contents = <span class=\"hljs-keyword\">await<\/span> navigator.clipboard.read();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This method returns an array of&nbsp;<code>ClipboardItems<\/code>&nbsp;representing the fact that a user may have selected multiple different items. A&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/ClipboardItem\">ClipboardItem<\/a>&nbsp;consists of:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>types<\/code>: An array of MIME types associated with the item<\/li>\n\n\n\n<li><code>presentationStyle<\/code>: This was one of the rare times when MDN failed to be helpful and I had to actually&nbsp;<a href=\"https:\/\/w3c.github.io\/clipboard-apis\/#presentation-style\">go to the spec<\/a>. This property represents whether the pasted content should be considered as appended content or an attachment. You can imagine how an email client works where if you paste text, it just adds to the current email, but binary data is usually handled as an attachment to the mail instead. You don&#8217;t really need to worry about it as it&#8217;s only supported in Firefox. That being said, you can make up your own mind depending on the mimetype being used.<\/li>\n<\/ul>\n\n\n\n<p>To get the actual contents you need to use the&nbsp;<code>getType<\/code>&nbsp;method, which feels oddly named. It takes a mimetype as an argument and returns a blob.<\/p>\n\n\n\n<p>Now things get a bit more complex. Your application has to figure out what makes sense to do based on the data in the clipboard. Let&#8217;s consider a more advanced version of the previous demo:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">'#readCB'<\/span>).addEventListener(<span class=\"hljs-string\">'click'<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  <span class=\"hljs-keyword\">let<\/span> contents = <span class=\"hljs-keyword\">await<\/span> navigator.clipboard.read();\n\n  <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">let<\/span> item <span class=\"hljs-keyword\">of<\/span> contents) {\n\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'Types for this item: '<\/span>, item.types);\n\n    <span class=\"hljs-keyword\">if<\/span> (item.types.includes(<span class=\"hljs-string\">'text\/html'<\/span>)) {\n      <span class=\"hljs-keyword\">let<\/span> blob = <span class=\"hljs-keyword\">await<\/span> item.getType(<span class=\"hljs-string\">'text\/html'<\/span>);\n      <span class=\"hljs-keyword\">let<\/span> html = <span class=\"hljs-keyword\">await<\/span> blob.text();\n      <span class=\"hljs-built_in\">console<\/span>.log(html);\n      $log.innerHTML += html.replaceAll(<span class=\"hljs-string\">'&lt;'<\/span>,<span class=\"hljs-string\">'&amp;lt;'<\/span>).replaceAll(<span class=\"hljs-string\">'&gt;'<\/span>,<span class=\"hljs-string\">'&amp;gt;'<\/span>);\n      $log.innerHTML += <span class=\"hljs-string\">'&lt;hr&gt;'<\/span>;\n    }\n\n    <span class=\"hljs-keyword\">if<\/span> (item.types.includes(<span class=\"hljs-string\">'text\/plain'<\/span>)) {\n      <span class=\"hljs-keyword\">let<\/span> blob = <span class=\"hljs-keyword\">await<\/span> item.getType(<span class=\"hljs-string\">'text\/plain'<\/span>);\n      <span class=\"hljs-keyword\">let<\/span> text = <span class=\"hljs-keyword\">await<\/span> blob.text();\n      <span class=\"hljs-built_in\">console<\/span>.log(text);\n      $log.innerHTML += text.replaceAll(<span class=\"hljs-string\">'&lt;'<\/span>,<span class=\"hljs-string\">'&amp;lt;'<\/span>).replaceAll(<span class=\"hljs-string\">'&gt;'<\/span>,<span class=\"hljs-string\">'&amp;gt;'<\/span>);\n      $log.innerHTML += <span class=\"hljs-string\">'&lt;hr&gt;'<\/span>;\n    }\n\n    <span class=\"hljs-keyword\">if<\/span> (item.types.includes(<span class=\"hljs-string\">'image\/png'<\/span>)) {\n      <span class=\"hljs-comment\">\/\/ modified from MDN sample<\/span>\n      <span class=\"hljs-keyword\">const<\/span> pngImage = <span class=\"hljs-keyword\">new<\/span> Image(); \n      pngImage.alt = <span class=\"hljs-string\">\"PNG image from clipboard\"<\/span>;\n      <span class=\"hljs-keyword\">const<\/span> blob = <span class=\"hljs-keyword\">await<\/span> item.getType(<span class=\"hljs-string\">\"image\/png\"<\/span>);\n      pngImage.src = URL.createObjectURL(blob);\n      $log.appendChild(pngImage);\n    }\n\n  }\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Remember: each item in the array has itself an array of mimetypes. This is very important because many places where you copy code (web pages, your IDE, etc.) may return <em>both<\/em> <code>text\/plain<\/code> and <code>text\/html<\/code> for an item. That&#8217;s&#8230; good, as it gives you options in terms of what you want to do. If you want to try to keep some of the original formatting, get the HTML. If you only care about the content, get just the plain text.<\/p>\n\n\n\n<p>Here&#8217;s another live demo, this one handling multiple content types.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_ZEdpZKv\" src=\"\/\/codepen.io\/anon\/embed\/ZEdpZKv?height=450&amp;theme-id=47434&amp;slug-hash=ZEdpZKv&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed ZEdpZKv\" title=\"CodePen Embed ZEdpZKv\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Copying from a PDF<\/h3>\n\n\n\n<p>Surprisingly, copying from a PDF will only return&nbsp;<code>text\/plain<\/code>, even if you select an entire document and it has images. This makes some sense though. Check out the screenshot from Acrobat below, where I had run &#8220;Select All&#8221;:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"845\" height=\"874\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721581055620_shot4.png?resize=845%2C874&#038;ssl=1\" alt=\"\" class=\"wp-image-3163\" style=\"width:476px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721581055620_shot4.png?w=845&amp;ssl=1 845w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721581055620_shot4.png?resize=290%2C300&amp;ssl=1 290w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/s_ECBD0FD7F063D62E16E0A0225C5996F2E05166DE03602AD22C0ACF8F413689D7_1721581055620_shot4.png?resize=768%2C794&amp;ssl=1 768w\" sizes=\"auto, (max-width: 845px) 100vw, 845px\" \/><\/figure>\n<\/div>\n\n\n<p>As you can see above, the textual elements are highlighted, not the image. You can copy an image from a PDF, but you can have to click on it specifically.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"security-concerns-and-sanitization\">Security Concerns and Sanitization<\/h2>\n\n\n\n<p>The MDN docs mention this in regards to reading from the clipboard:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Certain browsers may sanitize the clipboard data when it is read, to prevent malicious content from being pasted into the document. For example, Chrome (and other Chromium-based browsers) sanitizes HTML data by stripping&nbsp;<code>&lt;script&gt;<\/code>&nbsp;tags and other potentially dangerous content. Use the unsanitized array to specify a list of MIME types that should not be sanitized.<\/p>\n<\/blockquote>\n\n\n\n<p>The way around this is to pass a&nbsp;<code>formats<\/code>&nbsp;object to the&nbsp;<code>read<\/code>&nbsp;method that specifies an array of mimetypes the browser should&nbsp;<em>not<\/em>&nbsp;sanitize. Given a clipboard with potentially dangerous content, that means you would expect a different result from<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> contents = <span class=\"hljs-keyword\">await<\/span> navigator.clipboard.read();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>versus:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> contents = <span class=\"hljs-keyword\">await<\/span> navigator.clipboard.read({ <span class=\"hljs-attr\">unsanitized<\/span>: &#91;<span class=\"hljs-string\">'text\/html'<\/span>] });<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This was fairly difficult to test if I selected code that includes the&nbsp;<code>&lt;script&gt;<\/code>&nbsp;tag, it only reported&nbsp;<code>text\/plain<\/code>&nbsp;as a mimetype. The only way I was able to verify this was in the&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Clipboard\/read#reading_unsanitized_html_from_the_clipboard\">MDN example<\/a>&nbsp;where they kinda &#8220;force&#8221; the issue by writing to the clipboard as HTML. Honestly, I can&#8217;t see a reason to use this particular feature unless more &#8216;sanitized&#8217; things turn up. I&#8217;d recommend just letting the browser clear it out if it needs to. But to be clear, if you copy code that includes the&nbsp;<code>&lt;script&gt;<\/code>&nbsp;tag, it will come in as&nbsp;<code>text\/plain<\/code>&nbsp;and be read just fine.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"caveats\">Caveats<\/h2>\n\n\n\n<p>Though a user may have a&nbsp;<strong>file<\/strong>&nbsp;from the filesystem selected, if you try to read it from the clipboard, you get an empty array of results. I found this surprising as you can paste files into the browser and support reading them. We\u2019ll get into that in the article on pasting.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"some-examples-uses\">Examples Use Cases<\/h2>\n\n\n\n<p>How about some examples to give you ideas of how you would use this in the real world?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Addresses<\/h3>\n\n\n\n<p>Sites making use of client-side maps (like Google Maps or Leaflet) could read from the clipboard and attempt to parse the contents as an address, and if found, focus the map. This will probably require the use of a Geocoding API to translate a freeform address into longitude and latitude points.<\/p>\n\n\n\n<p>Google Maps on iOS (while not technically a web app) behaves this way. If you&#8217;ve given permission, if you happen to have an address on your clipboard you&#8217;ll see it as a one-click option right up top.<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"472\" height=\"1024\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9279.png?resize=472%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-3185\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9279.png?resize=472%2C1024&amp;ssl=1 472w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9279.png?resize=138%2C300&amp;ssl=1 138w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9279.png?resize=768%2C1665&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9279.png?resize=709%2C1536&amp;ssl=1 709w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9279.png?resize=945%2C2048&amp;ssl=1 945w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9279.png?w=1290&amp;ssl=1 1290w\" sizes=\"auto, (max-width: 472px) 100vw, 472px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"472\" height=\"1024\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9280.png?resize=472%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-3186\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9280.png?resize=472%2C1024&amp;ssl=1 472w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9280.png?resize=138%2C300&amp;ssl=1 138w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9280.png?resize=768%2C1665&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9280.png?resize=709%2C1536&amp;ssl=1 709w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9280.png?resize=945%2C2048&amp;ssl=1 945w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/IMG_9280.png?w=1290&amp;ssl=1 1290w\" sizes=\"auto, (max-width: 472px) 100vw, 472px\" \/><\/figure>\n<\/div>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\">QR Codes<\/h3>\n\n\n\n<p>How about a&nbsp;<a href=\"https:\/\/www.tampermonkey.net\/\">TamperMonkey<\/a>&nbsp;script that lets you take&nbsp;<em>any<\/em>&nbsp;block of text in your clipboard and turn it into a QR code? My buddy Todd Sharp <a href=\"https:\/\/gist.github.com\/recursivecodes\/f06198266cf2adb6da75435be6e410a0\">literally built this<\/a> the day after I wrote the first draft of this article.&nbsp;His work could be used in a regular web page as well to product QR codes from the clipboard.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">URLs<\/h3>\n\n\n\n<p>If you <em>knew<\/em> the user had a URL on their clipboard, your app could offer to do something with it. Perhaps automatically add useful URL params, shorten it, or otherwise. <\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>Can you think of any other use cases for reading from the clipboard? Admittedly, <em>writing<\/em> to the clipboard is generally a more common and useful ability, and we&#8217;ll get to that next. <\/p>\n\n\n<div class=\"box article-series\">\n  <header>\n    <h3 class=\"article-series-header\">Article Series<\/h3>\n  <\/header>\n  <div class=\"box-content\">\n            <ol>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/reading-from-the-clipboard-in-javascript\/\">Reading from the Clipboard in JavaScript<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/writing-to-the-clipboard-in-javascript\/\">Writing to the Clipboard in JavaScript<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/handling-paste-events-in-javascript\/\">Handling Paste Events in JavaScript<\/a>\n            <\/li>\n                  <\/ol>\n        <\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>While it&#8217;s a bit more common to *write* to the clipboard, JavaScript can also read from it. Plain text is pretty simple, while multimedia content is a bit more complex.<\/p>\n","protected":false},"author":29,"featured_media":3189,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[217,3],"class_list":["post-3136","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-clipboard","tag-javascript"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/07\/pexels-photo-544965.jpeg?fit=1880%2C1253&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/3136","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/29"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=3136"}],"version-history":[{"count":27,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/3136\/revisions"}],"predecessor-version":[{"id":3665,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/3136\/revisions\/3665"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/3189"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=3136"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=3136"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=3136"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}