Usage Reference
This page documents all migrate_content() parameters, the MigrationResult return
value, and common usage patterns. For a step-by-step first-run guide see the
Quickstart.
migrate_content()
Clones items from a source ArcGIS portal to a destination portal. Returns a
MigrationResult summarising how many items were migrated, skipped, or failed.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
source_gis |
GIS \| None |
None |
Pre-built authenticated GIS object for the source portal. When provided, source_env and secrets.yml are ignored for the source connection. |
destination_gis |
GIS \| None |
None |
Pre-built authenticated GIS object for the destination portal. When provided, destination_env and secrets.yml are ignored for the destination connection. |
source_env |
str |
"source" |
Key in config/secrets.yml identifying the source portal credentials. Ignored when source_gis is provided. |
destination_env |
str |
"destination" |
Key in config/secrets.yml identifying the destination portal credentials. Ignored when destination_gis is provided. |
resume |
bool |
False |
When True, items already present in the destination (matched by title and type) are skipped rather than re-cloned. Defaults to False (fresh-run). |
query |
str \| None |
None |
ArcGIS content search query string passed to gis.content.search(). None returns all items accessible to the authenticated source user. |
max_items |
int \| None |
None |
Cap on the number of source items to process. None means no limit. Useful for safety checks and dry runs. |
Note
source_gis and destination_gis are intended for testing or advanced scenarios
where portal connections are managed externally. For normal use, set credentials in
config/secrets.yml and let the function connect automatically.
MigrationResult
migrate_content() returns a MigrationResult dataclass with four fields:
| Field | Type | Description |
|---|---|---|
migrated |
int |
Number of items successfully cloned to the destination portal. |
skipped |
int |
Number of items skipped because they were already present in the destination (only non-zero in resume mode). |
failed |
int |
Number of items that could not be cloned due to an error. |
failures |
list[dict] |
Per-item failure records. Each entry has four keys: item_id, title, type, and error. |
result = migrate_content()
print(f"Migrated: {result.migrated}")
print(f"Skipped: {result.skipped}")
print(f"Failed: {result.failed}")
Note
failed > 0 does not raise an exception. The migration always runs to
completion; failures are collected and returned so callers can decide how to handle
them. Only a pre-flight error (bad credentials, identical portals) raises an
exception and exits the script with code 1.
Inspecting failures
Each record in result.failures is a plain dict with four keys:
| Key | Type | Contents |
|---|---|---|
item_id |
str |
The ArcGIS item ID of the failed source item. |
title |
str |
The title of the failed item. |
type |
str |
The item type (e.g. "Web Map", "Dashboard"). |
error |
str |
The exception message from the failed clone attempt. |
result = migrate_content()
if result.failed:
print(f"{result.failed} item(s) failed:")
for rec in result.failures:
print(f" [{rec['item_id']}] {rec['title']} ({rec['type']}): {rec['error']}")
Log file
Every run produces a timestamped log file in data/logs/:
The filename uses a 2-digit year (%y), so a run at 14:30 on 21 April 2026
produces clone_content_2604211430.log.
| Log level | Written to file | Written to console |
|---|---|---|
DEBUG |
✓ (all records) | ✗ by default |
INFO |
✓ | ✓ |
WARNING |
✓ | ✓ |
ERROR |
✓ | ✓ |
CRITICAL |
✓ | ✓ |
The console level is controlled by logging.level in config/config.yml
(default: DEBUG — all records shown). The file always captures DEBUG and above,
providing a full audit trail regardless of the console setting.
Resume mode
Use resume mode when a previous migration was interrupted and you want to continue from where it left off — without re-cloning items that already arrived.
# Fresh run (default) — clone everything regardless of destination state
result = migrate_content()
# Resume run — skip items already present in the destination
result = migrate_content(resume=True)
How items are matched: an item is considered already present when the destination
contains an item with the same title and the same type. If the title matches but the
type differs (e.g. a "Web Map" on the source vs. a "Dashboard" of the same name on
the destination), the item is treated as absent and will be cloned.
Tip
Resume mode reads the full destination item index once at the start of the run, so it does not make additional API calls per item during the loop.
Filtering items
By content type or keyword — query
The query parameter is passed directly to gis.content.search(). Use ArcGIS query
syntax to filter the source items:
# Migrate only Web Maps
result = migrate_content(query='type:"Web Map"')
# Migrate items owned by a specific user
result = migrate_content(query='owner:jsmith')
# Migrate items tagged "migration-ready"
result = migrate_content(query='tags:"migration-ready"')
# Combine conditions
result = migrate_content(query='type:"Dashboard" AND owner:jsmith')
Limit item count — max_items
Use max_items to process only the first N source items. This is useful for
safety checks and dry runs before committing to a full migration:
# Process only the first 10 items — useful for testing
result = migrate_content(max_items=10)
print(f"Test run: {result.migrated} migrated, {result.failed} failed")
Warning
max_items truncates the source item list after the search query is applied.
A value of 10 with query='type:"Web Map"' processes the first 10 Web Maps,
not 10 items total.