# Embedding maps

Maps built with CARTO can be easily embedded in other websites or applications, using an [`<iframe>` HTML element](https://www.w3schools.com/tags/tag_iframe.ASP). This is a great way for your maps to make a larger impact by reaching a larger audience, both outside and inside your organization.

Additionally, embedding a map can be a way to create beautiful data stories by wrapping your map with richer, interactive context and storytelling in a presentation or a website.

{% hint style="success" %}
For an in-depth tutorial on creating and embedding maps, we highly recommend visiting the [CARTO Academy guide](https://academy.carto.com/building-interactive-maps/sharing-and-collaborating/build-an-interactive-map-and-embed-it), which contains practical step-by-step resources.
{% endhint %}

<figure><img src="https://3029946802-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FybPdpmLltPkzGFvz7m8A%2Fuploads%2Fgit-blob-856e31d7f975ab81bbbed24767cc43f4d7ced1ff%2FScreenshot%202024-03-14%20at%2016.16.00.png?alt=media" alt=""><figcaption><p>Example of an embedded map with interactive controls via URL parameters</p></figcaption></figure>

## Embedding a map

To embed a map, simply click on "**Share**" as seen in [Publishing and sharing maps](https://docs.carto.com/carto-user-manual/maps/sharing-and-collaboration), and copy the **embed code** available in the sharing dialog. The resulting code should be similar to this one.

{% code overflow="wrap" %}

```html
<iframe 
    width="100%" 
    height="640px" 
    src="https://clausa.app.carto.com/map/ff76a0cd-fd9c-4893-8c2b-d9c587f2d699"></iframe>
```

{% endcode %}

## Public embedding

Maps that are shared as ***Public*** can be embedded anywhere without restrictions. Public maps protected with a password can also be embedded, forcing viewers to know and introduce the password.

## Private embedding

You can securely limit who can view your embedded map by sharing it exclusively with your **Organization,** specific **Users/Groups**, or just yourself as **Private.**

Then, when you embed a non-public map, there are different strategies to verify and control if the external application is allowed to load the embedded map:

{% hint style="warning" %}
**Security considerations:**

* Methods 1 and 2 rely on tokens that are either long-lived or grant access to user-level permissions. They must be treated as sensitive information. Do not expose said credentials in public repositories or client-side code that could be inspected by malicious actors.
* Make sure you follow [best practices when dealing with postMessage events](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage), including event.origin and targetOrigin verifications.
* **When including the Map API Access Token in your iFrame URL,** the token is visible in the browser's address bar and network requests. If this is not desired, please send the Map API Access Token via postMessage or use a different method for private embedding..
  {% endhint %}

### Method 1: using the Map API Access Token

The Map API Access Token is a long-lived, read-only [API Access Token](https://docs.carto.com/carto-for-developers/key-concepts/authentication-methods/api-access-tokens) automatically generated for each map. This is the simplest method for embedding private maps in external applications.

**How it works:**

1. Navigate to your [map's sharing settings in the CARTO interface](https://docs.carto.com/carto-user-manual/maps/sharing-and-collaboration) and copy the **Map API Access Token** displayed in the UI.
2. You can then pass this token in your app via either:
   1. **URL parameter:** Include the token directly in your iframe URL as a `token` query parameter.
   2. **PostMessage flow (recommended):**
      1. Initialize your iframe with a `use-external-access-token` query parameter.
      2. The embedded map will request the token via a postMessage event with eventType `cartoAccessTokenRequest` instead of expecting it in the URL.
         1. Make sure you verify the `event.origin` of this message.
      3. Respond to this message with a `cartoAccessToken` event type, where the event data is the **Map API Access Token**.
         1. Make sure you set the `targetOrigin` so that this message is only received by the CARTO iframe.

<details>

<summary>Example (URL Parameter)</summary>

```html
<iframe
  src="https://your-carto-domain.com/map/your-map-id?use-external-access-token&token=YOUR_MAP_API_ACCESS_TOKEN"
  width="100%"
  height="600px">
</iframe>
```

</details>

<details>

<summary>Example (PostMessage flow)</summary>

```html
<iframe 
  id="cartoMap"
  src="https://your-carto-domain.app.carto.com/map/your-map-id?use-external-access-token"
  width="100%" 
  height="600px">
</iframe>

<script>
  // Your Map API Access Token from CARTO settings
  const MAP_API_TOKEN = 'YOUR_MAP_API_ACCESS_TOKEN';
  const iframe = document.getElementById('cartoMap');

  // Listen for token requests from the embedded map
  window.addEventListener('message', (event) => {
    // Verify the origin for security
    if (event.origin !== 'https://your-carto-domain.app.carto.com') return;
         
    // Respond to token request
    if (event.data.type === 'cartoAccessTokenRequest') {
      iframe.contentWindow.postMessage({
        type: 'cartoAccessToken',
        data: MAP_API_TOKEN
      }, event.origin);
    }
  });
</script>
```

</details>

{% hint style="success" %}
**Best for:** Simple embedding scenarios where you control the host application and need a straightforward authentication method.
{% endhint %}

### Method 2: using an SPA OAuth Flow

[SPA OAuth Clients](https://docs.carto.com/carto-user-manual/developers/managing-credentials/spa-oauth-clients) are an authentication mechanism that generate short-lived tokens that represent a specific user's permissions, including access to specific maps. This is great if your application already makes authenticated calls to CARTO using these user-level tokens.

**How it works:**

1. Implement the recommended authentication flow with an [SPA OAuth Client](https://docs.carto.com/carto-for-developers/key-concepts/authentication-methods/applications#single-page-application-spa-oauth-clients) (where the user actively inputs their CARTO credentials), until you obtain a valid access token.
2. Make sure that the user has access to the embedded map, by [sharing this map](https://docs.carto.com/carto-user-manual/maps/sharing-and-collaboration) with them.
3. Use the **PostMessage flow** to pass the resulting OAuth Access Token to the embedded CARTO map:
   1. Initialize your iframe with a `use-external-access-token` query parameter.
   2. The embedded map will request the token via a postMessage event with eventType `cartoAccessTokenRequest` instead of expecting it in the URL.
      1. Make sure you verify the `event.origin` of this message.
   3. Respond to this message with a `cartoAccessToken` event type, where the event data is the **OAuth Access Token**.
      1. Make sure you set the `targetOrigin` so that this message is only received by the CARTO iframe.

<details>

<summary>Example (PostMessage flow)</summary>

```html
  <iframe 
    id="cartoMap"
    src="https://your-carto-domain.app.carto.com/map/your-map-id?use-external-access-token"
    width="100%" 
    height="600px">
  </iframe>

  <script>
  // After OAuth authentication with SPA, you'll get an access token
  const userAccessToken = await authenticateUser(); // Your OAuth implementation
  const iframe = document.getElementById('cartoMap');

  // Listen for token requests from the embedded map
  window.addEventListener('message', (event) => {
    // Verify origin for security
    if (event.origin !== 'https://your-carto-domain.com') return;

    if (event.data.type === 'cartoAccessTokenRequest') {
      // Respond with user's OAuth access token
      iframe.contentWindow.postMessage({
        type: 'cartoAccessToken',
        data: userAccessToken
      }, event.origin);
    }
  });
  </script>
```

</details>

{% hint style="success" %}
**Best for:** Embedding scenarios where there's already a parent application that already requires CARTO authentication via SPA OAuth Client for other API calls.
{% endhint %}

{% hint style="warning" %}
This method is not compatible with M2M OAuth Clients
{% endhint %}

### Method 3: re-using an existing login session

If no `token` URL parameter is used, and the postMessage `cartoAccessToken` flow is not completed, CARTO will fall back to re-using the existing login session in the browser, if it exists. This will work when the following requirements are met.

* Users must be previously logged-in to CARTO in the same browser as the embedded map
* Users must have access to the map *(for example, they need to be part of the group that map is shared with)*
* CARTO needs to be able to access cookies in the user browser. Learn more in the [known limitations](#known-limitations) section of this page.

If the user has access but is not currently logged-in to CARTO, they will need to login in a separate tab/window and refresh the page containing the embedded map.

<figure><img src="https://3029946802-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FybPdpmLltPkzGFvz7m8A%2Fuploads%2Fgit-blob-64cf21d2e7384e2b5f533b62dd35c5b0e403b2b4%2FScreenshot%202024-03-14%20at%2016.22.04.png?alt=media" alt=""><figcaption></figcaption></figure>

## Interacting with embedded maps

Users can interact with the embedded map in the same way they would in a standalone window by engaging with the map layers, widgets, SQL parameters...

An additional way to interact with the embedded map is by modifying the [**URL parameters**](https://docs.carto.com/carto-user-manual/maps/sharing-and-collaboration/url-parameters)**.** This is particularly interesting because it allows the parent application to control the state of the embedded CARTO map.

{% hint style="info" %}
By modifying dynamically URL parameters in your embedded map, you can, for example:

* Control the map zoom and camera from user controls in your parent application
* Modify the map filters (widgets and parameters) when the users selects different options in your parent application
* Show/hide layers based on the user preferences in your parent application
  {% endhint %}

## Listening to events from embedded maps

When embedding CARTO maps in an iframe, you can listen to events emitted by the embedded map to respond to user interactions and map state changes. This allows you to create bi-directional interactive experiences. The map uses the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) to communicate with the parent application.

{% hint style="warning" %}
When listening to postMessage events, **always verify the `event.origin` of the message** to prevent security vulnerabilities
{% endhint %}

#### cartoMapLoaded

This event is fired once when the map finishes loading and is ready to use.

* **Event Type:** `cartoMapLoaded`

<details>

<summary>Event Data reference</summary>

```json
{
  // The title of the map
  "title": "string",
  
  // List of widgets available on the map
  "widgets": [
    {
      "id": "string",        // Widget identifier
      "type": "string",      // Widget type: "category" | "pie" | "histogram" | "range"
      "title": "string"      // Widget title
    }
  ],
  
  // List of SQL parameters defined in the map
  "sqlParameters": [
    {
      "name": "string",      // Parameter name
      "type": "string"       // Parameter type: "category" | "numeric" | "dateRange" | "numericRange"
    }
  ]
}
```

</details>

<details>

<summary>Javascript Example</summary>

```javascript
window.addEventListener('message', (event) => {
  if (event.data.type === 'cartoMapLoaded') {
    console.log('Map loaded:', event.data.data.title);
    console.log('Available widgets:', event.data.data.widgets);
    console.log('SQL parameters:', event.data.data.sqlParameters);
  }
});
```

</details>

#### cartoMapUpdated

This event is fired whenever the map state changes (viewport changes, layer visibility, filters, etc.). This normally happens when the user zooms in, uses a widget or a SQL parameter, etc.

* **Event Type:** `cartoMapUpdated`

<details>

<summary><strong>Event Data reference</strong></summary>

```json
{
  "lat": number,              // Map center latitude
  "long": number,             // Map center longitude
  "zoom": number,             // Current zoom level
  "bearing": number,          // Map bearing/rotation in degrees
  "pitch": number,            // Map pitch/tilt in degrees
  "search": "string",         // (Optional) Current geocoder search text
  "layers": [number],         // Indices of visible layers
  "mask": "string",           // (Optional) WKT representation of the spatial filter geometry
  
  // Active widget filters
  "widgetFilters": [
    {
      "id": "string",         // Widget identifier
      "value": []             // string[] for category/pie widgets | number[][] for range/histogram, which is an array of [min, max] pairs
    }
  ],
  
  // Current SQL parameter values
  "sqlParametersValues": [
    {
      "name": "string",       // Parameter name
      "value": any            // string | number | string[] depending on parameter type
    }
  ]
}
```

</details>

<details>

<summary>Javascript Example</summary>

```javascript
window.addEventListener('message', (event) => {
  if (event.data.type === 'cartoMapUpdated') {
    const mapState = event.data.data;
    console.log('Map position:', mapState.lat, mapState.long, mapState.zoom);
    console.log('Active widget filters:', mapState.widgetFilters);
    console.log('SQL parameter values:', mapState.sqlParametersValues);
  }
});
```

</details>

## Known limitations

1. The parent application must specify a valid `origin` HTTP header for the CARTO embedded map to load. This is a security requirement.
2. **For private embedding when re-using existing login session (method 3):** If the user browser does not allow access to cookies to the CARTO embedded iframe, a screen will pop up, prompting for permission.

   * Some browsers such as Firefox or Brave offer advanced privacy/tracking protection that might interfere with the CARTO embedded iframe. While these features are great for user privacy, they also disable legitimate secure use cases like this one. Users can disable these protections.

   <figure><img src="https://3029946802-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FybPdpmLltPkzGFvz7m8A%2Fuploads%2Fgit-blob-39e587a0f71fb56decd6ffff58645b5e6cc846b3%2FScreenshot%202024-03-14%20at%2016.18.20.png?alt=media" alt=""><figcaption></figcaption></figure>
