Select to view content in your preferred language

Creating a backup of map incl. feature layers, visualization and attachments

145
5
yesterday
ErikAIversen
Occasional Contributor

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. 

0 Kudos
5 Replies
MobiusSnake
MVP Regular Contributor

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:

  • I use AGOL's built-in export process to create File GDB backups of the data.  FGDB is crucial (as opposed alternatives like shapefiles) because other data types involve the loss of some aspects of the data, such as null values or attachments.
  • I use a Python script that uses the ArcGIS API for Python (specifically the Item class) to connect to web maps, apps, etc. and downloads their definition as JSON files.  These JSON files capture map configuration like symbology and forms, element configuration and Data Expressions in dashboards, that kind of thing.  I push these JSON files into a Git repo, so I keep a complete history of these components.
ErikAIversen
Occasional Contributor

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?

0 Kudos
MobiusSnake
MVP Regular Contributor

I usually go with the Export Data button on the hosted feature layer's item page:

MobiusSnake_0-1747391946497.png

0 Kudos
MobiusSnake
MVP Regular Contributor

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.

ErikAIversen
Occasional Contributor

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!

0 Kudos