arcgis-popup-templates

ArcGIS Popup Templates

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "arcgis-popup-templates" with this command: npx skills add saschabrunnerch/arcgis-maps-sdk-js-ai-context/saschabrunnerch-arcgis-maps-sdk-js-ai-context-arcgis-popup-templates

ArcGIS Popup Templates

Use this skill for creating and customizing popup templates with various content types.

PopupTemplate Overview

Content Type Purpose

TextContent HTML or plain text

FieldsContent Attribute table

MediaContent Charts and images

AttachmentsContent File attachments

ExpressionContent Arcade expression results

CustomContent Custom HTML/JavaScript

RelationshipContent Related records

Basic PopupTemplate

layer.popupTemplate = { title: "{name}", content: "Population: {population}<br>Area: {area} sq mi" };

With Field Substitution

layer.popupTemplate = { title: "{city_name}, {state}", content: &#x3C;h3>Demographics&#x3C;/h3> &#x3C;p>Population: {population:NumberFormat(places: 0)}&#x3C;/p> &#x3C;p>Median Income: {median_income:NumberFormat(digitSeparator: true, places: 0)}&#x3C;/p> &#x3C;p>Founded: {founded_date:DateFormat(selector: 'date', datePattern: 'MMMM d, yyyy')}&#x3C;/p> };

Content Array (Multiple Content Types)

layer.popupTemplate = { title: "{name}", content: [ { type: "text", text: "<b>Overview</b><br>{description}" }, { type: "fields", fieldInfos: [ { fieldName: "population", label: "Population" }, { fieldName: "area", label: "Area (sq mi)" } ] }, { type: "media", mediaInfos: [{ type: "pie-chart", title: "Demographics", value: { fields: ["white", "black", "asian", "other"] } }] } ] };

Content Types

TextContent

Display HTML or text.

{ type: "text", text: &#x3C;div style="padding: 10px;"> &#x3C;h2>{name}&#x3C;/h2> &#x3C;p>{description}&#x3C;/p> &#x3C;a href="{website}" target="_blank">Visit Website&#x3C;/a> &#x3C;/div> }

FieldsContent

Display attributes as a table.

{ type: "fields", fieldInfos: [ { fieldName: "name", label: "Name" }, { fieldName: "population", label: "Population", format: { digitSeparator: true, places: 0 } }, { fieldName: "date_created", label: "Created", format: { dateFormat: "short-date" } }, { fieldName: "percentage", label: "Percentage", format: { places: 2, digitSeparator: true } } ] }

Date Formats

  • short-date

  • 12/30/2024

  • short-date-short-time

  • 12/30/2024, 3:30 PM

  • short-date-short-time-24

  • 12/30/2024, 15:30

  • short-date-long-time

  • 12/30/2024, 3:30:45 PM

  • short-date-long-time-24

  • 12/30/2024, 15:30:45

  • long-month-day-year

  • December 30, 2024

  • long-month-day-year-short-time

  • December 30, 2024, 3:30 PM

  • long-month-day-year-long-time

  • December 30, 2024, 3:30:45 PM

  • day-short-month-year

  • 30 Dec 2024

  • year

  • 2024

MediaContent

Display charts or images.

{ type: "media", mediaInfos: [ { title: "Sales by Quarter", type: "column-chart", // bar-chart, pie-chart, line-chart, column-chart, image value: { fields: ["q1_sales", "q2_sales", "q3_sales", "q4_sales"], normalizeField: "total_sales" // Optional } } ] }

Chart Types

Bar Chart

{ type: "bar-chart", title: "Population by Age", value: { fields: ["age_0_17", "age_18_34", "age_35_54", "age_55_plus"] } }

Pie Chart

{ type: "pie-chart", title: "Land Use Distribution", value: { fields: ["residential", "commercial", "industrial", "agricultural"] } }

Line Chart

{ type: "line-chart", title: "Monthly Sales", value: { fields: ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"] } }

Column Chart

{ type: "column-chart", title: "Revenue vs Expenses", value: { fields: ["revenue", "expenses"] } }

Image

{ type: "image", title: "Property Photo", value: { sourceURL: "{image_url}", linkURL: "{detail_page_url}" } }

// Fixed image { type: "image", value: { sourceURL: "https://example.com/logo.png" } }

AttachmentsContent

Display file attachments.

{ type: "attachments", displayType: "preview", // preview, list, auto title: "Photos" }

// With custom display { type: "attachments", displayType: "list", title: "Documents", description: "Related files for this record" }

ExpressionContent

Display Arcade expression results.

// First, define expression in expressionInfos layer.popupTemplate = { expressionInfos: [ { name: "population-density", title: "Population Density", expression: "Round($feature.population / $feature.area, 2)" }, { name: "age-category", title: "Age Category", expression: var age = $feature.building_age; if (age &#x3C; 25) return "New"; if (age &#x3C; 50) return "Moderate"; return "Historic"; } ], content: [ { type: "expression", expressionInfo: { name: "population-density" } } ] };

CustomContent

Create fully custom content with JavaScript.

import CustomContent from "@arcgis/core/popup/content/CustomContent.js";

const customContent = new CustomContent({ outFields: ["*"], creator: (event) => { const div = document.createElement("div"); const graphic = event.graphic;

div.innerHTML = `
  &#x3C;div class="custom-popup">
    &#x3C;h3>${graphic.attributes.name}&#x3C;/h3>
    &#x3C;canvas id="chart-${graphic.attributes.OBJECTID}">&#x3C;/canvas>
  &#x3C;/div>
`;

// Add custom logic, charts, etc.
return div;

} });

layer.popupTemplate = { title: "{name}", content: [customContent] };

RelationshipContent

Display related records.

{ type: "relationship", relationshipId: 0, // Relationship ID from the layer title: "Related Inspections", displayCount: 5, orderByFields: [ { field: "inspection_date", order: "desc" } ] }

FieldInfo Configuration

Basic FieldInfo

const fieldInfo = { fieldName: "population", label: "Population", visible: true, isEditable: false, statisticType: "sum" // For clustering: sum, min, max, avg, count };

Number Formatting

{ fieldName: "revenue", label: "Annual Revenue", format: { digitSeparator: true, places: 2 } }

// Currency (manual) { fieldName: "price", label: "Price", format: { digitSeparator: true, places: 2 } } // Use expression for currency symbol

Date Formatting

{ fieldName: "created_date", label: "Created", format: { dateFormat: "long-month-day-year" } }

Tooltip

{ fieldName: "status", label: "Status", tooltip: "Current processing status of the request" }

RelatedRecordsInfo

Configure how related records are displayed.

layer.popupTemplate = { title: "{name}", content: [{ type: "relationship", relationshipId: 0 }], relatedRecordsInfo: { showRelatedRecords: true, orderByFields: [{ field: "date", order: "desc" }] } };

Actions

Add custom buttons to popups.

layer.popupTemplate = { title: "{name}", content: "...", actions: [ { id: "zoom-to", title: "Zoom To", className: "esri-icon-zoom-in-magnifying-glass" }, { id: "edit", title: "Edit", className: "esri-icon-edit" }, { id: "delete", title: "Delete", className: "esri-icon-trash" } ] };

// Handle action clicks view.popup.on("trigger-action", (event) => { if (event.action.id === "zoom-to") { view.goTo(view.popup.selectedFeature); } else if (event.action.id === "edit") { // Open editor startEditing(view.popup.selectedFeature); } else if (event.action.id === "delete") { // Delete feature deleteFeature(view.popup.selectedFeature); } });

Action Button Types

// Icon button { id: "info", title: "More Info", className: "esri-icon-description" }

// Text button { id: "report", title: "Generate Report", type: "button" }

// Toggle button { id: "highlight", title: "Highlight", type: "toggle", value: false }

Dynamic Content with Functions

Content as Function

layer.popupTemplate = { title: "{name}", content: (feature) => { const attributes = feature.graphic.attributes;

// Conditional content
if (attributes.type === "residential") {
  return `
    &#x3C;h3>Residential Property&#x3C;/h3>
    &#x3C;p>Bedrooms: ${attributes.bedrooms}&#x3C;/p>
    &#x3C;p>Bathrooms: ${attributes.bathrooms}&#x3C;/p>
  `;
} else {
  return `
    &#x3C;h3>Commercial Property&#x3C;/h3>
    &#x3C;p>Square Footage: ${attributes.sqft}&#x3C;/p>
    &#x3C;p>Zoning: ${attributes.zoning}&#x3C;/p>
  `;
}

} };

Async Content Function

layer.popupTemplate = { title: "{name}", content: async (feature) => { const id = feature.graphic.attributes.OBJECTID;

// Fetch additional data
const response = await fetch(`/api/details/${id}`);
const data = await response.json();

return `
  &#x3C;h3>${data.title}&#x3C;/h3>
  &#x3C;p>${data.description}&#x3C;/p>
  &#x3C;img src="${data.imageUrl}" />
`;

} };

Arcade Expressions

In Title

layer.popupTemplate = { title: { expression: var name = $feature.name; var status = $feature.status; return name + " (" + status + ")"; }, content: "..." };

Expression Infos

layer.popupTemplate = { expressionInfos: [ { name: "formatted-date", title: "Formatted Date", expression: var d = $feature.created_date; return Text(d, "MMMM D, YYYY"); }, { name: "calculated-field", title: "Density", expression: "Round($feature.population / AreaGeodetic($feature, 'square-miles'), 1)" }, { name: "conditional-value", title: "Status", expression: var val = $feature.score; if (val >= 80) return "Excellent"; if (val >= 60) return "Good"; if (val >= 40) return "Fair"; return "Poor"; } ], content: [ { type: "fields", fieldInfos: [ { fieldName: "expression/formatted-date", label: "Created" }, { fieldName: "expression/calculated-field", label: "Population Density" }, { fieldName: "expression/conditional-value", label: "Rating" } ] } ] };

OutFields

Specify which fields to retrieve.

layer.popupTemplate = { title: "{name}", content: "...", outFields: ["name", "population", "area", "created_date"] };

// All fields layer.popupTemplate = { title: "{name}", content: "...", outFields: ["*"] };

Last Edit Info

Show who last edited the feature.

layer.popupTemplate = { title: "{name}", content: "...", lastEditInfoEnabled: true };

Popup Template from JSON

const popupTemplate = { title: "{name}", content: [{ type: "fields", fieldInfos: [ { fieldName: "category", label: "Category" }, { fieldName: "value", label: "Value" } ] }], outFields: ["name", "category", "value"] };

// Apply to layer layer.popupTemplate = popupTemplate;

// Or create from class import PopupTemplate from "@arcgis/core/PopupTemplate.js"; layer.popupTemplate = new PopupTemplate(popupTemplate);

Layer-Specific Popup

FeatureLayer with Popup

const featureLayer = new FeatureLayer({ url: "https://services.arcgis.com/.../FeatureServer/0", popupTemplate: { title: "{NAME}", content: [{ type: "fields", fieldInfos: [ { fieldName: "NAME", label: "Name" }, { fieldName: "POP2020", label: "Population (2020)", format: { digitSeparator: true } } ] }] } });

GeoJSONLayer with Popup

const geoJsonLayer = new GeoJSONLayer({ url: "data.geojson", popupTemplate: { title: "{properties/name}", // Note: GeoJSON uses properties/fieldName content: "{properties/description}" } });

GraphicsLayer Popup

const graphic = new Graphic({ geometry: point, attributes: { name: "Location A", value: 100 }, popupTemplate: { title: "{name}", content: "Value: {value}" } });

graphicsLayer.add(graphic);

Clustering Popups

layer.featureReduction = { type: "cluster", clusterRadius: 80, popupTemplate: { title: "Cluster of {cluster_count} features", content: [{ type: "fields", fieldInfos: [ { fieldName: "cluster_count", label: "Features in cluster" }, { fieldName: "cluster_avg_population", label: "Average Population", format: { digitSeparator: true, places: 0 } } ] }] }, clusterMinSize: 16, clusterMaxSize: 60, fields: [{ name: "cluster_avg_population", alias: "Average Population", onStatisticField: "population", statisticType: "avg" }] };

Common Patterns

Popup with Image Gallery

layer.popupTemplate = { title: "{name}", content: [ { type: "text", text: "{description}" }, { type: "attachments", displayType: "preview" } ] };

Multi-Tab Style Popup

layer.popupTemplate = { title: "{name}", content: [ { type: "text", text: "<b>General Information</b>" }, { type: "fields", fieldInfos: [ { fieldName: "category", label: "Category" }, { fieldName: "status", label: "Status" } ] }, { type: "text", text: "<hr><b>Statistics</b>" }, { type: "media", mediaInfos: [{ type: "pie-chart", title: "Distribution", value: { fields: ["typeA", "typeB", "typeC"] } }] } ] };

Conditional Content

layer.popupTemplate = { title: "{name}", expressionInfos: [{ name: "show-warning", expression: "$feature.risk_level > 7" }], content: (feature) => { const content = [{ type: "fields", fieldInfos: [ { fieldName: "name", label: "Name" }, { fieldName: "risk_level", label: "Risk Level" } ] }];

if (feature.graphic.attributes.risk_level > 7) {
  content.unshift({
    type: "text",
    text: '&#x3C;div style="background: #ffcccc; padding: 10px;">⚠️ High Risk Area&#x3C;/div>'
  });
}

return content;

} };

TypeScript Usage

PopupTemplate content uses autocasting with type properties. For TypeScript safety, use as const :

// Use 'as const' for type safety layer.popupTemplate = { title: "{name}", content: [ { type: "fields", fieldInfos: [ { fieldName: "name", label: "Name" }, { fieldName: "population", label: "Population" } ] }, { type: "media", mediaInfos: [{ type: "pie-chart", value: { fields: ["typeA", "typeB"] } }] } ] } as const;

Tip: See arcgis-core-maps skill for detailed guidance on autocasting vs explicit classes.

Reference Samples

  • intro-popuptemplate

  • Basic PopupTemplate configuration

  • popup-actions

  • Adding custom actions to popups

  • popup-customcontent

  • Custom popup content elements

  • popuptemplate-arcade

  • Using Arcade expressions in popups

  • popup-multipleelements

  • Multiple content elements in popups

Common Pitfalls

Field Names Case Sensitive: Field names must match exactly

// If field is "Population" (capital P) content: "{Population}" // Correct content: "{population}" // Wrong - shows literal {population}

OutFields Required: Fields used in popup must be in outFields

popupTemplate: { title: "{name}", content: "{description}", outFields: ["name", "description"] // Both required }

GeoJSON Field Path: GeoJSON requires properties/ prefix

// GeoJSON title: "{properties/name}" // Regular FeatureLayer title: "{name}"

Expression Reference: Use expression/ prefix for Arcade expressions

fieldInfos: [ { fieldName: "expression/my-expression", label: "Calculated" } ]

Async Content: Function content must return a value or Promise

// Wrong - no return content: (feature) => { const div = document.createElement("div"); }

// Correct content: (feature) => { const div = document.createElement("div"); return div; }

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

arcgis-widgets-ui

No summary provided by upstream source.

Repository SourceNeeds Review
General

arcgis-geometry-operations

No summary provided by upstream source.

Repository SourceNeeds Review
General

arcgis-core-maps

No summary provided by upstream source.

Repository SourceNeeds Review
General

arcgis-authentication

No summary provided by upstream source.

Repository SourceNeeds Review