HOW TO
Metadata is data that describes an item. However, ArcGIS Experience Builder is unable to display metadata from a feature layer because its widgets are bound to attribute fields and statistics instead of item metadata. Therefore, Python scripting in ArcGIS Notebooks is required to enable metadata from a feature layer to be displayed in Experience Builder. Refer to ArcGIS Online: Use ArcGIS API for Python in a notebook for more information. In this article, a Python script demonstrates how to extract and display metadata in Experience Builder.
This workflow extracts metadata (last edit date) using ArcGIS Notebooks and stores it in a hosted table for display in Experience Builder.
Prerequisites to successfully display metadata in Experience Builder: • Select a feature layer in Experience Builder. This layer is read-only, and metadata is extracted from this layer. • Create a hosted table in ArcGIS Online. This layer is used to store the updated metadata for display in Experience Builder.


# Defaults
from arcgis.gis import GIS
from arcgis.features import FeatureLayer
import requests
from datetime import datetime
import pytz
gis = GIS("home")
print("Logged in as:", gis.users.me.username)
Note: After pasting the code, click Run this cell and advanceto execute the cell before proceeding to the next step.
Note: Click Insert a cell below (B) to add a cell below the current cell.
SOURCE_LAYER_URL = "<feature layer URL>"
DEST_TABLE_URL = "<hosted table URL>/0" #hosted table URL-> make sure to paste the URL that ends with /0.
DEST_OBJECTID = 1
FIELD_TEXT = "last_updated_text" #field created in user own hosted table
FIELD_DATE = "last_updated_date" #field created in user own hosted table
MYT = "Asia/Kuala_Lumpur" #change this to user own location
FORMAT = "%Y-%m-%d %H:%M"
print("✅ Parameters set.")
import requests
from datetime import datetime, timezone
from zoneinfo import ZoneInfo # Python 3.11+, no external dependency
def request_json(url, params=None, timeout=30):
p = {"f": "json", **(params or {})}
r = requests.get(url, params=p, timeout=timeout)
r.raise_for_status()
j = r.json()
if "error" in j:
raise RuntimeError(f"REST error at {url}: {j['error']}")
return j
def get_last_updated_ms(layer_url: str) -> int:
"""Return epoch ms of last updated, preferring editingInfo.lastEditDate."""
# 1) Try layer endpoint
lyr = request_json(layer_url)
ei = (lyr.get("editingInfo") or {})
if isinstance(ei.get("lastEditDate"), (int, float)):
return int(ei["lastEditDate"])
# Try common direct keys
for k in ("lastEditDate", "serviceUpdatedAt"):
if isinstance(lyr.get(k), (int, float)):
return int(lyr[k])
# 2) Try service root (parent)
parent_url = layer_url.rsplit("/", 1)[0]
srv = request_json(parent_url)
ei = (srv.get("editingInfo") or {})
if isinstance(ei.get("lastEditDate"), (int, float)):
return int(ei["lastEditDate"])
if isinstance(srv.get("serviceUpdatedAt"), (int, float)):
return int(srv["serviceUpdatedAt"])
raise RuntimeError("Could not find last updated timestamp in layer or service root.")
# Compute values
ts_ms = get_last_updated_ms(SOURCE_LAYER_URL) # <-- epoch ms (UTC)
dt_utc = datetime.fromtimestamp(ts_ms/1000, tz=timezone.utc)
dt_myt = dt_utc.astimezone(ZoneInfo(MYT))
text_val = dt_myt.strftime(FORMAT) + " MYT"
print("Last updated (text):", text_val)
print("Last updated (UTC datetime):", dt_utc.isoformat())
print("Last updated (MYT datetime):", dt_myt.isoformat())
from arcgis.features import FeatureLayer
import time
dest_layer = FeatureLayer(DEST_TABLE_URL, gis=gis)
# OPTIONAL: ensure fields exist (uncomment if needed)
def ensure_field(layer: FeatureLayer, name: str, kind: str = "string"):
fields = {f["name"].lower(): f for f in layer.properties.fields}
if name.lower() in fields:
return
if kind.lower() == "date":
new_field = {"name": name, "type": "esriFieldTypeDate", "alias": name, "nullable": True}
else:
new_field = {"name": name, "type": "esriFieldTypeString", "alias": name, "length": 255, "nullable": True}
print(f"Creating field '{name}'...")
res = layer.manager.add_to_definition({"fields": [new_field]})
time.sleep(1.5)
if isinstance(res, dict) and not res.get("success", True):
raise RuntimeError(f"Failed to create field {name}: {res}")
# Uncomment if you need schema creation
# ensure_field(dest_layer, FIELD_TEXT, "string")
# ensure_field(dest_layer, FIELD_DATE, "date")
# Try to fetch the anchor record
q = dest_layer.query(where=f"OBJECTID={int(DEST_OBJECTID)}", out_fields="*")
if q.features:
record = q.features[0]
record.attributes[FIELD_TEXT] = text_val
record.attributes[FIELD_DATE] = ts_ms # <-- ArcGIS Date = epoch ms (UTC)
res = dest_layer.edit_features(updates=[record])
else:
# Add one if missing (OBJECTID is usually auto-managed)
new_feat = {"attributes": {FIELD_TEXT: text_val, FIELD_DATE: ts_ms}}
res = dest_layer.edit_features(adds=[new_feat])
print("✅ Record updated:", res)
rec = dest_layer.query(where=f"OBJECTID={int(DEST_OBJECTID)}", out_fields="*").features
if rec:
attrs = rec[0].attributes
print(f"{FIELD_TEXT}: {attrs.get(FIELD_TEXT)}")
print(f"{FIELD_DATE} (epoch ms UTC): {attrs.get(FIELD_DATE)}")
else:
print(f"No record found with OBJECTID={DEST_OBJECTID}. A new record may have been added with a different OBJECTID.")
tab and click Add data in the Data panel.






to open the Dynamic content pane.
The Text widget below shows the updated metadata information.

Article ID: 000039962
Get help from ArcGIS experts
Start chatting now