Write Arcade expressions for dynamic calculations in popups, renderers, labels, and field calculations. Use for data-driven styling, custom labels, and computed fields.
Install
npx skillscat add saschabrunnerch/arcgis-maps-sdk-js-ai-context/arcgis-arcade Install via the SkillsCat registry.
ArcGIS Arcade Expressions
Use this skill for writing Arcade expressions for popups, renderers, labels, and calculations.
Arcade Basics
Arcade is an expression language for ArcGIS. It's used for:
- Dynamic popup content
- Data-driven rendering
- Custom labels
- Field calculations
- Form validation
Basic Syntax
// Variables
var population = $feature.population;
var area = $feature.area_sqkm;
// Calculations
var density = population / area;
// Return result
return Round(density, 2);Arcade in PopupTemplates
Expression Infos
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²"
};Complex Expressions
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"
}]
}
]
};Arcade in Renderers
Value Expression
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" }
}
]
};Visual Variable Expression
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 }
]
}]
};Arcade in Labels
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"
}];Common Arcade Functions
Math Functions
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]) // 2Text Functions
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"Date Functions
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 dateGeometry Functions
Area($feature, "square-kilometers")
Length($feature, "kilometers")
Centroid($feature)
Buffer($feature, 100, "meters")
Intersects($feature, $otherFeature)
Contains($feature, $point)Conditional Functions
// 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"
)Array Functions
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]Feature Access
// 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 storeExecute Arcade Programmatically
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);Arcade in HTML (Script Tags)
<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>TypeScript Usage
Arcade expression configurations use autocasting. For TypeScript safety, use as const:
// Use 'as const' for expression-based visualizations
layer.renderer = {
type: "simple",
symbol: { type: "simple-marker" },
visualVariables: [{
type: "size",
valueExpression: "$feature.population / $feature.area",
stops: [
{ value: 50, size: 4 },
{ value: 500, size: 20 }
]
}]
} as const;Tip: See arcgis-core-maps skill for detailed guidance on autocasting vs explicit classes.
Reference Samples
popuptemplate-arcade- Arcade expressions in PopupTemplatespopuptemplate-arcade-expression-content- Arcade expression content in popups
Common Pitfalls
Null values: Check for nulls with
IsEmpty($feature.field)Type coercion: Use
Number()orText()for explicit conversionCase sensitivity: Arcade is case-insensitive for functions but field names match exactly
Performance: Complex expressions in renderers can slow performance
Debugging: Use
Console()function to debug expressions