The default was that layer name was displayed unless you change it to be driven by the attribute. How do we set up expression to show layer name without typing this in manually for every layer?
We found a workaround so you dont have to type this in for every layer: return GetFeatureSetInfo($layer).webMapLayerTitle is the expression that returns the layer name in the popup.
Thanks for this! The expression isn't working in my Arcade item in the popup. Did you envision it elsewhere?
Please provide some snips on how you are trying to use this expression.
If you want to do this for all the layers in the map you can use notebooks to automate it:
from arcgis.gis import GIS
import json
import copy
gis = GIS("home")
WEBMAP_ID = "PASTE_WEBMAP_ITEM_ID_HERE"
expr_name = "expr_layer_name"
expr_title = "Layer Name"
expr_code = """
var info = GetFeatureSetInfo(GetFeatureSet($feature));
return info.webMapLayerTitle;
""".strip()
item = gis.content.get(WEBMAP_ID)
data = item.get_data()
for lyr in data.get("operationalLayers", []):
# Skip layers without popups if desired
popup = lyr.setdefault("popupInfo", {})
# Add expressionInfos if missing
expressions = popup.setdefault("expressionInfos", [])
# Avoid duplicate expression
existing = next((e for e in expressions if e.get("name") == expr_name), None)
if existing:
existing["title"] = expr_title
existing["expression"] = expr_code
else:
expressions.append({
"name": expr_name,
"title": expr_title,
"expression": expr_code
})
# Add to description without overwriting existing popup content
desc = popup.get("description", "")
layer_line = f"Layer: {{expression/{expr_name}}}<br>"
if f"{{expression/{expr_name}}}" not in desc:
popup["description"] = layer_line + desc
item.update(item_properties={
"text": json.dumps(data)
})
print("Updated popups for all operational layers.")
Or something like this if you want to keep all other fields in popup:
# --- Web map popups: Title = Layer Name, Body = All Fields table (like your screenshot) ---
from arcgis.gis import GIS
from arcgis.features import FeatureLayer
import json, datetime
WEBMAP_ID = "yourmapid" # your map
DRY_RUN = False # True = preview, don't save
INCLUDE_SYSTEM_FIELDS = True # keep OBJECTID/SHAPE_*/editor tracking, etc.
# Arcade expression: prefer the web map's layer title; fallback to service layer name
ARC_EXPR = """
var info = GetFeatureSetInfo($layer);
var t = info.webMapLayerTitle;
var n = info.layerName;
return IIf(IsEmpty(t), IIf(IsEmpty(n), 'Layer', n), t);
"""
def _expr_infos():
return [{
"name": "expr0",
"title": "Layer Name",
"expression": ARC_EXPR,
"returnType": "string"
}]
def _get_fields_from(layer_dict, gis):
"""Return list of {'fieldName','label','visible'} in the service order."""
# 1) Try popupInfo.fieldInfos first (to preserve existing aliases/order if present)
pi = layer_dict.get("popupInfo")
if isinstance(pi, dict):
finfos = pi.get("fieldInfos")
if isinstance(finfos, list) and finfos:
out = []
for f in finfos:
if not isinstance(f, dict):
continue
name = f.get("fieldName") or f.get("name")
if not name:
continue
out.append({"fieldName": name, "label": f.get("label", name), "visible": True})
if out:
return out
# 2) layerDefinition.fields (common for map-image sublayers / feature collections)
ld = layer_dict.get("layerDefinition")
if isinstance(ld, dict) and isinstance(ld.get("fields"), list) and ld["fields"]:
out = []
for f in ld["fields"]:
nm = f.get("name")
if not nm:
continue
out.append({"fieldName": nm, "label": f.get("alias", nm), "visible": True})
if out:
return out
# 3) Service fields via URL
url = layer_dict.get("url")
if url:
try:
fl = FeatureLayer(url, gis=gis)
fields = getattr(fl.properties, "fields", None)
out = []
if isinstance(fields, list):
for f in fields:
nm = f.get("name")
if not nm:
continue
out.append({"fieldName": nm, "label": f.get("alias", nm), "visible": True})
if out:
return out
except Exception:
pass
# 4) FeatureCollection inner layerDefinition
fc = layer_dict.get("featureCollection")
if isinstance(fc, dict):
for fcl in (fc.get("layers") or []):
fld = fcl.get("layerDefinition")
if isinstance(fld, dict) and isinstance(fld.get("fields"), list) and fld["fields"]:
out = []
for f in fld["fields"]:
nm = f.get("name")
if not nm:
continue
out.append({"fieldName": nm, "label": f.get("alias", nm), "visible": True})
if out:
return out
return []
def _maybe_filter_system(fields):
if INCLUDE_SYSTEM_FIELDS:
return fields
sys_prefixes = ("SHAPE",)
exclude_exact = {"OBJECTID","FID","GLOBALID","GLOBAL_ID",
"CreationDate","Creator","EditDate","Editor",
"created_date","created_user","last_edited_date","last_edited_user"}
out = []
for f in fields:
nm = f["fieldName"]
up = nm.upper()
if up in exclude_exact or any(up.startswith(p) for p in sys_prefixes):
continue
out.append(f)
return out
def _ensure_popup(layer_dict, gis, path, changes):
"""Replace popup with: title expr + fields table; applies to any schema location."""
changed = False
# enable popups if disabled
if layer_dict.get("disablePopup") is True:
layer_dict["disablePopup"] = False
changed = True
changes.append(f"{path}: enabled popups")
# locations where popupInfo may live
spots = []
# direct
if isinstance(layer_dict.get("popupInfo"), dict):
spots.append(("popupInfo", layer_dict["popupInfo"]))
else:
layer_dict["popupInfo"] = {}
spots.append(("popupInfo(created)", layer_dict["popupInfo"]))
changed = True
# map-image sublayer
ld = layer_dict.get("layerDefinition")
if isinstance(ld, dict):
if isinstance(ld.get("popupInfo"), dict):
spots.append(("layerDefinition.popupInfo", ld["popupInfo"]))
else:
ld["popupInfo"] = {}
spots.append(("layerDefinition.popupInfo(created)", ld["popupInfo"]))
layer_dict["layerDefinition"] = ld
changed = True
# feature collection inner layers
fc = layer_dict.get("featureCollection")
if isinstance(fc, dict):
for idx, fcl in enumerate(fc.get("layers") or []):
if isinstance(fcl.get("popupInfo"), dict):
spots.append((f"featureCollection.layers[{idx}].popupInfo", fcl["popupInfo"]))
else:
fcl["popupInfo"] = {}
spots.append((f"featureCollection.layers[{idx}].popupInfo(created)", fcl["popupInfo"]))
changed = True
# unified fields list (service order + aliases)
fields = _get_fields_from(layer_dict, gis)
fields = _maybe_filter_system(fields)
for label, pi in spots:
# Title via Arcade
pi["title"] = "{expression/expr0}"
pi["expressionInfos"] = _expr_infos()
# Replace body with a single fields table exactly like the viewer's table
pi["popupElements"] = [{"type": "fields", "fieldInfos": fields}]
# For older viewers, keep fieldInfos mirrored too (order/labels)
pi["fieldInfos"] = fields
# Clean extras so only table shows (like your screenshot)
pi["description"] = None
pi["mediaInfos"] = []
pi["showAttachments"] = False
changed = True
if changed:
changes.append(f"{path}: enforced title+fields-table ({len(fields)} fields)")
return changed
def _walk(node, gis, path, changes):
changed = False
if isinstance(node, dict):
if _ensure_popup(node, gis, path, changes):
changed = True
# Recurse children
for i, child in enumerate(node.get("layers") or []):
if isinstance(child, dict):
if _walk(child, gis, f"{path}.layers[{i}]", changes):
changed = True
for i, child in enumerate(node.get("tables") or []):
if isinstance(child, dict):
if _walk(child, gis, f"{path}.tables[{i}]", changes):
changed = True
fc = node.get("featureCollection")
if isinstance(fc, dict):
for i, fcl in enumerate(fc.get("layers") or []):
if isinstance(fcl, dict):
if _walk(fcl, gis, f"{path}.featureCollection.layers[{i}]", changes):
changed = True
return changed
def run(webmap_id, dry_run=False):
gis = GIS("home")
item = gis.content.get(webmap_id)
if not item:
raise RuntimeError("Web map not found")
data = item.get_data()
if not data:
raise RuntimeError("No web map data")
print(f"Loaded: {item.title} ({item.id})")
changes = []
changed_any = False
for i, lyr in enumerate(data.get("operationalLayers", []) or []):
if _walk(lyr, gis, f"operationalLayers[{i}]", changes):
changed_any = True
for i, tbl in enumerate(data.get("tables", []) or []):
if _walk(tbl, gis, f"tables[{i}]", changes):
changed_any = True
if not changed_any:
print("No changes necessary.")
return {"updated": False, "changes": changes}
backup = f"webmap_backup_{item.id}_{datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')}.json"
with open(backup, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
print(f"Backup saved: {backup}")
if dry_run:
print("\nDRY RUN — sample changes:")
for line in changes[:30]:
print(" •", line)
if len(changes) > 30:
print(f"... plus {len(changes)-30} more")
return {"updated": False, "changes": changes, "backup": backup}
ok = item.update(data=data)
if not ok:
raise RuntimeError("Item update returned False")
print("\n✅ Updated. Open the **web map** (not an app) in a NEW tab and hard-refresh (Ctrl+F5).")
for line in changes[:30]:
print(" •", line)
if len(changes) > 30:
print(f"... plus {len(changes)-30} more")
return {"updated": True, "changes": changes, "backup": backup}
result = run(WEBMAP_ID, DRY_RUN)
result