A Detailed Breakdown of the <script> Tag

By Colin Ihrig

When the <script> tag was originally introduced, it was used to add only the most basic level of interactivity to web pages. But the web has changed a lot, and the <script> tag has evolved. The proliferation of JavaScript has made it one of the most important HTML tags. This article explores the <script> tag in detail, including best practices for using it in modern web pages.

Specifying the Scripting Language

While JavaScript is the default scripting language of the web, browsers can support any number of additional languages. For example, Internet Explorer also supports a scripting language derived from Visual Basic named VBScript. In order to specify the scripting language of choice, the <script> tag’s “type” attribute is used. Technically, “type” specifies the MIME type of the script. The following example uses “type” to specify JavaScript’s MIME type, “text/javascript”.

<script type="text/javascript">
  // JavaScript code here

In older versions of HTML it was necessary to specify a value for “type”. However, starting in HTML5, “type” defaults to “text/javascript”. This decision was made because the W3C realized that JavaScript is the only universally supported scripting language. Therefore, HTML5 <script> tags can be shortened in the following fashion.

  // JavaScript code here

The “language” Attribute

There is a second attribute, “language”, which can be used to specify the scripting language. Unlike “type”, the possible values for “language” were never standardized. This attribute has been deprecated for a long time now, but some people still use it. It should not be used under any circumstances.

Inline and External Scripts

The <script> tag allows code to either be embedded directly into HTML, or included from external script files. Inline scripts are created by placing code between <script> and </script> tags in an HTML file. The following code shows an example inline <script>.

  // Inline JavaScript code here

Inline scripts are a quick and easy way to add code to web pages. However, large scripts can also lead to cluttered HTML files. The alternative approach is to include code in external script files. The external files are referenced via a URL specified by the “src” attribute. The following example shows how external script files are included. In this example, the external script file is named external.js, and is located in the same directory as the HTML file. Also, note that the closing </script> tag is still required.

<script src="external.js"></script>

XHTML Compatibility

XHTML rules are much stricter than those of HTML. When special XML characters (such as & and <) are used in scripts in XHTML files, they cause errors. The simplest workaround is to use external script files. However, if you simply must write inline scripts, then you will need to include CDATA (character data) sections in your file. Within CDATA sections the special XML characters can be used freely. The following example uses a CDATA section that is compatible with both XHTML and HTML syntax. Note that XHTML also requires the use of the “type” attribute.

<script type="text/javascript">
  alert((1 < 2) && (3 > 2));

Dynamic Script Tag Injection

Dynamic script tag injection is a technique in which new <script> elements are created at runtime. When the new <script> is added to the page, its “src” URL is automatically downloaded and executed. The following code shows an example dynamic script tag injection. In the example, a new <script> element is created using the document.createElement() function. Next, the “src” attribute is set to the URL of the script file. Finally, the new element is added to the document’s head, causing the script to be downloaded and executed.

var script = document.createElement("script");

script.setAttribute("src", url);


The <script> tag is not subject to the same origin policy. Web pages can exploit this freedom to request cross-site data. JSONP, a technique for making cross-site AJAX-like requests, makes extensive use of dynamic script tag injection. Under the JSONP model, each AJAX request is replaced by a script tag injection.

The “async” Attribute

When a <script> tag is encountered, the browser stops what it is doing and begins downloading/executing the script. This default behavior is known as synchronous blocking. During this period of blocking, the page may seem unresponsive or slow to the user. To mitigate this problem, HTML5 introduced the “async” attribute for <script> tags. “async” is a Boolean attribute which, when specified, indicates that a script should unblock the rest of the page, and execute asynchronously. According to the specification, “async” should only be used with external script files. Unfortunately, “async” is not yet supported in Opera.  The following example shows how the “async” attribute is used.

<script src="file.js" async="async"></script>

Normally, “async” defaults to false. However, when dealing with dynamically injected scripts, the default value becomes true. This can lead to problems if scripts with interdependencies are inserted at the same time. For example, assume that a page injects a third-party library originating from a slow server. At (almost) the same time, the page also injects a script that makes calls to the library. Since the scripts are asynchronous, the second file could potentially get downloaded and executed before the library is available. The solution is to preserve the program order by setting “async” to false for both scripts. This concept is illustrated in the following example. In the example, the page will wait for the first script to load before moving on to the second script.

var library = document.createElement("script");
var local = document.createElement("script");

library.async = false;
local.async = false;
library.setAttribute("src", "remote-library-code.js");
local.setAttribute("src", "local-file.js");
// use the library code

The “defer” Attribute

As previously stated, <script> tags cause the browser to block the rest of the page while the script is processed. This can lead to problems if the script references DOM elements which haven’t finished loading yet. In this scenario, the DOM-related code is typically placed in an event handler such as window load or DOMContentLoaded. Another option is to postpone execution until the document has been parsed using the “defer” attribute. Like “async”, “defer” is a Boolean attribute that should only be used with external script files. The following example shows how “defer” works. This example requires an HTML file and a separate JavaScript file named defer.js. The HTML source is shown below.

<!DOCTYPE html>
<html lang="en">
  <title>defer Example</title>
  <meta charset="UTF-8" />
  <script src="defer.js" defer="defer"></script>
  <span id="message"></span>

The code for defer.js is shown below. If the “defer” attribute were not present, this code would fail because the DOM is not ready when the <span> element is accessed.

var span = document.getElementById("message");

span.textContent = "The DOM is ready for scripting!";

It is possible to specify both “async” and “defer” on the same <script> element. This is possible so that browsers that do not support “async” can fall back on the “defer” behavior instead of synchronous blocking. According to the specification, “defer” is overridden by “async”. Unfortunately, “defer” is also unsupported in Opera.

Performance Considerations

All of the examples on this page have shown <script> tags placed in the document’s head. This can actually degrade performance because the rest of the page is blocked while the scripts are processed. Obviously, the “defer” and “async” attributes offer one solution. Unfortunately, they are not supported everywhere yet. A second option is to move <script> tags to the bottom of the <body> tag whenever possible ― this is usually fine as long as the script does not call document.write().

Using external script files instead of inline scripts is another technique that often improves performance. This leads to smaller HTML files, at the expense of creating additional HTTP requests. However, script files are often shared among multiple HTML pages, and can be cached by the browser. The overall result is a smaller HTML download without the additional server requests. Also remember that scripts can be downloaded dynamically after the page is loaded.

Things to Remember

  • The <script> tag always requires a matching </script> tag.
  • The scripting language is specified by the “type” attribute. Do not use the “language” attribute. HTML5 defaults to “text/javascript”.
  • XHTML files should use external script files or CDATA sections to allow special characters.
  • Dynamic script tag injections allow cross-site resource requests.
  • The “async” attribute causes a script to execute asynchronously from the rest of the page. It can also preserve order for dynamically inserted scripts.
  • The “defer” attribute can postpone script execution until the document is ready.
  • Move <script> tags to the end of the <body> when possible.
  • Use external script files which can be cached and shared among pages.
  • thank for share. good!

  • Good stuff here, Colin. I like articles that focus on a single topic. Easy to follow, and lots of info on a single topic.

    Couple of things to point out. You said:

    In older versions of HTML it was necessary to specify a value for “type”

    That’s not really true, and actually irrelevant. For validation purposes, yes, you needed the type attribute, but no browser ever required it. Every script tag works without “type” or “language”, in every browser (even really old ones).

    Also, in the “things to remember” section, you said:

    The scripting language is specified by the “type” attribute. However, starting in HTML5, “type” defaults to “text/javascript”.

    Again, that’s not entirely accurate. It’s not “HTML5” that does this, it’s the browser. HTML5 is the syntax you use, and the browser interprets the syntax a certain way. Even if HTML5 required a “type” attribute (for validation), it wouldn’t really change the fact that the browser would still recognize a “script” tag with no “type”. Like you said, it defaults to “text/javascript” — but it’s the browser that does that, not the language.

    Anyhow, those are just a few minor points that probably needed to be worded a little differently, but thanks again for this easy to follow post.

    • Hi Louis. Thanks for the comment. You’re right. Each browser represents a unique implementation of the standards. My points are valid according to the standards (and for validation as you pointed out), but at the end of the day, the browser vendors can implement whatever they want (cough Microsoft).

  • Udeme

    I am new to web design and development.How can i become a professional in web design and dev. Thanks

    • Udeme, this is an exciting time to get into web design, so congrats. My advice would be to learn as much as possible. Read good sites like this one. More importantly, practice writing code. Finally, once you think you’re ready, try to secure some freelance work.

  • I’m a student and my instructor is using CDATA for all of his sample sites but I never heard any explanation why we need to use it specially that I need to correct my code everytime I validate it, that extra characters always been a headache to me, thanks for this tips..CDATA it is :)

  • If you liked this article, check out the follow up on my blog at The follow up contains more details that didn’t make the cut in this article.

  • Andres

    great roundup! im a seasoned web developer and i like to see articles like this one to refresh the basics

  • I found a mistake on the second example of the async attribute:

    library.setAttribute(“remote-library-code.js”, url);
    local.setAttribute(“local-file.js”, url);

    I think it should be:

    library.setAttribute(“src”, “remote-library-code.js”);
    local.setAttribute(“src”, “local-file.js”);

    Great article anyway!

    • You’re absolutely right. Thanks, Diego. I have corrected it. And thank you for reading!

  • Never seen such a comprehensive explanation about the script tag. Thanks for a great post !

  • Colin, great article, please refer to the case where, in older HTML-parsed browsers (as opposed to the newer XML-parsed ones) the DOM element cannot be “empty” (as in ) and the “closing tag” should be present. When such script elements are created by DOM (eg, via JScript or PHP) they will be empty, and browsers in trying to be backwards-compatible will not accept those “empty” elements. When generating via DOM I have resorted to generate the following: // , by appending a text child node containing the “// ” text. With DOM, I do the same with the /* */. -DM

  • Sorry, I forgot to escape the html-entities. Colin, great article, please refer, if you deem it appropriate, to the case where, in older HTML-parsed browsers (as opposed to the newer XML-parsed ones) the <script> DOM element cannot be “empty” (as in <script type=”text/javascript” src=”filename.js”/>) and the </script> “closing tag” should be present. When such script elements are created by DOM (eg, via JScript or PHP) they will be empty, and browsers in trying to be backwards-compatible will not accept those “empty” elements. When generating via DOM I have resorted to generate the following:<script type=”text/javascript” src=”filename.js”/>// </script>, by appending a text child node containing the “// ” text. With DOM, I do the same with the <style type=”text/css” src=”filename.css”/>/* */</style>. -DM

  • Theresa

    Isn’t the “lang” attribute needed for accessibility for people with disabilities (screen readers, etc.)?

    • In the article I discouraged use of the “language” attribute on the script tag, not the “lang” attribute.

  • Steve

    You shouldn’t comment out the CDATA tag when using it with XHTML. Having it commented only works where the page is actually served as HTML and is just pretending to be XHTML.

    The proper type value to use for JavaScript is type=”application/javascript” because text/javascript was deprecated long ago. You only need to use text/javascript if you want the script to be able to work as either JavaScript or JScript. IE8 and earlier don’t support either XHTML or JavaScript and so when using a CDATA tag the correct type=”application/javascript” should always be used.

    Unfortunately Firefox is the only browser that correctly identifies that it cannot run jScript when it encounters type=”text/jscript” and so to stop Chrome, Safari and Operafrom trying to run code they don’t understand you’d need to wrap the jScript inside a jScript conditional comment. IE9 is the only browser to understand both JavaScript and jScript.

    The language attribute is still essential when using the script tag to define server side languages eg.

    • The example script tag that uses CDATA validates as XHTML according to the W3C Markup Validation Service. The use of CDATA is required for XHTML, while the comment is required for HTML. If you’re definitely using XHTML, then the comment can be excluded. Similarly, if you’re definitely using HTML, then the CDATA section can be left out.

      Yes, “text/javascript” is deprecated, but there have been documented problems with IE when using “application/javascript”. I recommend using HTML5, which doesn’t require you to specify a “type” attribute, and thus not have to deal with this issue.

      As for server side languages, this article is only focused on client side scripting.

Get the latest in Front-end, once a week, for free.