Adding JavaScript to a Page

JavaScript · Lesson 2 of 61 6 min read Updated

Before JavaScript can do anything on a page, the browser has to load it. You can write JavaScript directly inside an HTML file or keep it in a separate .js file, and where you put it decides whether your code can see the page at all.

In this lesson you will learn the <script> element, external script files, and the defer, async and type="module" attributes that control when a script runs.

The script element

JavaScript lives inside a <script> element. The browser reads the HTML from top to bottom, and when it reaches a <script>, it runs the code inside it.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Script at the End</title>
</head>
<body>
  <h1>Jaipur Sweet House</h1>
  <p id="status">Checking...</p>

  <script>
    const statusText = document.querySelector("#status");
    statusText.textContent = "Open today from 9 am to 9 pm";
  </script>
</body>
</html>

The script sits at the end of <body>, after the paragraph. By the time the browser runs it, the paragraph already exists, so the code can find it with document.querySelector() and change its text.

You do not need a type attribute for normal JavaScript. Older pages often have type="text/javascript", but browsers treat every script as JavaScript by default.

Why the position matters

Put the same script in the <head>, and it breaks:

HTML
<head>
  <meta charset="utf-8">
  <title>Broken</title>
  <script>
    // runs before the body exists, so this finds nothing (null)
    document.querySelector("#status").textContent = "Open today";
  </script>
</head>

When the browser reaches this script, it has not read the <body> yet. document.querySelector("#status") returns null, a value that means "nothing", and setting text on nothing throws an error such as "Cannot set properties of null". This is one of the most common beginner bugs. The fix is to make the script run after the page is parsed, which the next sections show.

External script files

Writing JavaScript inside HTML is fine for small demos. Real projects keep JavaScript in separate files with the .js extension and link them with the src attribute.

JavaScript
// file: js/app.js
const today = new Date().getFullYear();
document.querySelector("#year").textContent = today;
HTML
<!-- file: index.html -->
<footer>
  <p>&copy; <span id="year"></span> Kabir Electronics, Rohtak</p>
</footer>
<script src="js/app.js" defer></script>

External files have clear benefits:

  • Cleaner code. HTML stays about structure and JavaScript stays about behaviour.
  • Reuse. Ten pages can share one app.js file.
  • Speed. The browser caches (saves) the file after the first visit, so other pages load faster.

The src path works like an image path: it is relative to the HTML file unless it starts with / or https://. A <script> with src must stay empty, because the browser ignores any code you write between the tags.

defer, async and modules

A plain <script src> in the <head> stops the browser. It downloads the file, runs it, and only then continues reading the HTML. Three options fix this.

AttributeDownloadsRunsOrder kept?
nonePauses the page while downloadingImmediatelyYes
deferIn the backgroundAfter the HTML is fully readYes
asyncIn the backgroundAs soon as it arrivesNo
type="module"In the backgroundAfter the HTML is fully readYes
  • Use defer for normal scripts that work with the page. You can then put the script in the <head>, and it still sees every element.
  • Use async only for independent scripts, such as a visitor counter, that do not depend on the page or on other scripts.
  • Use type="module" for modern code that is split into files with import and export. Module scripts are deferred automatically, even when they are written inline.

defer and async only work on scripts with a src attribute. A module script is different: even an inline one waits for the page, as this example shows.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Module in the Head</title>
  <script type="module">
    // module scripts wait until the HTML is read
    const statusLine = document.querySelector("#trains");
    statusLine.textContent = "12015 Ajmer Shatabdi: on time";
  </script>
</head>
<body>
  <h1>Train Status</h1>
  <p id="trains">Loading...</p>
</body>
</html>

You will learn how modules split code into files in Modules.

Which should you use?

For your own pages, follow this simple rule:

  • Small demo or single page: a <script> at the end of <body>.
  • Real site: external files in the <head> with defer, or with type="module" once you use import.

Both rules guarantee that the HTML exists before your code runs.

Common mistakes

  • Script in the head without defer. The code runs before the elements exist and querySelector() returns null. Add defer, or move the script to the end of <body>.
  • Code inside a script that has src. <script src="app.js">console.log("hi")</script> ignores the inline code. Use two separate <script> elements.
  • Wrong file path. If src="app.js" but the file is in a js folder, nothing runs. Open DevTools (press F12) and check the Console and Network tabs for a 404 error.
  • Expecting async scripts to run in order. Two async scripts can run in either order. Use defer when one script depends on another.

Key points

  • JavaScript goes inside a <script> element, either inline or linked with src.
  • The browser runs a classic script as soon as it reaches it, so position matters.
  • Put scripts at the end of <body>, or in the <head> with defer.
  • async scripts run as soon as they download, in any order.
  • type="module" scripts are deferred automatically and can use import.
  • External .js files keep code clean, reusable and cached.

Exercise

Create a page with a heading and an empty paragraph that has id="msg". Add a script in the <head> that writes "Welcome to Lucknow Library" into the paragraph. Make sure it works even though the script is in the head.

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Library</title>
  <script type="module">
    document.querySelector("#msg").textContent = "Welcome to Lucknow Library";
  </script>
</head>
<body>
  <h1>City Library</h1>
  <p id="msg"></p>
</body>
</html>

A module script waits until the HTML is read, so the paragraph exists when the code runs. With an external file, <script src="app.js" defer> would work the same way.

Quick Quiz

1. Why does document.querySelector("#box") return null in a plain script in the <head>?

2. What does defer do on <script src="app.js" defer>?

3. Which script waits for the page automatically, even when written inline?

Learn JavaScript with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call