Azure Developer Services

Add an Interactive Map to Your App with Azure Maps: From Zero to First Marker

You have a web app — a store locator, a delivery tracker, an asset dashboard — and you need a map in it. Not a screenshot of a map, but a real one: pan, zoom, a pin you can drop at a latitude and longitude, a popup when someone clicks it. The instinct is to reach for whatever map library you saw in a tutorial, paste a key, and move on. Then the bill arrives, or the key leaks, or the map renders blank in production, and you wish someone had walked you through it properly the first time. Azure Maps is Microsoft’s geospatial platform — a set of REST services (tiles, search, routing, geolocation, traffic, weather) plus a browser Web SDK that renders an interactive, WebGL-accelerated map — and this article is that proper walk-through.

We are doing one concrete thing: adding an interactive map with a single marker to a web page, backed by a real Azure Maps account, the right way. “The right way” means you understand the two pieces you create (an Azure Maps account and the credential it issues), you choose the pricing tier and authentication mode deliberately, and you can reproduce the whole thing three ways — the Azure portal, the az CLI, and Bicep so it lives in source control. By the end you will have a map on screen with a pin on it, and you will know exactly which knob does what.

This is deliberately a first map — not a routing engine or a tour of every REST endpoint, but the smallest end-to-end slice that is still real and production-aware. The habits you form here (don’t ship a key in client-side code, pick the cheapest tier that works, tag and budget the resource) are the ones you keep when the map grows up.

What problem this solves

Every “where” feature needs the same plumbing: a rendered basemap, a coordinate system, the ability to place and style points, and a way to react to clicks. Building that yourself — sourcing tiles, handling projections, writing pan/zoom math — is months of work and a legal minefield around map-data licensing. A managed platform hands you all of it behind one credential and one SDK. Azure Maps solves “I need a commercially-licensed map this week, billed on the same invoice and using the same identity model as the rest of my Azure estate.”

What breaks without doing this deliberately is predictable. People embed a subscription key in a public page, where anyone with DevTools can read it and a scraper can run up the bill. People pick the wrong pricing tier and either lack a feature or pay for one they don’t use. People see a blank grey rectangle because the SDK script didn’t load, the container <div> has zero height, or the key is wrong — three causes, one symptom. None are hard once you’ve seen them; all cost an afternoon the first time.

Who hits this: front-end and full-stack developers adding a location feature, solo builders who want something that “just works” without tile servers, and architects standardising on Azure who want maps on the same identity (Microsoft Entra ID), governance and billing as everything else. If you’ve ever pasted a maps key into a <script> tag and hoped, this article is the antidote.

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You need an Azure subscription (a free trial is enough — Azure Maps has a generous free grant, see Cost & sizing) and the ability to run the az CLI, locally or in Azure Cloud Shell (which has az and Bicep pre-installed — the fastest start). You should be comfortable editing a small HTML file and serving it over a local web server (python3 -m http.server; the Web SDK needs http:///https://, not file://). Basic JavaScript helps; no GIS, cartography or WebGL knowledge is assumed.

Where this fits: Azure Maps is a developer service your application calls — the map runs in the browser, the account and credential live in your subscription. Where the app hosting the map runs is the Azure App Service vs Container Apps vs AKS decision; Azure Maps is agnostic to it, since the SDK is just static JS and CSS, and the account sits in your Azure resource hierarchy like any other resource. When you graduate from a raw key to a managed identity and a secrets store, you’ll lean on Azure Key Vault and the patterns in Azure App Configuration and Key Vault references. Once real users hit the map, you’ll watch usage and errors through Azure Monitor and Application Insights.

A quick orientation to the moving parts before the deep dive:

Piece What it is Where it lives You touch it…
Azure Maps account The billable resource that exposes the Maps services Your subscription / resource group Once, at setup
Pricing tier (SKU) S0 or S1 — sets features, limits and billing model A property of the account At create time (changeable)
Credential A shared key, an SAS token, or an Entra token Issued by the account Every map load
Web SDK Browser JS/CSS library that renders the map A CDN or your node_modules In your front-end code
REST services Search, Route, Render, Geolocation, etc. Microsoft-hosted endpoints The SDK calls them for you

Core concepts

Five ideas make everything that follows obvious.

The account is the resource; the SDK is the code. An Azure Maps account is a lightweight Azure resource — a name, a resource group, a pricing tier, and credentials it issues — that does almost nothing by itself. The visible, interactive map is produced entirely in the browser by the Web SDK (the atlas JavaScript library), which authenticates to the Maps REST services using a credential the account issued. You create one account, then write code that points at it; one account can back many maps and apps.

A “map” is a WebGL canvas the SDK manages. When you call new atlas.Map(...), the SDK creates a WebGL canvas inside a <div> you give it, fetches vector map tiles from the Render service, and handles pan and zoom. You position it with a camera: a center ([longitude, latitude], longitude first) and a zoom level (roughly 0 = whole world, 22 = building-level). Markers, lines and popups layer on this canvas.

Coordinates are [lon, lat], not [lat, lon]. This trips up nearly everyone. Azure Maps follows the GeoJSON standard — longitude first. A human says “latitude, longitude” (Bengaluru is 12.97 N, 77.59 E), but the SDK wants [77.59, 12.97]. Get it backwards and your marker lands in the ocean off Africa, not in India. Memorise: X then Y, lon then lat.

A marker is a styled point you attach to the map. The simplest pin is an HtmlMarker — an HTML element (a teardrop pin by default) anchored to a coordinate. You give it a position (and optional color/text), add it to the map, and it tracks the camera as you pan and zoom. Attach a Popup and wire a click event to open it — the whole “first marker” experience.

The credential is the thing you must protect. The account issues a shared key (primary and secondary, for rotation) granting full access to the Maps services — anyone with it can spend your quota. In client-side code, which is public by definition, don’t ship a raw shared key beyond a throwaway demo; the grown-up options are Microsoft Entra ID with a runtime token, or an SAS token with a tight expiry and rate cap. We’ll start with a key for speed, then harden it.

The vocabulary in one table

Term One-line definition Why it matters for your first map
Azure Maps account The Azure resource exposing Maps services The thing you create and bill
Pricing tier / SKU S0 or S1 (Gen2) — feature & billing band Decides cost model and which APIs you can call
Shared key A long secret string granting full access Fastest auth; unsafe to ship in public JS
SAS token A scoped, time-limited, rate-capped token Safer client-side credential than a raw key
Microsoft Entra ID auth Token-based auth via Entra + RBAC The production-grade auth model
Web SDK (atlas) Browser JS/CSS that renders the map The code that draws the interactive map
Map The WebGL canvas object The map itself
Camera center + zoom (+ bearing/pitch) Where the map is looking
HtmlMarker An HTML pin anchored to a coordinate Your first pin
Popup An info bubble The “click the pin” experience
GeoJSON The [lon, lat] coordinate standard Why coordinate order is lon-first

Choosing your pricing tier

Azure Maps bills on the Gen2 model, exposed as two SKUs on the account; you pick one at create time and can change it later. The difference is the billing model and which capabilities are surfaced, not map quality — the basemap looks the same either way.

Tier Billing model Best for Notes
Gen2 S0 Pay-as-you-go per transaction, with a monthly free grant Most apps, including production, that pay per use The default starting point; you pay for what you call
Gen2 S1 Higher-throughput / committed-style band for heavy, sustained use High-volume workloads needing elevated limits Choose only when S0 limits or throttling bite

For a first map — and most apps — Gen2 S0 is the correct choice: a free monthly allowance (see Cost & sizing), pay-as-you-go beyond that, and full support for the Web SDK, markers and the common services. Reach for S1 only when you’ve measured sustained high volume and need its elevated throughput; don’t pick the bigger tier “to be safe.”

A note on the transaction, because it drives the bill: panning/zooming pulls map tiles, metered in batches (render transactions count per a set number of tiles, not per tile); each search or routing call is its own transaction.

Choosing your authentication

This is the decision that separates a safe map from a leaked credential. Three ways the Web SDK can authenticate to the Maps services:

Auth method How it works Safe in public client JS? Setup effort Use it for
Shared key A static secret string sent with each call No — it’s visible to anyone Lowest Local demos, server-to-server calls
SAS token A signed, time-boxed, rate-limited token minted server-side Yes, if short-lived and scoped Medium Public web apps without full Entra wiring
Microsoft Entra ID A bearer token from Entra; access via Azure RBAC Yes — the recommended model Higher Production apps and enterprises

The honest pattern for learning: use a shared key to get the map on screen in five minutes, then never ship that key in a public page — a key in a page anyone can open is your account password, published. The lab’s local-demo key is fine because the page runs only on your machine; the moment it’s public, switch to SAS or Entra.

How the credential reaches the SDK differs only in the authOptions.authType value — 'subscriptionKey', 'sas', or 'aad'. The lab uses the shared-key version; Part 6 shows the exact Entra and SAS variants. The map code is identical across all three; only the authOptions block differs.

Loading the Web SDK

Two ways to get the atlas library into your page. Option A — the CDN (fastest, what the lab uses): two <link>/<script> tags at Microsoft’s Azure Maps CDN give you the latest v3 SDK with no build step — ideal for learning. Option B — the npm package (npm install azure-maps-control) bundles atlas with Vite/webpack, pinning a version and fitting a component framework (React/Vue/Angular); move to it once the map is part of a real front end. Either way, load both SDK assets:

Asset What it is CDN tag
atlas.min.css The SDK’s stylesheet (controls, markers, popups) <link rel="stylesheet" href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.css">
atlas.min.js The SDK script that defines the atlas namespace <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.js"></script>

Forgetting the CSS is a classic first mistake: the map works but markers and controls look broken because their styles never loaded.

Placing your first marker

Once the map exists, a marker is three lines: create it with a position, add it to the map, and wire a popup to a click. The HtmlMarker options you’ll use on day one:

Option What it sets Example Default
position The [lon, lat] the pin sits at [77.5946, 12.9716] required
color The pin fill colour '#E81123' a default blue
text A short label drawn on the pin '1' none
draggable Whether the user can drag the pin true false
htmlContent Custom HTML instead of the default pin '<div class="dot"></div>' the teardrop pin

And the three SDK calls that put it on screen and make it interactive:

Call What it does
new atlas.HtmlMarker({ position, color, text }) Creates the pin object
map.markers.add(marker) Renders it on the map
map.events.add('click', marker, handler) Runs handler on click (e.g. open a popup)

The lab assembles these into one working file.

Architecture at a glance

Picture three layers with one credential threading through them. In the browser, your HTML page loads the atlas Web SDK (CSS + JS) and calls new atlas.Map(...), handing it the <div> to draw in and an authOptions block. Across the network, the SDK authenticates with that credential and requests vector map tiles from the Azure Maps Render service over HTTPS, streaming them in as you pan and zoom; search and routing go to their own REST endpoints the same way. In your subscription sits the Azure Maps account — the resource that validates the credential, meters transactions for billing, and issued that credential in the first place.

The flow is a loop: the page asks the SDK for a map, the SDK requests tiles using your credential, the account validates it and counts the usage, tiles render, and your HtmlMarker is drawn on top. The marker and popup are pure client-side — no server round-trip — so clicking a pin is instant. The model to keep: the account authorises and bills; the SDK draws; the credential is the bridge. The lab makes those three layers talk; Security notes makes the bridge safe to cross in public.

Real-world scenario

Cafezure, a 40-store coffee chain in Bengaluru, wanted a store-locator page: a map of every outlet, click a pin for address and hours. A junior developer shipped it in two days with the Azure Maps Web SDK and a shared subscription key pasted straight into the page’s JavaScript. It demoed great and went live on a Friday.

By Monday, two things had gone wrong. First, a competitor’s scraper had found the key in the page source (it’s right there in DevTools) and was hammering the Render and Search services to harvest store data, pushing the transaction count up sharply — not yet a huge bill on S0’s free grant, but a clear abuse trend and a leaked credential that now had to be rotated. Second, the map intermittently rendered as a blank grey box for some users. The developer assumed it was the key; it wasn’t. The page’s CSS framework set the map container’s height to auto, which computed to zero — so the WebGL canvas was there but had no pixels to fill. Where another element forced the container open it worked; elsewhere, grey.

The architect’s fix took an afternoon. The shared key was rotated immediately and removed from client code. Authentication moved to a short-lived SAS token minted by a tiny serverless endpoint, scoped to Render and Search with a rate cap and a one-hour expiry — so even a leaked token expires fast and can’t exceed the cap. The blank-map bug was fixed by giving the container an explicit height (#map { height: 480px; }) and confirming the SDK CSS loaded. They fronted the token endpoint with an origin check, and turned on diagnostic logging to Log Analytics so transaction spikes raise an alert.

The lesson this whole article teaches: the map was never the hard part — it rendered in two days. The hard parts were the credential (don’t ship a raw key publicly) and the container (a WebGL map needs an explicit, non-zero height). Get those two right on day one and the map is boring, in the best way. Cafezure now treats the maps key like any other secret — rotated on a schedule, never in client code, monitored for anomalies — and the locator has run clean since.

Advantages and disadvantages

Advantages Disadvantages
Managed, commercially-licensed map data — no tile servers to run Another service and credential to govern and secure
First-party Azure resource: same billing, RBAC, tagging, regions Smaller third-party plugin ecosystem than some rival map libraries
Generous free monthly grant covers learning and small apps Pay-per-transaction can surprise you if a key leaks or a loop runs
Modern WebGL Web SDK (vector tiles, smooth pan/zoom) WebGL dependency: very old browsers/devices degrade
Microsoft Entra ID + SAS auth for proper credential hygiene Doing auth right (Entra/SAS) is more setup than pasting a key
Rich service family to grow into (search, route, traffic, weather) Easy to over-provision the tier or call services you don’t need

The advantages matter most when you’re already on Azure and want maps on the same identity, governance and invoice, with licensed data and a free on-ramp that scales to production. The disadvantages bite on a tiny static site where any free widget would do — or for a team with no appetite to manage secrets, who will leak a key, so invest the hour in SAS/Entra first.

Hands-on lab

This is the centrepiece. You will create an Azure Maps account (three ways), build a page with an interactive map, drop a marker, validate it, then tear it all down. Total Azure cost: effectively zero — you’ll stay inside the free grant.

What you’ll build: a single HTML page showing a map centred on Bengaluru with one clickable, popup-bearing marker, authenticated with a shared key for local development.

Prerequisites checklist:

Need How to get it Verify
Azure subscription Free trial at azure.com az account show returns a subscription
az CLI + Bicep Use Cloud Shell, or install locally az version shows azure-cli and bicep
A text editor Any
A local web server python3 -m http.server (built in) python3 --version works
A modern browser Edge/Chrome/Firefox/Safari WebGL enabled (default)

Part 1 — Create the account in the Azure portal

  1. Sign in to the Azure portal (portal.azure.com).
  2. In the top search bar, type Azure Maps and select Azure Maps Accounts.
  3. Click + Create.
  4. On the Basics tab, fill in:
    • Subscription: your subscription.
    • Resource group: click Create new, name it rg-maps-lab.
    • Name: maps-lab-kv (globally meaningful, lowercase).
    • Pricing tier: select Gen2 (this maps to the S0 pay-as-you-go model).
    • Region: pick one near you (e.g. Central India) — this is the metadata region.
  5. Tick the box acknowledging the terms (it confirms you accept the Azure Maps licensing).
  6. Click Review + create, then Create.
  7. Expected output: a deployment-succeeded notification within ~30 seconds. Click Go to resource.
  8. In the account’s left menu, open Authentication (or Settings → Authentication). You’ll see Shared Key Authentication with a Primary Key and Secondary Key. Click the copy icon on the Primary Key and keep it handy for Part 4.

Validation: the account’s Overview blade shows status as available and the pricing tier as Gen2/S0. You now have a working Maps account and a key.

Part 2 — Create the same account with the az CLI

If you prefer the command line (or want it reproducible), do this instead of — or in addition to — Part 1. Open Cloud Shell (the >_ icon in the portal) or a local terminal.

  1. Sign in and select your subscription (skip in Cloud Shell, you’re already signed in):
az login
az account set --subscription "<your-subscription-name-or-id>"
  1. Create the resource group:
az group create --name rg-maps-lab --location centralindia

Expected output: JSON with "provisioningState": "Succeeded".

  1. Register the Maps resource provider (only needed once per subscription; harmless if already done):
az provider register --namespace Microsoft.Maps
  1. Create the Azure Maps account on the Gen2 S0 tier:
az maps account create \
  --name maps-lab-cli \
  --resource-group rg-maps-lab \
  --sku S0 \
  --kind Gen2 \
  --accept-tos

Expected output: JSON describing the account, including "name": "maps-lab-cli" and the SKU. The --accept-tos flag is your programmatic acceptance of the licensing terms — the create fails without it.

  1. Retrieve the keys (don’t print these into shared logs):
az maps account keys list \
  --name maps-lab-cli \
  --resource-group rg-maps-lab \
  --query "{primary:primaryKey, secondary:secondaryKey}" -o json

Expected output: a JSON object with primary and secondary key strings. Copy the primary for the lab page.

The az maps account commands you’ll use in this lab and beyond:

Command What it does
az maps account create Creates the account (needs --sku, --kind, --accept-tos)
az maps account show Shows account properties
az maps account list Lists accounts in a group/subscription
az maps account keys list Reads the primary/secondary shared keys
az maps account keys renew Rotates a key (--key primary or --key secondary)
az maps account update Changes tier or other properties
az maps account delete Deletes the account

Part 3 — Declare it as Bicep (infrastructure as code)

For anything you intend to keep, put it in source control. Create a file main.bicep:

@description('Name of the Azure Maps account (globally meaningful, lowercase).')
param mapsAccountName string = 'maps-lab-bicep'

@description('Location for the resource group; Maps metadata is global.')
param location string = resourceGroup().location

resource maps 'Microsoft.Maps/accounts@2023-06-01' = {
  name: mapsAccountName
  location: 'global'              // Azure Maps account location is 'global'
  sku: {
    name: 'S0'                    // Gen2 pay-as-you-go; use 'S1' only for sustained high volume
  }
  kind: 'Gen2'
  properties: {
    disableLocalAuth: false       // true would turn OFF shared-key auth (Entra-only) — keep false for the lab
  }
  tags: {
    purpose: 'maps-first-map-lab'
    owner: 'vinod'
  }
}

output mapsAccountId string = maps.id
output mapsAccountName string = maps.name

Deploy it into the existing resource group:

az deployment group create \
  --resource-group rg-maps-lab \
  --template-file main.bicep

Expected output: "provisioningState": "Succeeded" and the two outputs (mapsAccountId, mapsAccountName). You now have the same account, but reproducibly, reviewable in a pull request. Note disableLocalAuth: leaving it false keeps shared-key auth on (what the lab uses); flipping it to true is the one-line way to enforce Entra-only authentication in production.

The key Bicep properties and what each controls:

Property Values Meaning
location 'global' Azure Maps accounts deploy as global resources
sku.name 'S0' | 'S1' Gen2 pricing tier
kind 'Gen2' The current generation
properties.disableLocalAuth false | true true disables shared keys → Entra-only
tags any map Cost-allocation and ownership metadata

Part 4 — Build the map page and drop a marker

Create a file named index.html. This is the whole app. Replace YOUR_AZURE_MAPS_KEY with the primary key you copied.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>My First Azure Map</title>

  <!-- 1) Azure Maps Web SDK: CSS + JS (both required) -->
  <link rel="stylesheet"
        href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.css" />
  <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.js"></script>

  <style>
    html, body { margin: 0; height: 100%; }
    /* 2) The map container MUST have an explicit, non-zero height */
    #map { width: 100%; height: 480px; }
  </style>
</head>
<body>
  <div id="map"></div>

  <script>
    // 3) Create the map, authenticating with a shared key (LOCAL DEMO ONLY)
    const map = new atlas.Map('map', {
      center: [77.5946, 12.9716],   // [longitude, latitude] — Bengaluru. lon FIRST.
      zoom: 11,
      authOptions: {
        authType: 'subscriptionKey',
        subscriptionKey: 'YOUR_AZURE_MAPS_KEY'
      }
    });

    // 4) Wait until the map is ready, then add a marker + popup
    map.events.add('ready', function () {

      const marker = new atlas.HtmlMarker({
        position: [77.5946, 12.9716],   // same [lon, lat]
        color: '#E81123',
        text: '1'
      });
      map.markers.add(marker);

      const popup = new atlas.Popup({
        content: '<div style="padding:8px">Hello from Bengaluru!</div>',
        pixelOffset: [0, -30]
      });

      // 5) Open the popup when the marker is clicked
      map.events.add('click', marker, function () {
        popup.setOptions({ position: marker.getOptions().position });
        popup.open(map);
      });
    });
  </script>
</body>
</html>

Serve it (the SDK won’t run from a file:// path — it needs a web origin):

# from the folder containing index.html
python3 -m http.server 8080

Then open http://localhost:8080 in your browser.

Expected output at each step:

Step What you should see
Page loads An interactive map centred on Bengaluru
Pan/zoom Smooth movement, labels in view
The marker A red teardrop pin labelled “1” downtown
Click the pin A popup reading “Hello from Bengaluru!”
DevTools → Console No red 401/403 or atlas is not defined errors
DevTools → Network 200 responses from atlas.microsoft.com tile requests

Part 5 — Validate it properly

  1. Visual: the pin sits downtown, not in the sea (in the ocean off Africa means you swapped lat/lon — use [77.59, 12.97]).
  2. Network tab: filtered on atlas.microsoft.com, tile requests return 200 (a 401/403 means a bad or unauthorized key).
  3. Console: no atlas is not defined (SDK script didn’t load) and no auth errors.
  4. CLI cross-check: confirm the account is live and on the right tier:
az maps account show --name maps-lab-cli --resource-group rg-maps-lab \
  --query "{name:name, sku:sku.name, kind:kind}" -o table

Expected output: a one-row table showing your account, S0, Gen2.

Part 6 — (Optional) See the Entra and SAS variants

You don’t deploy these, but here’s exactly what changes for the safe auth modes — only the authOptions block differs; the map and marker code is identical.

Microsoft Entra ID:

authOptions: {
  authType: 'aad',
  aadAppId: '<your-entra-app-client-id>',
  aadTenant: '<your-entra-tenant-id>',
  clientId: '<the-maps-account-client-id>'   // from the account's Authentication blade
}

SAS token (the token is minted server-side and fetched at runtime):

authOptions: {
  authType: 'sas',
  getToken: function (resolve, reject, map) {
    fetch('/api/maps-token')          // your tiny server endpoint returns a short-lived SAS string
      .then(r => r.text())
      .then(resolve)
      .catch(reject);
  }
}

Part 7 — Teardown (do this when done)

A learning resource that lingers becomes a line item. Delete the whole resource group — it removes every account you created in one shot:

az group delete --name rg-maps-lab --yes --no-wait

Expected output: the command returns immediately (--no-wait); deletion completes in the background. Verify after a minute:

az group exists --name rg-maps-lab
# -> false  (once deletion finishes)

In the portal, the equivalent is rg-maps-labDelete resource group → type the name to confirm. Either way, your bill returns to zero. To keep one account instead, delete the others individually with az maps account delete --name <name> --resource-group rg-maps-lab.

Common mistakes & troubleshooting

The “blank grey box” is the single most reported first-map problem, with at least four distinct causes that look identical. Work this table top to bottom — confirm with the exact check, then apply the fix.

# Symptom Root cause How to confirm Fix
1 Blank grey box, atlas is not defined in console SDK <script> didn’t load / loaded after your code Console error names atlas; Network shows atlas.min.js missing or 404 Ensure the SDK <script> is in <head> and loads before your map code
2 Blank box, no console error Container <div> has zero height Inspect #map in DevTools → computed height is 0px Give #map an explicit height, e.g. height: 480px
3 Map area present but 401/403 on tile requests Bad, wrong, or unauthorized key Network tab: tile requests return 401/403 Re-copy the primary key; confirm it’s the right account
4 Markers/controls look broken or invisible SDK CSS not loaded No atlas.min.css in Network; controls unstyled Add the <link> for atlas.min.css
5 Pin lands in the ocean off Africa Coordinates swapped to [lat, lon] Pin near 0,0-ish / Gulf of Guinea Use [lon, lat] — longitude first
6 Map shows but Failed to fetch / CORS errors Page opened via file:// URL bar shows file:///... Serve over http:// (python3 -m http.server)
7 Marker code runs but nothing appears Added the marker before the map was ready No marker; no error Add markers inside the 'ready' event handler
8 Works locally, 403 once deployed Public key blocked / domain not allowed 403 only from the deployed origin Move to SAS/Entra; don’t ship raw keys publicly
9 Intermittent 429 Too Many Requests Throttled — too many transactions Network tab shows 429 Reduce call rate; cache; consider S1 if sustained
10 az maps account create fails immediately Missing terms acceptance Error mentions terms / tos Add --accept-tos to the create command
11 Bicep deploy fails on location Used a region instead of global Error on the location property Set location: 'global' for the Maps account

The one distinction that saves the most time: the blank-box symptom has three causes that look identical, but the tool tells them apart — the Console naming atlas means a script-order/load problem, a computed height of 0 means the container, and a 401/403 in Network means the key.

Best practices

Security notes

The map is harmless; the credential is the entire attack surface, so treat it like one.

Cost & sizing

Azure Maps is metered by transaction, and a first map costs effectively nothing because Gen2 includes a free monthly grant before any pay-as-you-go charges. The free quantities differ per service (renders, searches, route calls each have their own allotment and per-unit price) and are on the Azure Maps pricing page — check current figures before you scale, but for learning and small apps you’ll stay inside the free tier. What drives the bill, and how to keep it small:

Cost driver What it is How to control it
Map render transactions Tile/render usage as users pan/zoom Sensible default zoom; don’t auto-refresh the map
Search transactions Geocoding / place lookups Cache results; debounce search-as-you-type
Route / traffic / weather calls Each call is its own transaction Only call what the feature needs
Leaked-key abuse A scraper hammering your account SAS/Entra auth + spike alerts (the big one)
Wrong tier Paying for S1 headroom you don’t use Start S0; upgrade only on measured need

A rough sense of scale: a store-locator that a few thousand monthly visitors load once or twice each generates a modest number of render transactions — typically inside the free grant, so ₹0 / $0 in practice. The cost risk here is almost never legitimate traffic; it’s an exposed key being abused. Size the tier at S0, protect the credential, set a spike alert, and revisit only when real usage tells you to.

Interview & exam questions

1. What is an Azure Maps account, and what does it actually do? It’s the billable Azure resource that exposes the Maps REST services and issues credentials (shared keys, SAS, Entra access). It doesn’t render maps itself — the browser Web SDK does, authenticating with a credential the account issued.

2. Why are coordinates [longitude, latitude] and not [latitude, longitude]? Azure Maps follows the GeoJSON standard, which orders coordinates X-then-Y, i.e. longitude first. Humans say “lat, long,” so swapping them is a classic bug that lands markers in the wrong place.

3. Why should you not put a shared key in client-side JavaScript? Client code is public — anyone can read the key in DevTools and use your account, spending your quota or running up a bill. Production apps use Microsoft Entra ID tokens or short-lived, rate-capped SAS tokens instead.

4. What’s the difference between Gen2 S0 and S1? Both are the current Gen2 generation; S0 is pay-as-you-go with a free monthly grant and suits most apps, while S1 offers elevated throughput for sustained high-volume workloads. Start on S0 and upgrade only on measured need.

5. A user reports a blank grey box where the map should be. Name three distinct causes. The SDK script didn’t load (atlas is not defined), the container <div> has zero height, or the key is wrong/unauthorized (401/403 on tiles). You distinguish them via the console, computed height, and the Network tab respectively.

6. Which two SDK assets must you load, and what breaks if you forget the CSS? atlas.min.js (the library) and atlas.min.css (the styles). Without the CSS the map may render but markers, popups and controls look broken or invisible because their styles never loaded.

7. What is disableLocalAuth on a Maps account? A property that, when set to true, turns off shared-key authentication so the account accepts only Microsoft Entra ID tokens — the cleanest way to enforce keyless, identity-based access in production.

8. How do you rotate a shared key without downtime? The account has primary and secondary keys; switch your apps to the secondary, regenerate the primary (az maps account keys renew --key primary), then optionally rotate again. Two keys exist precisely to allow zero-downtime rotation.

9. What is a transaction in Azure Maps billing, and why does it matter? A metered unit of usage — render/tile batches, a search, a route call each count. Cost scales with transactions, so a leaked key being scraped, or a runaway loop, directly increases the bill.

10. Where do you add a marker so it actually appears, and why? Inside the map’s 'ready' event handler. Adding to map.markers before the map initialises silently does nothing, because the rendering surface isn’t ready yet.

11. Your map works locally but returns 403 in production. What’s the fix? A raw shared key that was fine locally is blocked or simply must not be shipped publicly. Move to SAS or Entra authentication and serve the credential from a protected server endpoint.

Quick check

  1. In Azure Maps coordinates, which comes first — latitude or longitude?
  2. Name the two SDK files you must include in the page.
  3. Which authentication method is unsafe to ship in public client-side code, and what should you use instead?
  4. You see a blank grey box and the console says atlas is not defined. What’s the cause?
  5. What’s the safest way to make a learning Maps account stop costing money?

Answers

  1. Longitude first — coordinates are [lon, lat] (GeoJSON order).
  2. atlas.min.css and atlas.min.js — both are required; missing CSS breaks marker/control styling.
  3. A shared subscription key; use Microsoft Entra ID or a short-lived SAS token instead.
  4. The SDK <script> didn’t load (or loaded after your code) — put the atlas.min.js tag in <head>.
  5. Delete the resource group (az group delete --name rg-maps-lab --yes) — it removes the account and returns the bill to zero.

Glossary

Next steps

AzureAzure MapsWeb SDKMapsMarkersGeolocationJavaScriptBicep
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading