Automated backup of an entire Shopify store with full media download and selective restore. Runs as n8n workflows with two independent backup strategies:
- Local backup — store data + media to local/NAS storage
- Google Drive backup — store data + media directly to Google Drive
Import one or both depending on your needs.
- n8n (self-hosted) with Docker CLI available in the container
- anvil — the recommended base image (includes Docker CLI + rclone)
- Shopify Custom App with Admin API access
- rclone (Google Drive workflow only — included in the base image above)
- Google Drive OAuth2 credentials in n8n (Google Drive workflow only)
| Resource | Backed Up | Restorable | Notes |
|---|---|---|---|
| Shop info | ✅ | ❌ | Read-only reference |
| Policies | ✅ | ❌ | Managed in admin |
| Shipping zones | ✅ | ❌ | Reference only |
| Countries/taxes | ✅ | ❌ | Reference only |
| Products | ✅ | ✅ | With variants and images |
| Product metafields | ✅ | ✅ | Per-product |
| Variant metafields | ✅ | ✅ | Per-variant |
| Inventory levels | ✅ | ✅ | Per-location quantities |
| Inventory items | ✅ | ❌ | SKU/tracking info |
| Custom collections | ✅ | ✅ | |
| Smart collections | ✅ | ✅ | Including rules |
| Collection metafields | ✅ | ✅ | |
| Collects | ✅ | ❌ | Product↔Collection maps |
| Customers | ✅ | ✅ | |
| Customer metafields | ✅ | ✅ | |
| Orders | ✅ | ❌ | Historical reference |
| Draft orders | ✅ | ❌ | Complex state |
| Pages | ✅ | ✅ | |
| Page metafields | ✅ | ✅ | |
| Blogs | ✅ | ✅ | |
| Articles | ✅ | ✅ | Per-blog |
| Themes + assets | ✅ | ✅ | Liquid, CSS, JS, images |
| Redirects | ✅ | ✅ | URL redirects |
| Script tags | ✅ | ✅ | |
| Price rules + discounts | ✅ | ✅ | |
| Gift cards | ✅ | ❌ | Security restrictions |
| Metafield definitions | ✅ | ✅ | All owner types (GraphQL) |
| Metaobject definitions | ✅ | ✅ | Schema + fields (GraphQL) |
| Metaobjects | ✅ | ✅ | All types, upsert (GraphQL) |
| Shop metafields | ✅ | ✅ | Shop-level custom data (GraphQL) |
All binary files from Shopify's CDN, stored in a structured folder:
media/
├── products/
│ ├── classic-blue-t-shirt/
│ │ ├── 1_front.jpg
│ │ └── 2_back.jpg
│ └── canvas-tote-bag/
│ └── 1_product-photo.png
├── collections/
│ ├── clothing.jpg
│ └── accessories.jpg
├── files/
│ ├── images/
│ ├── documents/
│ └── videos/
└── manifest.json
Media sync is incremental — only new or changed files are downloaded.
Shopify admin → Settings → Apps and sales channels → Develop apps → Create an app
Required Admin API scopes:
read_products write_products
read_customers write_customers
read_orders
read_inventory write_inventory
read_content write_content
read_themes write_themes
read_files
read_script_tags write_script_tags
read_shipping read_locations
read_price_rules write_price_rules
read_discounts write_discounts
read_gift_cards
read_metaobject_definitions write_metaobject_definitions
read_metaobject_entries write_metaobject_entries
Install the app and copy the Admin API access token.
Copy the backup scripts into your n8n container:
docker cp shopify-backup.mjs n8n:/opt/shopify-backup/shopify-backup.mjs
docker cp shopify-media-sync.mjs n8n:/opt/shopify-backup/shopify-media-sync.mjs
docker cp shopify-restore.mjs n8n:/opt/shopify-backup/shopify-restore.mjs
docker exec n8n chmod +x /opt/shopify-backup/*.mjsTo make scripts survive container rebuilds, create a persistent copy:
# Scripts on CasaOS host (persists across updates) mkdir -p /DATA/Data/shopify-backup cp shopify-backup.mjs shopify-media-sync.mjs shopify-restore.mjs /DATA/Data/shopify-backup/Then set these n8n environment variables:
SHOPIFY_BACKUP_SCRIPT_PATH=/home/node/data/shopify-backup/shopify-backup.mjs SHOPIFY_MEDIA_SCRIPT_PATH=/home/node/data/shopify-backup/shopify-media-sync.mjs SHOPIFY_RESTORE_SCRIPT_PATH=/home/node/data/shopify-backup/shopify-restore.mjs
Add these to your n8n instance (CasaOS app settings or container environment):
SHOPIFY_STORE=your-store.myshopify.com
SHOPIFY_ACCESS_TOKEN=shpat_your_access_token_here
SHOPIFY_API_VERSION=2025-01Local workflow only:
BACKUP_DATA_DIR=/backups/data
BACKUP_MEDIA_DIR=/backups/mediaGoogle Drive workflow only:
GDRIVE_BACKUP_FOLDER_ID=your_google_drive_folder_id
GDRIVE_MEDIA_STAGING=/home/node/data/gdrive-media-cache
RCLONE_REMOTE=gdrive
RCLONE_MEDIA_PATH=Shopify Backups/media
RCLONE_CONFIG=/home/node/data/rclone/rclone.confIn n8n: Workflows → Import from File → select the JSON.
| Workflow | Purpose | Schedule |
|---|---|---|
n8n-backup-local.json |
Backup to local/NAS storage | Daily 2:00AM |
n8n-backup-gdrive.json |
Backup directly to Google Drive | Daily 3:00AM |
n8n-restore.json |
Restore from local or Drive | Manual |
Import one or both backup workflows. The restore workflow supports both sources.
Activate the backup workflow(s) and run manually once to test.
Verify the scripts work against your store before importing to n8n:
export SHOPIFY_STORE=your-store.myshopify.com
export SHOPIFY_ACCESS_TOKEN=shpat_xxx
export BACKUP_OUTPUT_DIR=/tmp/shopify-test
# Run data backup
node shopify-backup.mjs
# Run media sync
export MEDIA_OUTPUT_DIR=/tmp/shopify-media-test
node shopify-media-sync.mjs
# Dry-run restore
node shopify-restore.mjs --file /tmp/shopify-test/shopify-backup_*.json --dry-runOpen the Shopify Restore from Backup workflow and edit the Restore Configuration node:
const config = {
source: 'local', // or 'gdrive'
backup_path: '/backups/data/FILENAME.json', // local path
backup_file_id: '', // Google Drive file ID
resources: 'all', // or comma-separated list
dry_run: true, // preview first!
skip_existing: false
};Always dry-run first, review the output, then set dry_run: false.
{ resources: 'inventory', dry_run: false }
{ resources: 'products,product_metafields,variant_metafields,inventory', dry_run: false }
{ resources: 'pages,page_metafields,blogs,articles', dry_run: false }
{ resources: 'metafield_definitions,metaobject_definitions,metaobjects', dry_run: false }
{ resources: 'theme_assets', dry_run: false }Both backup workflows have a Notify on Failure placeholder node. Connect your preferred service: Email/SMTP, Slack, Telegram, or webhook (Uptime Kuma, Healthchecks.io, etc.).
| Store Size | Products | Data Backup | Media (first run) | Media (incremental) |
|---|---|---|---|---|
| Small | <100 | 3-5 min | 5-10 min | <1 min |
| Medium | 100-500 | 5-15 min | 15-30 min | 1-3 min |
| Large | 500-2000 | 15-45 min | 30-90 min | 2-10 min |
The backup contains personal data (customers, orders). Under GDPR/AVG:
- Restrict access to backup storage to authorized personnel only
- JSON backups auto-expire after 30 days (retention cleanup in local workflow)
- Media files don't contain PII (images only)
- Google Drive: use a dedicated Workspace account with proper access controls
- Right to erasure: document that backups rotate on a 30-day cycle
- Consider encrypting the backup directory (LUKS, ZFS encryption, etc.)
.
├── README.md # This file
├── LICENSE # GPL-3.0-or-later
├── shopify-backup.mjs # Data backup script (REST + GraphQL)
├── shopify-media-sync.mjs # Incremental media download
├── shopify-restore.mjs # Selective restore with dry-run
├── n8n-backup-local.json # n8n workflow: local/NAS backup
├── n8n-backup-gdrive.json # n8n workflow: Google Drive backup
└── n8n-restore.json # n8n workflow: restore from local or Drive