Help us improve
Share bugs, ideas, or general feedback.
From arcgis-maps-sdk-js-ai-context
Writes Arcade expressions for dynamic calculations in ArcGIS popups, renderers, labels, and field calculations. Use for data-driven styling, custom labels, and computed fields.
npx claudepluginhub saschabrunnerch/arcgis-maps-sdk-js-ai-context --plugin arcgis-maps-sdk-js-ai-contextHow this skill is triggered — by the user, by Claude, or both
Slash command
/arcgis-maps-sdk-js-ai-context:arcgis-arcadeThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill for writing Arcade expressions for popups, renderers, labels, and calculations.
Configures rich popup templates for ArcGIS features using text, fields, media, charts, attachments, and related records. Use for customizing popups with field formatting, images, Arcade expressions, or related data display.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Share bugs, ideas, or general feedback.
Use this skill for writing Arcade expressions for popups, renderers, labels, and calculations.
import * as arcade from "@arcgis/core/arcade.js";
const arcade = await $arcgis.import("@arcgis/core/arcade.js");
Arcade is an expression language for ArcGIS. It's used for:
// Variables
var population = $feature.population;
var area = $feature.area_sqkm;
// Calculations
var density = population / area;
// Return result
return Round(density, 2);
const popupTemplate = {
title: "{name}",
expressionInfos: [
{
name: "population-density",
title: "Population Density",
expression: "Round($feature.population / $feature.area_sqkm, 2)",
},
{
name: "formatted-date",
title: "Formatted Date",
expression: "Text($feature.created_date, 'MMMM D, YYYY')",
},
],
content: "Density: {expression/population-density} people/km²",
};
const popupTemplate = {
title: "{name}",
expressionInfos: [
{
name: "predominant-category",
title: "Predominant Category",
expression: `
var fields = [
{ value: $feature.category_a, alias: "Category A" },
{ value: $feature.category_b, alias: "Category B" },
{ value: $feature.category_c, alias: "Category C" }
];
var maxValue = -Infinity;
var maxCategory = "";
for (var i in fields) {
if (fields[i].value > maxValue) {
maxValue = fields[i].value;
maxCategory = fields[i].alias;
}
}
return maxCategory;
`,
},
],
content: [
{
type: "text",
text: "The predominant category is: {expression/predominant-category}",
},
{
type: "fields",
fieldInfos: [
{
fieldName: "expression/predominant-category",
},
],
},
],
};
const renderer = {
type: "unique-value",
valueExpression: `
var labor = $feature.labor_force;
var notLabor = $feature.not_in_labor_force;
if (labor > notLabor) {
return "In labor force";
} else {
return "Not in labor force";
}
`,
valueExpressionTitle: "Labor Force Status",
uniqueValueInfos: [
{
value: "In labor force",
symbol: { type: "simple-fill", color: "blue" },
},
{
value: "Not in labor force",
symbol: { type: "simple-fill", color: "orange" },
},
],
};
const renderer = {
type: "simple",
symbol: { type: "simple-marker", color: "red" },
visualVariables: [
{
type: "size",
valueExpression: "Sqrt($feature.population) * 0.1",
valueExpressionTitle: "Population (scaled)",
stops: [
{ value: 10, size: 4 },
{ value: 100, size: 40 },
],
},
{
type: "opacity",
valueExpression: "($feature.value / $feature.max_value) * 100",
valueExpressionTitle: "Percentage of max",
stops: [
{ value: 20, opacity: 0.2 },
{ value: 80, opacity: 1 },
],
},
],
};
layer.labelingInfo = [
{
symbol: {
type: "text",
color: "black",
font: { size: 10 },
},
labelExpressionInfo: {
expression: `
var name = $feature.name;
var pop = $feature.population;
if (pop > 1000000) {
return name + " (" + Round(pop/1000000, 1) + "M)";
} else if (pop > 1000) {
return name + " (" + Round(pop/1000, 0) + "K)";
}
return name;
`,
},
where: "population > 50000",
},
];
Round(3.14159, 2) // 3.14
Floor(3.9) // 3
Ceil(3.1) // 4
Abs(-5) // 5
Sqrt(16) // 4
Pow(2, 3) // 8
Min(1, 2, 3) // 1
Max(1, 2, 3) // 3
Sum([1, 2, 3]) // 6
Mean([1, 2, 3]) // 2
Upper("hello") // "HELLO"
Lower("HELLO") // "hello"
Trim(" hello ") // "hello"
Left("hello", 2) // "he"
Right("hello", 2) // "lo"
Mid("hello", 2, 2) // "ll"
Find("l", "hello") // 2
Replace("hello", "l", "L") // "heLLo"
Split("a,b,c", ",") // ["a", "b", "c"]
Concatenate(["a", "b"]) // "ab"
Now() // Current date/time
Today() // Current date
Year($feature.date_field) // Extract year
Month($feature.date_field) // Extract month (1-12)
Day($feature.date_field) // Extract day
DateDiff(Now(), $feature.date, "days") // Days between dates
Text($feature.date, "MMMM D, YYYY") // Format date
Area($feature, "square-kilometers")
Length($feature, "kilometers")
Centroid($feature)
Buffer($feature, 100, "meters")
Intersects($feature, $otherFeature)
Contains($feature, $point)
// IIf (inline if)
IIf($feature.value > 100, "High", "Low")
// When (multiple conditions)
When(
$feature.type == "A", "Type A",
$feature.type == "B", "Type B",
"Other"
)
// Decode (value matching)
Decode($feature.code,
1, "One",
2, "Two",
3, "Three",
"Unknown"
)
var arr = [1, 2, 3, 4, 5];
Count(arr) // 5
First(arr) // 1
Last(arr) // 5
IndexOf(arr, 3) // 2
Includes(arr, 3) // true
Push(arr, 6) // [1, 2, 3, 4, 5, 6]
Reverse(arr) // [5, 4, 3, 2, 1]
Sort(arr) // [1, 2, 3, 4, 5]
Slice(arr, 1, 3) // [2, 3]
// Current feature
$feature.fieldName
// All features in layer (for aggregation)
var allFeatures = FeatureSet($layer);
var filtered = Filter(allFeatures, "type = 'A'");
var total = Sum(filtered, "value");
// Related records
var related = FeatureSetByRelationshipName($feature, "relationshipName");
// Global variables
$map // Reference to map
$view // Reference to view
$datastore // Reference to data store
import * as arcade from "@arcgis/core/arcade.js";
// Create profile
const profile = {
variables: [
{
name: "$feature",
type: "feature",
},
],
};
// Compile expression
const executor = await arcade.createArcadeExecutor(
"Round($feature.value * 100, 2)",
profile,
);
// Execute with feature
const result = await executor.executeAsync({
$feature: graphic,
});
console.log("Result:", result);
Note: In v5.0,
ExecuteContext.lruCacheis deprecated. UseExecuteContext.cacheinstead.
<script type="text/plain" id="my-expression">
var total = $feature.value_a + $feature.value_b;
var percentage = Round((total / $feature.max_value) * 100, 1);
return percentage + "%";
</script>
<script type="module">
const expression = document.getElementById("my-expression").text;
const popupTemplate = {
expressionInfos: [
{
name: "my-calc",
expression: expression,
},
],
content: "Value: {expression/my-calc}",
};
</script>
Different Arcade profiles expose different global variables and functions:
| Profile | Global Variables | Used For |
|---|---|---|
popup | $feature, $layer, $map, $datastore | Popup content |
visualization | $feature, $view | Renderers, visual variables |
labeling | $feature, $view | Label expressions |
field-calculate | $feature, $layer | Field calculations |
form-calculation | $feature, $layer, $originalFeature | Form calculated expressions |
constraint | $feature, $layer, $originalFeature | Form validation |
alias | $feature, $layer | Cluster popup aliases |
popuptemplate-arcade - Arcade expressions in PopupTemplatespopuptemplate-arcade-expression-content - Arcade expression content in popupsvisualization-arcade - Arcade-driven visualizationarcade-execute-chart - Execute Arcade with chartingNull values: Always check for nulls with IsEmpty($feature.field) before arithmetic operations. Dividing by null or adding to null produces null.
// Anti-pattern: no null check
return $feature.population / $feature.area;
// Correct: check for null
if (IsEmpty($feature.area) || $feature.area == 0) {
return "N/A";
}
return Round($feature.population / $feature.area, 2);
Type coercion: Use Number() or Text() for explicit conversion when mixing types.
Case sensitivity: Arcade function names are case-insensitive, but field names must match the source data exactly.
Performance: Complex expressions in renderers and labels evaluate per feature per frame. Keep them simple for large datasets.
Debugging: Use the Console() function to debug expressions. Output appears in the browser's developer console.
FeatureSet queries: FeatureSet() and Filter() execute server-side queries. They can be slow in popup expressions if the layer has many features.
arcgis-popup-templates for popup template configurationarcgis-visualization for renderer and symbol configurationarcgis-smart-mapping for data-driven visualizationarcgis-coding-components for the <arcgis-arcade-editor> component