| name | arcgis-core-maps |
| description | Create 2D and 3D maps using ArcGIS Maps SDK for JavaScript. Use for initializing maps, scenes, views, and navigation. Supports both Map Components (web components) and Core API approaches. |
ArcGIS Core Maps
Use this skill when creating 2D maps (MapView) or 3D scenes (SceneView) with the ArcGIS Maps SDK for JavaScript.
Import Patterns
Direct ESM Imports (Recommended for Build Tools)
Use with Vite, webpack, Rollup, or other build tools:
import Map from "@arcgis/core/Map.js";
import MapView from "@arcgis/core/views/MapView.js";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer.js";
- Tree-shakeable
- Standard JavaScript modules
- Best for production applications
Dynamic Imports (CDN / No Build Tools)
Use with CDN script tags when no build step is available:
const Map = await $arcgis.import("@arcgis/core/Map.js");
const MapView = await $arcgis.import("@arcgis/core/views/MapView.js");
const [FeatureLayer, Graphic] = await $arcgis.import([
"@arcgis/core/layers/FeatureLayer.js",
"@arcgis/core/Graphic.js",
]);
- Works with Map Components (web components)
- No build step required
- Good for quick prototypes and demos
- Requires
<script src="https://js.arcgis.com/5.0/"></script> in HTML
Note: The examples in this skill use Direct ESM imports. For CDN usage, replace import X from "path" with const X = await $arcgis.import("path").
Autocasting vs Explicit Classes (TypeScript)
The ArcGIS SDK supports autocasting - passing plain objects instead of class instances.
When to Use Explicit Classes (Non-Autocast)
Use new SimpleRenderer(), new Point(), etc. when:
- You need instance methods or
instanceof checks
- Building shared internal libraries - constructor APIs surface breaking changes at compile time
- You want strong editor discoverability -
new SimpleRenderer({ ... }) exposes properties clearly
- You mutate objects incrementally - long-lived instances are clearer as real classes
import SimpleRenderer from "@arcgis/core/renderers/SimpleRenderer.js";
import SimpleMarkerSymbol from "@arcgis/core/symbols/SimpleMarkerSymbol.js";
const renderer = new SimpleRenderer({
symbol: new SimpleMarkerSymbol({
color: [226, 119, 40],
size: 8,
}),
});
When to Use Autocasting
Use plain objects with type property when:
- Configuration-heavy code - renderers, symbols, popups are usually data, not behavior
- UI-driven configuration - React state → plain objects → SDK properties is simpler
- Serialization and reuse matter - configs can be stored, diffed, tested, reused
- Property updates after creation -
layer.renderer = { ... } works cleanly in React useEffect
const renderer = {
type: "simple",
symbol: {
type: "simple-marker",
color: [226, 119, 40],
size: 8,
},
} as const;
const renderer = {
type: "simple",
symbol: {
type: "simple-marker",
color: [226, 119, 40],
size: 8,
},
} satisfies __esri.SimpleRendererProperties;
TypeScript Best Practices
The real TypeScript concern is keeping discriminated unions narrow:
const symbol = { type: "simple-marker", color: "red" };
const symbol = { type: "simple-marker", color: "red" } as const;
const symbol: __esri.SimpleMarkerSymbolProperties = {
type: "simple-marker",
color: "red",
};
Recommended Default
- Autocast for configuration (renderers, symbols, popups, labels)
- Explicit classes for behavior (when you need methods or instanceof)
- Use
as const or satisfies to maintain type safety with autocasting
Two Approaches
1. Map Components (Modern - Recommended)
Web components approach using <arcgis-map> and <arcgis-scene>.
2. Core API
Traditional JavaScript approach using Map, MapView, and SceneView classes.
CDN Setup
Map Components Approach
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ArcGIS Map</title>
<style>
html,
body {
height: 100%;
margin: 0;
}
</style>
<script
type="module"
src="https://js.arcgis.com/calcite-components/3.3.3/calcite.esm.js"
></script>
<script src="https://js.arcgis.com/5.0/"></script>
<script
type="module"
src="https://js.arcgis.com/5.0/map-components/"
></script>
</head>
<body>
<arcgis-map basemap="topo-vector" center="-118.24,34.05" zoom="12">
<arcgis-zoom slot="top-left"></arcgis-zoom>
</arcgis-map>
</body>
</html>
Core API Approach
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ArcGIS Map</title>
<style>
html,
body,
#viewDiv {
height: 100%;
margin: 0;
}
</style>
<link
rel="stylesheet"
href="https://js.arcgis.com/5.0/esri/themes/light/main.css"
/>
<script src="https://js.arcgis.com/5.0/"></script>
</head>
<body>
<div id="viewDiv"></div>
<script type="module">
import Map from "@arcgis/core/Map.js";
import MapView from "@arcgis/core/views/MapView.js";
const map = new Map({ basemap: "topo-vector" });
const view = new MapView({
container: "viewDiv",
map: map,
center: [-118.24, 34.05],
zoom: 12,
});
</script>
</body>
</html>
2D Maps
Map Components
<arcgis-map basemap="topo-vector" center="-118.24,34.05" zoom="12">
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-compass slot="top-left"></arcgis-compass>
<arcgis-home slot="top-left"></arcgis-home>
<arcgis-locate slot="top-left"></arcgis-locate>
</arcgis-map>
<script type="module">
const mapElement = document.querySelector("arcgis-map");
await mapElement.viewOnReady();
const view = mapElement.view;
const map = mapElement.map;
</script>
Core API
import Map from "@arcgis/core/Map.js";
import MapView from "@arcgis/core/views/MapView.js";
const map = new Map({ basemap: "streets-vector" });
const view = new MapView({
container: "viewDiv",
map: map,
center: [-118.24, 34.05],
zoom: 12,
constraints: {
minZoom: 5,
maxZoom: 18,
rotationEnabled: false,
},
});
3D Scenes
Map Components
<arcgis-scene basemap="topo-3d" ground="world-elevation">
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
</arcgis-scene>
<script type="module">
const sceneElement = document.querySelector("arcgis-scene");
await sceneElement.viewOnReady();
const view = sceneElement.view;
</script>
Core API
import Map from "@arcgis/core/Map.js";
import SceneView from "@arcgis/core/views/SceneView.js";
const map = new Map({
basemap: "topo-3d",
ground: "world-elevation",
});
const view = new SceneView({
container: "viewDiv",
map: map,
camera: {
position: {
longitude: -118.24,
latitude: 34.05,
z: 25000,
},
heading: 0,
tilt: 45,
},
});
Loading WebMaps and WebScenes
WebMap (2D)
<arcgis-map item-id="f2e9b762544945f390ca4ac3671cfa72">
<arcgis-zoom slot="top-left"></arcgis-zoom>
</arcgis-map>
import MapView from "@arcgis/core/views/MapView.js";
import WebMap from "@arcgis/core/WebMap.js";
const webmap = new WebMap({
portalItem: { id: "f2e9b762544945f390ca4ac3671cfa72" },
});
const view = new MapView({
map: webmap,
container: "viewDiv",
});
WebScene (3D)
<arcgis-scene item-id="YOUR_WEBSCENE_ID">
<arcgis-zoom slot="top-left"></arcgis-zoom>
</arcgis-scene>
import SceneView from "@arcgis/core/views/SceneView.js";
import WebScene from "@arcgis/core/WebScene.js";
const webscene = new WebScene({
portalItem: { id: "YOUR_WEBSCENE_ID" },
});
const view = new SceneView({
map: webscene,
container: "viewDiv",
});
Navigation Components
| Component | Purpose |
|---|
arcgis-zoom | Zoom in/out buttons |
arcgis-compass | Orientation indicator, click to reset north |
arcgis-home | Return to initial extent |
arcgis-locate | Find user's location |
arcgis-navigation-toggle | Switch between pan/rotate modes (3D) |
arcgis-fullscreen | Toggle fullscreen mode |
arcgis-scale-bar | Display map scale |
Slot Positions
<arcgis-map basemap="streets-vector">
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-home slot="top-left"></arcgis-home>
<arcgis-search slot="top-right"></arcgis-search>
<arcgis-legend slot="bottom-left"></arcgis-legend>
<arcgis-scale-bar slot="bottom-right"></arcgis-scale-bar>
</arcgis-map>
Available slots: top-left, top-right, bottom-left, bottom-right, top-start, top-end, bottom-start, bottom-end
Component Attributes
The <arcgis-map> / <arcgis-scene> components expose many declarative attributes beyond basemap, center, zoom. High-value ones:
| Attribute | Type | Purpose |
|---|
item-id | string | Load a WebMap/WebScene from ArcGIS Online or Enterprise |
rotation | number | Map rotation in degrees |
scale | number | Initial scale (e.g. 50000 for 1:50,000) |
extent | Extent (autocast) | Initial visible extent |
viewpoint | Viewpoint (autocast) | Combined center + scale + rotation |
spatial-reference | SpatialReference | Coordinate system (WKID) |
time-extent | TimeExtent | Temporal filter for time-aware layers |
animations-disabled | boolean | Disable all goTo animations |
popup-disabled | boolean | Disable the auto-popup on click |
popup-component-enabled | boolean | Use the new Popup component (beta) instead of the widget |
auto-destroy-disabled | boolean | Keep the view alive across unmount — critical for React strict mode and SPA route changes |
attribution-mode | "dark" | "light" | Attribution text theme |
hide-attribution | boolean | Hide attribution (check the SDK license terms first) |
Some properties are only settable via JavaScript (not as attributes):
| Property | Type | Purpose |
|---|
padding | ViewPadding | Offset the UI logical area when side panels cover part of the map |
theme | Theme | Light / dark rendering theme |
background | ColorBackground | Background color for non-tiled regions |
graphics | Collection | View-level graphics, independent of any layer |
highlights | Collection | Up to 6 highlight option sets for feature highlighting |
analyses | Collection | 3D analyses (line-of-sight, viewshed) — SceneView only |
aria | ARIAProperties | Accessibility labels and descriptions |
<arcgis-map
auto-destroy-disabled
basemap="topo-vector"
center="-118.24,34.05"
zoom="12"
></arcgis-map>
const mapElement = document.querySelector("arcgis-map");
await mapElement.viewOnReady();
mapElement.padding = { left: 320 };
Map Properties
| Property | Type | Description |
|---|
basemap | string or Basemap | Base layer(s) for the map |
ground | string or Ground | Surface properties (elevation) |
layers | Collection | Operational layers |
tables | Collection | Non-spatial tables |
allLayers | Collection (readonly) | Flattened layer collection |
allTables | Collection (readonly) | Flattened table collection |
editableLayers | Collection (readonly) | Layers that support editing |
focusAreas | Collection (readonly) | Focus areas for the map |
Map Methods
| Method | Returns | Purpose |
|---|
add(layer, index?) | void | Add a layer, optionally at a specific index |
addMany(layers, index?) | void | Add multiple layers in one call |
remove(layer) | Layer | Remove a single layer |
removeMany(layers) | Layer[] | Remove multiple layers |
removeAll() | Layer[] | Remove all operational layers |
reorder(layer, index?) | Layer | Change layer draw order |
findLayerById(id) | Layer | Find a layer by its id property |
findTableById(id) | Layer | Find a table by its id property |
destroy() | void | Release the map, its layers, basemap, ground |
map.add(baseLayer, 0);
map.addMany([featureLayer, graphicsLayer, imageryLayer]);
map.reorder(roadsLayer, 2);
const roads = map.findLayerById("roads");
if (roads) roads.visible = false;
Pattern: A single Map instance can be shared across both a MapView (2D) and a SceneView (3D). User interaction happens on the view, not the map, so two views over the same map stay in sync for layers while maintaining independent camera/extent state.
View Configuration
Setting Initial Extent
const view = new MapView({
container: "viewDiv",
map: map,
center: [-118.24, 34.05],
zoom: 12,
});
const view = new MapView({
container: "viewDiv",
map: map,
center: [-118.24, 34.05],
scale: 50000,
});
const view = new MapView({
container: "viewDiv",
map: map,
extent: {
xmin: -118.5,
ymin: 33.8,
xmax: -117.9,
ymax: 34.3,
spatialReference: { wkid: 4326 },
},
});
Programmatic Navigation
await view.goTo({
center: [-118.24, 34.05],
zoom: 15,
});
await view.goTo(
{ center: [-118.24, 34.05], zoom: 15 },
{ duration: 2000, easing: "ease-in-out" },
);
await view.goTo(layer.fullExtent);
await view.goTo(featureSet.features);
View Constraints
view.constraints = {
minZoom: 5,
maxZoom: 18,
};
view.constraints = {
geometry: layer.fullExtent,
minScale: 500000,
};
view.constraints = {
rotationEnabled: false,
};
View State Properties
| Property | Type | Description |
|---|
ready | boolean | Whether the view is fully initialized |
updating | boolean | Whether any layer or the view is updating |
stationary | boolean | True when no navigation is in progress |
interacting | boolean | True when user is interacting (pan/zoom) |
suspended | boolean | True when the view is not visible |
focused | boolean | True when the view has keyboard focus |
resolution | number | Map units per pixel |
Event Handling
Component Events
Map Components fire DOM events on the <arcgis-map> / <arcgis-scene> element itself, prefixed with arcgisView. These mirror the view events but can be handled via addEventListener on the element — including before the view is ready.
const mapElement = document.querySelector("arcgis-map");
mapElement.addEventListener("arcgisViewReadyChange", () => {
if (mapElement.ready) {
const view = mapElement.view;
const map = mapElement.map;
}
});
When to use which:
- Component events on the element — declarative, framework-friendly, work before the view is ready. Good for React/Vue patterns that want DOM-style event listeners.
- View events via
view.on(...) — imperative, return a handle for cleanup, work identically to Core API code.
Most view events have a corresponding arcgisView* component event. See the arcgis-map component reference for the full list.
View Events
view.when(() => {
console.log("View is ready");
});
view.on("click", (event) => {
console.log("Clicked at:", event.mapPoint);
});
view.on("pointer-move", (event) => {
const point = view.toMap(event);
console.log("Mouse at:", point.longitude, point.latitude);
});
view.on("click", async (event) => {
const response = await view.hitTest(event);
if (response.results.length > 0) {
const graphic = response.results[0].graphic;
console.log("Clicked feature:", graphic.attributes);
}
});
import * as reactiveUtils from "@arcgis/core/core/reactiveUtils.js";
reactiveUtils.watch(
() => view.extent,
(extent) => console.log("Extent changed:", extent),
);
reactiveUtils.watch(
() => view.stationary,
(isStationary) => {
if (isStationary) {
console.log("Navigation complete");
}
},
);
View Events Reference
| Event | Description |
|---|
click | User clicks on the view |
double-click | User double-clicks on the view |
immediate-click | Fires immediately on click (no double-click delay) |
pointer-move | Mouse/touch pointer moves over the view |
pointer-down / pointer-up | Pointer press/release |
pointer-enter / pointer-leave | Pointer enters/leaves the view |
key-down / key-up | Keyboard events when view is focused |
drag | User drags (pan) on the view |
hold | User holds pointer on the view |
mouse-wheel | Mouse wheel scroll |
resize | View container resizes |
focus / blur | View gains/loses keyboard focus |
layerview-create | A layer view is created |
layerview-destroy | A layer view is destroyed |
Common Basemaps
| Basemap ID | Description |
|---|
streets-vector | Street map |
topo-vector | Topographic |
satellite | Satellite imagery |
hybrid | Satellite with labels |
dark-gray-vector | Dark gray canvas |
gray-vector | Light gray canvas |
osm | OpenStreetMap |
topo-3d | 3D topographic (SceneView) |
Planetary Visualization (Mars)
Mars Scene
<arcgis-scene>
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-navigation-toggle slot="top-left"></arcgis-navigation-toggle>
</arcgis-scene>
<script type="module">
import ElevationLayer from "@arcgis/core/layers/ElevationLayer.js";
import TileLayer from "@arcgis/core/layers/TileLayer.js";
const viewElement = document.querySelector("arcgis-scene");
viewElement.spatialReference = { wkid: 104971 };
viewElement.camera = {
position: {
x: 27.63423,
y: -6.34466,
z: 1281525,
spatialReference: { wkid: 104971 },
},
heading: 332,
tilt: 37,
};
await viewElement.viewOnReady();
const marsElevation = new ElevationLayer({
url: "https://astro.arcgis.com/arcgis/rest/services/OnMars/MDEM200M/ImageServer",
});
viewElement.ground = { layers: [marsElevation] };
const marsImagery = new TileLayer({
url: "https://astro.arcgis.com/arcgis/rest/services/OnMars/MDIM/MapServer",
title: "Mars Imagery",
});
viewElement.map.add(marsImagery);
</script>
Overview Map (Synchronized Views)
Overview Map with Scene
<arcgis-scene basemap="hybrid" ground="world-elevation">
<arcgis-zoom slot="top-left"></arcgis-zoom>
<arcgis-map
basemap="topo-vector"
id="overviewDiv"
slot="top-right"
></arcgis-map>
</arcgis-scene>
<style>
#overviewDiv {
width: 300px;
height: 200px;
border: 1px solid black;
}
</style>
<script type="module">
import Graphic from "@arcgis/core/Graphic.js";
import * as reactiveUtils from "@arcgis/core/core/reactiveUtils.js";
const sceneElement = document.querySelector("arcgis-scene");
const overviewElement = document.querySelector("arcgis-map");
await sceneElement.viewOnReady();
await overviewElement.viewOnReady();
overviewElement.constraints.rotationEnabled = false;
overviewElement.view.ui.components = [];
const visibleAreaGraphic = new Graphic({
symbol: {
type: "simple-fill",
color: [0, 0, 0, 0.5],
outline: null,
},
});
overviewElement.graphics.add(visibleAreaGraphic);
reactiveUtils.watch(
() => sceneElement.visibleArea,
async (visibleArea) => {
visibleAreaGraphic.geometry = visibleArea;
await overviewElement.goTo(visibleArea);
},
{ initial: true },
);
</script>
Reference Samples
intro-mapview - Basic MapView setup and configuration
intro-sceneview - Basic SceneView setup for 3D
webmap-basic - Loading a WebMap from portal
watch-for-changes-reactiveutils - Reactive property watching with reactiveUtils
overview-map - Creating an overview/inset map
Related Skills
- See
arcgis-layers for adding data layers to maps.
- See
arcgis-starter-app for project setup and scaffolding.
- See
arcgis-core-utilities for reactiveUtils and promiseUtils details.
- See
arcgis-interaction for hit testing, popups, and user interaction.
Common Pitfalls
-
Missing CSS for Core API: The Core API requires main.css for widgets and popups to render correctly.
<script type="module">
import MapView from "@arcgis/core/views/MapView.js";
import Map from "@arcgis/core/Map.js";
const view = new MapView({
container: "viewDiv",
map: new Map({ basemap: "topo-vector" }),
});
</script>
<link
rel="stylesheet"
href="https://js.arcgis.com/5.0/esri/themes/light/main.css"
/>
<script type="module">
import MapView from "@arcgis/core/views/MapView.js";
import Map from "@arcgis/core/Map.js";
const view = new MapView({
container: "viewDiv",
map: new Map({ basemap: "topo-vector" }),
});
</script>
Impact: The map itself renders, but widgets (Zoom, Legend, Search) and popups appear unstyled or completely broken. Layouts collapse and controls become unusable.
-
Not awaiting viewOnReady(): View properties are not available until the view is ready.
const mapElement = document.querySelector("arcgis-map");
const view = mapElement.view;
view.goTo({ center: [-118, 34] });
const mapElement = document.querySelector("arcgis-map");
await mapElement.viewOnReady();
const view = mapElement.view;
view.goTo({ center: [-118, 34] });
Impact: view is null or undefined before the component initializes, causing runtime errors on any property access or method call.
-
Coordinate order: ArcGIS uses [longitude, latitude], not [latitude, longitude].
const view = new MapView({
center: [34.05, -118.24],
zoom: 12,
});
const view = new MapView({
center: [-118.24, 34.05],
zoom: 12,
});
Impact: The map centers on the wrong location, often in the middle of the ocean or on a different continent, with no error message to indicate the mistake.
-
Missing viewDiv height: Ensure the container has height:
html,
body,
#viewDiv {
height: 100%;
margin: 0;
}
-
Script type: Use type="module" for async/await support:
<script type="module">
</script>
-
Layers belong to exactly one Map: Adding a layer to a second Map silently removes it from the first.
const layer = new FeatureLayer({ url: "..." });
const map1 = new Map({ layers: [layer] });
const map2 = new Map({ layers: [layer] });
const layer1 = new FeatureLayer({ url: "..." });
const layer2 = new FeatureLayer({ url: "..." });
const map1 = new Map({ layers: [layer1] });
const map2 = new Map({ layers: [layer2] });
Impact: The layer appears only in the most recently created map, with no error or warning. Common when toggling between 2D and 3D with separate map instances rather than sharing one map across two views.