Over time the company I work for has built up a lot of maps in online that are no longer in active use. All these maps and feature layers contribute to credit usage, but we have kept them online since we sometimes have to revisit old maps for new projects with returning clients.
Is there a way to create a file geodatabase that includes all the layers, their visualization and attachments (if any)? I've tried to open the map in ArcGIS Pro and use the "Extract Data" geoprocess tool. Sadly it didn't keep the original visualization nor the attatchments, but it did keep all the layers saved into one file geodatabase.
Generally speaking, there isn't a way to store map properties, layer symbology, etc. in a geodatabase. I have seem some custom approaches where these types of things are persisted in BLOB fields, or in large text fields with JSON content.
When I'm working on an AGOL project where I need to backup the data as well as maps, apps, etc., I'll usually do this:
Thank you very much for the explanation. The built-in export process for AGOL, are you refering to this: https://doc.arcgis.com/en/arcgis-online/analyze/extract-data-mv.htm ? Is there any way you would be willing to share the code you have used for the Python script for maps?
I usually go with the Export Data button on the hosted feature layer's item page:
Here are they two key functions I use for item exports and imports (this is typically when I've messed something up and need to roll back my changes). This doesn't include connecting to ArcGIS Online to get an instance of the GIS class, that object gets passed into these functions along with the Item ID:
def download_data(gis: GIS, item_id: str, save_path: str):
"""
Opens the specified item's data and saves it to the specified path.
:param gis: The GIS connection.
:param item_id: The Item ID to open.
:param save_path: The location to save the item data.
:return: None.
"""
content_mgr = gis.content
item = content_mgr.get(item_id)
if item is None:
raise Exception(f"download_data: Could not open item.")
item_data = item.get_data()
with open(save_path, "w") as output_file:
json.dump(item_data, output_file)
def update_data(gis: GIS, item_id: str, file_path: str):
"""
Updates the specified item with the data in the specified file.
:param gis: The GIS connection.
:param item_id: The Item ID to update.
:param file_path: The file to update the item with.
:return: None.
"""
content_mgr = gis.content
item = content_mgr.get(item_id)
if item is None:
raise Exception(f"update_data: Could not open item.")
with open(file_path) as input_file:
input_file_data = input_file.read()
if not item.update(data=input_file_data):
raise Exception(f"update_data: update() returned failure code.")
Edit to add - there are a handful of settings that may not be captured with this approach, depending on the type of item you're downloading. I haven't really tested it with Experience Builder but I have my suspicions it may not work so well; I typically use it with web maps and dashboards.
Hi! I made two different scripts meant to be used as toolboxes in ArcGIS Pro inspired by your approach named "ExportBackup" and "RestoreMap":
1. Find the map in AGOL you want to create a complete backup from and open it in ArcGIS Pro.
2. Run this script which loops through every layer except basemaps, WMS etc. and saves them as individual .gdb (with attachments) and .lyrx (keeps symobology):
import arcpy
import os
import csv
import re
# Get output folder from script tool parameter
output_folder = arcpy.GetParameterAsText(0)
# Prepare log
log_file_path = os.path.join(output_folder, "layer_export_log.csv")
log_rows = [["Original Layer Name", "Sanitized Name", "GDB Path", "Attachments Exported", "Symbology File"]]
# Helper to sanitize names
def sanitize_name(name):
return re.sub(r'[^a-zA-Z0-9_]', '_', name)[:50]
# Get current ArcGIS Pro project and active map
project = arcpy.mp.ArcGISProject("CURRENT")
map_obj = project.activeMap
# Loop through all layers
for lyr in map_obj.listLayers():
# Skip group layers, basemaps, or unsupported types
if lyr.isGroupLayer or lyr.isBasemapLayer or not lyr.isFeatureLayer:
try:
arcpy.AddMessage(f"⏭️ Skipping: {lyr.name}")
except:
arcpy.AddMessage("⏭️ Skipping unsupported or service-based layer")
continue
# Try to access the layer name safely
try:
original_name = lyr.name
except:
original_name = "Unnamed_Layer"
layer_name = sanitize_name(original_name)
gdb_path = os.path.join(output_folder, f"{layer_name}.gdb")
lyrx_path = os.path.join(output_folder, f"{layer_name}.lyrx")
attachments_exported = "Included in GDB"
arcpy.AddMessage(f"\n🔄 Exporting: {original_name} → {layer_name}")
# Create File GDB
if not arcpy.Exists(gdb_path):
arcpy.management.CreateFileGDB(output_folder, f"{layer_name}.gdb")
out_feature = os.path.join(gdb_path, layer_name)
try:
# Export feature class
arcpy.conversion.FeatureClassToFeatureClass(
in_features=lyr,
out_path=gdb_path,
out_name=layer_name
)
arcpy.AddMessage("📎 Attachments preserved within File Geodatabase.")
# Temporarily add exported data to map
temp_layer = map_obj.addDataFromPath(out_feature)
# Copy symbology from original layer
try:
temp_layer.symbology = lyr.symbology
arcpy.AddMessage(":artist_palette: Symbology copied from original layer.")
except Exception as e:
arcpy.AddWarning(f":warning: Could not copy symbology: {str(e)}")
# Save styled layer to .lyrx
temp_layer.saveACopy(lyrx_path)
arcpy.AddMessage(f":floppy_disk: Symbology saved to: {lyrx_path}")
# Clean up
map_obj.removeLayer(temp_layer)
except Exception as e:
arcpy.AddWarning(f"❌ Failed to export {original_name}: {str(e)}")
continue
# Log export result
log_rows.append([original_name, layer_name, gdb_path, attachments_exported, lyrx_path])
# Write CSV log
with open(log_file_path, mode="w", newline='', encoding="utf-8") as log_file:
writer = csv.writer(log_file)
writer.writerows(log_rows)
arcpy.AddMessage(f"\n📝 Log file saved to: {log_file_path}")
arcpy.AddMessage("✅ Done! All eligible layers exported.")
3. Run this script to restore the map just like it was:
import arcpy
import os
# Folder containing .gdb and matching .lyrx files
input_folder = arcpy.GetParameterAsText(0)
project = arcpy.mp.ArcGISProject("CURRENT")
map_obj = project.activeMap
arcpy.AddMessage(f":open_file_folder: Restoring from: {input_folder}")
for item in os.listdir(input_folder):
if item.lower().endswith(".gdb"):
layer_name = os.path.splitext(item)[0]
gdb_path = os.path.join(input_folder, item)
lyrx_path = os.path.join(input_folder, f"{layer_name}.lyrx")
arcpy.env.workspace = gdb_path
feature_classes = arcpy.ListFeatureClasses()
if not feature_classes:
arcpy.AddWarning(f":warning: No feature classes in {gdb_path}.")
continue
fc_path = os.path.join(gdb_path, feature_classes[0])
arcpy.AddMessage(f"➕ Adding: {layer_name}")
try:
new_layer = map_obj.addDataFromPath(fc_path)
if os.path.exists(lyrx_path):
try:
sym_layer_file = arcpy.mp.LayerFile(lyrx_path)
sym_layer = sym_layer_file.listLayers()[0]
new_layer.symbology = sym_layer.symbology
arcpy.AddMessage(f":artist_palette: Symbology copied from: {lyrx_path}")
except Exception as e:
arcpy.AddWarning(f":warning: Failed to copy symbology: {str(e)}")
else:
arcpy.AddWarning(f":artist_palette: No .lyrx found for: {layer_name}")
except Exception as e:
arcpy.AddWarning(f"❌ Failed to load {layer_name}: {str(e)}")
arcpy.AddMessage("✅ Done restoring layers.")
Both toolboxes has to be set up with a mandatory output folder for the "ExportBackup" and input folder for "RestoreMap". Works like a charm!