This guide shows you how to build a web page with an interactive map display
results computed on the fly in Earth Engine. It is suited to people with
beginner or intermediate knowledge of HTML, CSS, and JavaScript.

Below is a demo of the interactive map you'll create in this guide. It shows
the terrain slope of the Grand Canyon computed in Earth Engine, represented by
varying shades of grey. You can pan and zoom the map to compute and display
results for additional areas. Note that you must login to the demo.
<iframe src="https://developers.devsite.google/frame/earth-engine/custom-apps/client-js/index_7352e5d3904e6ff10fa624b40bb93bb9fd632cd13e596b43a32510133b5be1be.frame" class="framebox inherit-locale " allow="clipboard-write https://developers.devsite.google" allowfullscreen is-upgraded></iframe>

The approach described here is typically used when creating
*single-page applications (SPAs)*. In an SPA, the entire app is usable in a web
browser without the page being reloaded from the server. As such, these apps
don't require the development and hosting of a custom server-side component.

Since the app's code will be run in the user's web browser (also known as
*client-side* execution), developers shouldn't embed sensitive credentials such
as service account private keys directly in the application. Instead, users of
the application must authenticate using their own accounts which have been
registered for Earth Engine access.

In this guide, you'll learn how to:

1. Display a button allowing users to sign in using their Earth Engine account.
2. Define a basic analysis in Earth Engine.
3. Embed an interactive map to display results using the Maps JavaScript API.

## Prerequisites

### Set up your Cloud Project

1. Before you begin, follow instructions in [Set up your Earth Engine enabled
   Cloud Project](https://developers.google.com/earth-engine/earthengine_cloud_project_setup). Make a note of the Client ID obtained in the section "Set up OAuth 2.0". Since your application will allow users to sign in with their own Google account, you can skip the section "Create and register a service account".
2. [Enable the Maps JavaScript API](https://console.cloud.google.com/apis/library/maps-backend.googleapis.com) for your project.

### Get a Maps API key

See [Get the API key](https://developers.google.com/maps/documentation/javascript/get-api-key) in the Maps
JavaScript API documentation to learn how to obtain an API key allowing you to
use Maps JavaScript API in your web application.

It is strongly recommended that you also follow instructions in the
[Restrict the API key](https://developers.google.com/maps/documentation/javascript/get-api-key#restrict_key)
section to ensure only authorized requests are made with your API key.

## Create your application

### Step 1. Create an HTML page

To begin, define a basic HTML web page as follows:

```html
<!DOCTYPE html>
<html>
  <head>
    <style>
      /* Set the size of the div element that contains the map. */
      #map-container {
        height: 400px;
        width: 100%;
        background-color: #eee;
      }
    </style>
  </head>
  <body>
    <!-- The "Sign in with Google" button, initially hidden. -->
    <input
      id="g-sign-in"
      type="image"
      src="https://developers.google.com/identity/images/btn_google_signin_light_normal_web.png"
      onclick="onSignInButtonClick()"
      alt="Sign in with Google"
      hidden
    />

    <!-- Element where map will be added. -->
    <div id="map-container"></div>
    <script>
      // JavaScript code goes here.
    </script>
  </body>
</html>
```

This basic HTML does several things:

- Uses CSS styles to define the size and background color of the map shown while initializing.
- Defines a "Sign in with Google" button that calls a function `onSignInButtonClick()` when clicked. This function will be defined in JavaScript in the next sections.
- Defines an empty element that will contain the map once it's initialized.
- Adds an empty `<script>` block to hold JavaScript code defined below.

> [!NOTE]
> **Note:** If you're unsure how to set up and host a static web page, see [Hosting a static website](https://cloud.google.com/storage/docs/hosting-static-website) in the Google Cloud Storage documentation for one possible approach.

### Step 2. Define behaviors in JavaScript

In the steps that follow, JavaScript code can be placed directly inside the
`<script>` tag.

#### Define callback to set up APIs and map

Define a function to do work once the user is signed in. The Earth Engine API
can only be initialized and invoked once the user has been authenticated. More
on that soon.

This example initializes the Earth Engine and Maps APIs, creates a tile source
that computes the slope of terrain on demand, and adds the tile source to the
map as an overlay displayed by the Maps JavaScript API:

```javascript
// Initializes Maps JavaScript API and Earth Engine API, instructing the map
// to pull tiles from Earth Engine and to overlay them on the map.
function setUpMap() {
  // Hide the sign-in button.
  document.getElementById("g-sign-in").setAttribute("hidden", "true");

  // Initialize the Earth Engine API. Must be called once before using the API.
  ee.initialize(null, null, null, null, null, 'my-project');

  // Get a reference to the placeholder DOM element to contain the map.
  const mapContainerEl = document.getElementById("map-container");

  // Create an interactive map inside the placeholder DOM element.
  const embeddedMap = new google.maps.Map(mapContainerEl, {
    // Pan and zoom initial map viewport to Grand Canyon.
    center: {lng: -112.8598, lat: 36.2841},
    zoom: 9,
  });

  // Obtain reference to digital elevation model and apply algorithm to
  // calculate slope.
  const srtm = ee.Image("CGIAR/SRTM90_V4");
  const slope = ee.Terrain.slope(srtm);

  // Create a new tile source to fetch visible tiles on demand and display them
  // on the map.
  const mapId = slope.getMap({min: 0, max: 60});
  const tileSource = new ee.layers.EarthEngineTileSource(mapId);
  const overlay = new ee.layers.ImageOverlay(tileSource);
  embeddedMap.overlayMapTypes.push(overlay);
}
```

#### Define handler for sign-in button clicks

Next, add a function to show the sign-in popup when the sign-in button is
clicked. If successful, the `setUp()` method is called.

```javascript
// Handles clicks on the sign-in button.
function onSignInButtonClick() {
  // Display popup allowing the user to sign in with their Google account and to
  // grant appropriate permissions to the app.
  ee.data.authenticateViaPopup(setUpMap);
}
```

This callback is associated with the button in the HTML above via its `onclick`
attribute. Note that in this example, the sign-in button is initially hidden
when the page is loaded. In the next section, we'll check if the user is
signed in before displaying the button.

#### Define main entry point

Now to define the top-level code to be executed first when the page is loaded.
It uses Earth Engine's built-in authentication helper function
[ee.data.authenticateViaPopup()](https://developers.google.com/earth-engine/apidocs/ee-data-authenticateviaoauth)
to check if the user is already signed in. If the user is signed in, the
function requests the appropriate permissions, and calls `setUp()` to initialize
the Earth Engine and Maps APIs if successful.

If instead the user is not signed in, the sign-in button is displayed. The user
can then click the button to trigger `onSignInButtonClick()`, which in turn
shows the popup and calls `setUp()` to kick things off.

```javascript
// If the user is signed in, display a popup requesting permissions needed to
// run the app, otherwise show the sign-in button.
ee.data.authenticateViaOauth(
  // The OAuth Client ID defined above.
  CLIENT_ID,
  // Callback invoked immediately when user is already signed in.
  setUpMap,
  // Show authentication errors in a popup.
  alert,
  // Request permission to only read and compute Earth Engine data on behalf of
  // user.
  /* extraScopes = */ ['https://www.googleapis.com/auth/earthengine.readonly'],
  // Show sign-in button if reusing existing credentials fails.
  () => document.getElementById("g-sign-in").removeAttribute("hidden"),
  // Don't require ability to write and access Cloud Platform on behalf of the
  // user.
  /* opt_suppressDefaultScopes = */ true
);
```

## Try it yourself

The complete solution presented in this guide follows. At the top right corner
of the sample code are three buttons. Click the left-most button to open the
sample in JSFiddle.

Replace `YOUR_API_KEY` and `YOUR_CLIENT_ID` with the Maps API key and OAuth
client ID obtained in [Prerequisites](https://developers.google.com/earth-engine/custom-apps/client-js#prerequisites) (you can click these
placeholders in the code below to have them automatically inserted). Also
replace `'my-project'` with your Google Cloud project ID.

```
<!-- Load Maps JavaScript API. For production apps, append you own ?key=YOUR_API_KEY. -->

<script src="https://maps.googleapis.com/maps/api/js?key="></script>
<script src="https://ajax.googleapis.com/ajax/libs/earthengine/0.1.365/earthengine-api.min.js"></script>
```

```
<!-- The "Sign in with Google" button, initially hidden. -->
<input
  id="g-sign-in"
  type="image"
  src="https://developers.google.com/identity/images/btn_google_signin_light_normal_web.png"
  onclick="onSignInButtonClick()"
  alt="Sign in with Google"
  hidden
/>

<!-- Element where map will be added. -->
<div id="map-container"></div>
```

```
/* Set the size of the div element that contains the map. */
  #map-container {
    height: 400px;
    width: 100%;
    background-color: #eee;
  }
```

```
// The OAuth Client ID from the Google Developers Console.
// REMINDER: Be sure to add a valid ID here!
const CLIENT_ID = "";

// Initializes Maps JavaScript API and Earth Engine API, instructing the map
// to pull tiles from Earth Engine and to overlay them on the map.
function setUpMap() {
  // Hide the sign-in button.
  document.getElementById("g-sign-in").setAttribute("hidden", "true");

  // Initialize the Earth Engine API. Must be called once before using the API.
  ee.initialize(null, null, null, null, null, 'my-project');

  // Get a reference to the placeholder DOM element to contain the map.
  const mapContainerEl = document.getElementById("map-container");

  // Create an interactive map inside the placeholder DOM element.
  const embeddedMap = new google.maps.Map(mapContainerEl, {
    // Pan and zoom initial map viewport to Grand Canyon.
    center: {lng: -112.8598, lat: 36.2841},
    zoom: 9,
  });

  // Obtain reference to digital elevation model and apply algorithm to
  // calculate slope.
  const srtm = ee.Image("CGIAR/SRTM90_V4");
  const slope = ee.Terrain.slope(srtm);

  // Create a new tile source to fetch visible tiles on demand and display them
  // on the map.
  const mapId = slope.getMap({min: 0, max: 60});
  const tileSource = new ee.layers.EarthEngineTileSource(mapId);
  const overlay = new ee.layers.ImageOverlay(tileSource);
  embeddedMap.overlayMapTypes.push(overlay);
}

// Handles clicks on the sign-in button.
function onSignInButtonClick() {
  // Display popup allowing the user to sign in with their Google account and to
  // grant appropriate permissions to the app.
  ee.data.authenticateViaPopup(setUpMap);
}

// If the user is signed in, display a popup requesting permissions needed to
// run the app, otherwise show the sign-in button.
ee.data.authenticateViaOauth(
  // The OAuth Client ID defined above.
  CLIENT_ID,
  // Callback invoked immediately when user is already signed in.
  setUpMap,
  // Show authentication errors in a popup.
  alert,
  // Request permission to only read and compute Earth Engine data on behalf of
  // user.
  /* extraScopes = */ ['https://www.googleapis.com/auth/earthengine.readonly'],
  // Show sign-in button if reusing existing credentials fails.
  () => document.getElementById("g-sign-in").removeAttribute("hidden"),
  // Don't require ability to write and access Cloud Platform on behalf of the
  // user.
  /* opt_suppressDefaultScopes = */ true
);
```

```html
<!DOCTYPE html>
<html>
  <head>
    <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/earthengine/0.1.365/earthengine-api.min.js"></script>
    <style>
      /* Set the size of the div element that contains the map. */
      #map-container {
        height: 400px;
        width: 100%;
        background-color: #eee;
      }
    </style>
  </head>
  <body>
    <!-- The "Sign in with Google" button, initially hidden. -->
    <input
      id="g-sign-in"
      type="image"
      src="https://developers.google.com/identity/images/btn_google_signin_light_normal_web.png"
      onclick="onSignInButtonClick()"
      alt="Sign in with Google"
      hidden
    />

    <!-- Element where map will be added. -->
    <div id="map-container"></div>
    <script>
      // The OAuth Client ID from the Google Developers Console.
      const CLIENT_ID = "YOUR_CLIENT_ID";
      
      // Initializes Maps JavaScript API and Earth Engine API, instructing the map
      // to pull tiles from Earth Engine and to overlay them on the map.
      function setUpMap() {
        // Hide the sign-in button.
        document.getElementById("g-sign-in").setAttribute("hidden", "true");
      
        // Initialize the Earth Engine API. Must be called once before using the API.
        ee.initialize(null, null, null, null, null, 'my-project');
      
        // Get a reference to the placeholder DOM element to contain the map.
        const mapContainerEl = document.getElementById("map-container");
      
        // Create an interactive map inside the placeholder DOM element.
        const embeddedMap = new google.maps.Map(mapContainerEl, {
          // Pan and zoom initial map viewport to Grand Canyon.
          center: {lng: -112.8598, lat: 36.2841},
          zoom: 9,
        });
      
        // Obtain reference to digital elevation model and apply algorithm to
        // calculate slope.
        const srtm = ee.Image("CGIAR/SRTM90_V4");
        const slope = ee.Terrain.slope(srtm);
      
        // Create a new tile source to fetch visible tiles on demand and display them
        // on the map.
        const mapId = slope.getMap({min: 0, max: 60});
        const tileSource = new ee.layers.EarthEngineTileSource(mapId);
        const overlay = new ee.layers.ImageOverlay(tileSource);
        embeddedMap.overlayMapTypes.push(overlay);
      }
      
      // Handles clicks on the sign-in button.
      function onSignInButtonClick() {
        // Display popup allowing the user to sign in with their Google account and to
        // grant appropriate permissions to the app.
        ee.data.authenticateViaPopup(setUpMap);
      }
      
      // If the user is signed in, display a popup requesting permissions needed to
      // run the app, otherwise show the sign-in button.
      ee.data.authenticateViaOauth(
        // The OAuth Client ID defined above.
        CLIENT_ID,
        // Callback invoked immediately when user is already signed in.
        setUpMap,
        // Show authentication errors in a popup.
        alert,
        // Request permission to only read and compute Earth Engine data on behalf of
        // user.
        /* extraScopes = */ ['https://www.googleapis.com/auth/earthengine.readonly'],
        // Show sign-in button if reusing existing credentials fails.
        () => document.getElementById("g-sign-in").removeAttribute("hidden"),
        // Don't require ability to write and access Cloud Platform on behalf of the
        // user.
        /* opt_suppressDefaultScopes = */ true
      );
    </script>
  </body>
</html>
```

<br />