The defining feature of digital products is the downloadable file. This chapter covers:
Each digital listing can have multiple downloadable files. When a customer purchases, they get access to download all files attached to that listing.
| Constraint | Limit |
|---|---|
| Max file size | 20 MB per file |
| Max files per listing | 5 files |
| Allowed formats | Most common formats (PDF, PNG, JPG, ZIP, etc.) |
For larger files, you’ll need to:
def upload_digital_file(client, shop_id, listing_id, file_path, rank=1):
"""Upload a digital file to a listing."""
with open(file_path, "rb") as f:
response = client.post(
f"/application/shops/{shop_id}/listings/{listing_id}/files",
files={"file": f},
data={"rank": rank, "name": file_path.name}
)
return response
The rank parameter determines download order (1 = first).
from pathlib import Path
def upload_all_files(client, shop_id, listing_id, file_paths):
"""Upload multiple files to a single listing."""
results = []
for rank, path in enumerate(file_paths, start=1):
path = Path(path)
if not path.exists():
results.append({"file": str(path), "status": "not found"})
continue
try:
result = upload_digital_file(client, shop_id, listing_id, path, rank)
results.append({"file": path.name, "status": "uploaded"})
except Exception as e:
results.append({"file": path.name, "status": "error", "error": str(e)})
return results
def list_digital_files(client, shop_id, listing_id):
"""Get all digital files for a listing."""
response = client.get(
f"/application/shops/{shop_id}/listings/{listing_id}/files"
)
return response["results"]
Returns:
[
{
"listing_file_id": 123456,
"listing_id": 1234567890,
"rank": 1,
"filename": "planner-2024.pdf",
"filesize": "2.5 MB",
"create_timestamp": 1699900000
}
]
def delete_digital_file(client, shop_id, listing_id, listing_file_id):
"""Remove a digital file from a listing."""
client.delete(
f"/application/shops/{shop_id}/listings/{listing_id}/files/{listing_file_id}"
)
When you update a product, you need to replace the file. Unfortunately, Etsy doesn’t have a direct “replace” endpoint—you must delete and re-upload:
def replace_digital_file(client, shop_id, listing_id, new_file_path):
"""Replace all files for a listing with a new file."""
# 1. Get existing files
existing = list_digital_files(client, shop_id, listing_id)
# 2. Delete old files
for file in existing:
delete_digital_file(client, shop_id, listing_id, file["listing_file_id"])
# 3. Upload new file
return upload_digital_file(client, shop_id, listing_id, new_file_path)
⚠️ Important: Customers who already purchased retain access to the original files. New downloads get the updated files.
See code/digital_files.py for complete implementation.
def create_listing_with_files(client, shop_id, listing_data, file_paths):
"""Create a new listing and upload its files."""
# 1. Create the listing
listing = client.post(
f"/application/shops/{shop_id}/listings",
data=listing_data
)
listing_id = listing["listing_id"]
# 2. Upload files
for rank, path in enumerate(file_paths, 1):
upload_digital_file(client, shop_id, listing_id, path, rank)
return listing_id
If you have a common file (like a license or bonus) across listings:
def update_common_file_all_listings(client, shop_id, old_filename, new_file_path):
"""Replace a specific file across all listings that have it."""
listings = get_all_listings(client, shop_id)
updated = []
for listing in listings:
files = list_digital_files(client, shop_id, listing["listing_id"])
for file in files:
if file["filename"] == old_filename:
# Delete old, upload new
delete_digital_file(
client, shop_id, listing["listing_id"], file["listing_file_id"]
)
upload_digital_file(
client, shop_id, listing["listing_id"], new_file_path, file["rank"]
)
updated.append(listing["listing_id"])
return updated
Use clear, consistent file names:
def format_filename(product_name, variant=None, extension="pdf"):
"""Create a clean, customer-friendly filename."""
name = product_name.lower().replace(" ", "-")
if variant:
name = f"{name}-{variant}"
return f"{name}.{extension}"
# Examples:
# monthly-planner.pdf
# monthly-planner-a4.pdf
# monthly-planner-letter.pdf
When offering multiple formats, consider ZIPping them:
import zipfile
from pathlib import Path
def create_bundle_zip(files, output_name):
"""Create a ZIP bundle from multiple files."""
zip_path = Path(output_name)
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
for file in files:
zf.write(file, Path(file).name)
return zip_path
Include a text file with instructions:
README_TEMPLATE = """
Thank you for your purchase!
FILES INCLUDED:
{file_list}
HOW TO PRINT:
1. Open the PDF file
2. Set print size to 100% (Actual Size)
3. Use high-quality paper for best results
NEED HELP?
Contact me through Etsy messages.
Terms: Personal use only. Do not redistribute.
"""
If your files exceed 20MB:
import zipfile
def compress_file(input_path, output_path=None):
"""Compress a file using maximum ZIP compression."""
input_path = Path(input_path)
output_path = output_path or input_path.with_suffix('.zip')
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
zf.write(input_path, input_path.name)
return output_path, output_path.stat().st_size
For very large products, create multiple listings or use external delivery with a link file.
After uploading, verify files are attached:
def verify_listing_files(client, shop_id, listing_id, expected_count):
"""Verify the correct number of files are attached."""
files = list_digital_files(client, shop_id, listing_id)
actual_count = len(files)
if actual_count != expected_count:
return {
"status": "mismatch",
"expected": expected_count,
"actual": actual_count,
"files": [f["filename"] for f in files]
}
return {"status": "ok", "files": [f["filename"] for f in files]}
With listings created and files uploaded, the next chapter covers handling orders—what happens after customers purchase your digital products.
| ← Previous: Creating Listings | Next: Orders & Fulfillment → |