#!/usr/bin/env python3 """ Facebook Ads CSV Bulk Upload Generator Complete field mapping with validation for all campaign types Author: Buba Date: 2026-02-11 """ import csv import datetime from typing import List, Dict, Optional from enum import Enum class CampaignObjective(Enum): """All supported Facebook campaign objectives""" OUTCOME_TRAFFIC = "OUTCOME_TRAFFIC" OUTCOME_SALES = "OUTCOME_SALES" OUTCOME_LEADS = "OUTCOME_LEADS" OUTCOME_ENGAGEMENT = "OUTCOME_ENGAGEMENT" OUTCOME_APP_PROMOTION = "OUTCOME_APP_PROMOTION" OUTCOME_AWARENESS = "OUTCOME_AWARENESS" class OptimizationGoal(Enum): """Ad set optimization goals""" LINK_CLICKS = "LINK_CLICKS" LANDING_PAGE_VIEWS = "LANDING_PAGE_VIEWS" IMPRESSIONS = "IMPRESSIONS" REACH = "REACH" OFFSITE_CONVERSIONS = "OFFSITE_CONVERSIONS" THRUPLAY = "THRUPLAY" POST_ENGAGEMENT = "POST_ENGAGEMENT" class BillingEvent(Enum): """Billing event types""" IMPRESSIONS = "IMPRESSIONS" LINK_CLICKS = "LINK_CLICKS" THRUPLAY = "THRUPLAY" class CallToAction(Enum): """Call to action button types""" LEARN_MORE = "LEARN_MORE" SHOP_NOW = "SHOP_NOW" SIGN_UP = "SIGN_UP" DOWNLOAD = "DOWNLOAD" GET_QUOTE = "GET_QUOTE" CONTACT_US = "CONTACT_US" BOOK_NOW = "BOOK_NOW" APPLY_NOW = "APPLY_NOW" NO_BUTTON = "NO_BUTTON" class FBAdsCSVGenerator: """ Complete Facebook Ads CSV generator with all API fields """ # Complete field mapping CAMPAIGN_FIELDS = [ "Campaign Name", "Campaign Objective", "Buying Type", "Campaign Status", "Campaign Budget Optimization", "Campaign Budget", "Campaign Bid Strategy", "Campaign Spend Limit", ] AD_SET_FIELDS = [ "Ad Set Name", "Optimization Goal", "Billing Event", "Bid Strategy", "Bid Amount", "Daily Budget", "Lifetime Budget", "Start Date", "End Date", "Ad Set Status", "Targeting Age Min", "Targeting Age Max", "Targeting Gender", "Targeting Locations", "Targeting Custom Audiences", "Targeting Excluded Custom Audiences", "Targeting Interests", "Targeting Behaviors", "Targeting Languages", "Placements", "Platform Positions", "Optimization Window", "Attribution Setting", "Conversion Window", "Promoted Object Pixel ID", "Promoted Object Custom Event Type", "Destination Type", ] AD_FIELDS = [ "Ad Name", "Ad Status", "Body", # Primary text "Title", # Headline "Caption", # Link description "Link", # Destination URL "Call To Action", "Image Hash", # For existing images "Image File", # For new uploads "Video ID", # For existing videos "Video File", # For new uploads "Website URL", # Can be different from Link "Display Link", # vanity URL shown "UTM Source", "UTM Medium", "UTM Campaign", "UTM Term", "UTM Content", "Dynamic Creative", "Additional Body 1", "Additional Body 2", "Additional Body 3", "Additional Title 1", "Additional Title 2", "Additional Title 3", "Additional Image 1", "Additional Image 2", "Use Page Post", "Page Post ID", "Instagram Actor ID", "Facebook Page ID", ] # Carousel specific CAROUSEL_FIELDS = [ "Carousel Card 1 Title", "Carousel Card 1 Body", "Carousel Card 1 Link", "Carousel Card 1 Image", "Carousel Card 2 Title", "Carousel Card 2 Body", "Carousel Card 2 Link", "Carousel Card 2 Image", # ... up to 10 cards ] # Multi-language DLO MULTI_LANGUAGE_FIELDS = [ "Language 1 Code", "Language 1 Body", "Language 1 Title", "Language 1 Link", "Language 2 Code", "Language 2 Body", "Language 2 Title", "Language 2 Link", ] # Partnership ads PARTNERSHIP_FIELDS = [ "Partnership Ad Code", "Creator Account ID", ] ALL_FIELDS = ( CAMPAIGN_FIELDS + AD_SET_FIELDS + AD_FIELDS + CAROUSEL_FIELDS[:8] + # Include first 2 carousel cards MULTI_LANGUAGE_FIELDS[:8] + # Include 2 languages PARTNERSHIP_FIELDS ) @staticmethod def validate_date(date_str: str) -> bool: """Validate date format (YYYY-MM-DD)""" try: datetime.datetime.strptime(date_str, "%Y-%m-%d") return True except ValueError: return False @staticmethod def validate_budget(budget: str) -> bool: """Validate budget is a positive number""" try: return float(budget) > 0 except ValueError: return False @staticmethod def validate_age(age: str) -> bool: """Validate age is between 13-65+""" try: age_int = int(age) return 13 <= age_int <= 65 except ValueError: return age == "65+" def generate_traffic_campaign( self, campaign_name: str, ad_set_name: str, ad_name: str, daily_budget: float, target_url: str, ad_copy: Dict[str, str], image_file: str = None, image_hash: str = None ) -> List[Dict]: """ Generate a complete traffic campaign CSV row Args: campaign_name: Name of the campaign ad_set_name: Name of the ad set ad_name: Name of the ad daily_budget: Daily budget in dollars target_url: Landing page URL ad_copy: Dict with 'body', 'title', 'caption' image_file: Filename for new image upload image_hash: Hash for existing image in library Returns: List of row dictionaries ready for CSV """ row = { # Campaign Level "Campaign Name": campaign_name, "Campaign Objective": CampaignObjective.OUTCOME_TRAFFIC.value, "Buying Type": "AUCTION", "Campaign Status": "ACTIVE", "Campaign Budget Optimization": "FALSE", "Campaign Budget": "", "Campaign Bid Strategy": "LOWEST_COST_WITHOUT_CAP", "Campaign Spend Limit": "", # Ad Set Level "Ad Set Name": ad_set_name, "Optimization Goal": OptimizationGoal.LINK_CLICKS.value, "Billing Event": BillingEvent.LINK_CLICKS.value, "Bid Strategy": "LOWEST_COST_WITHOUT_CAP", "Bid Amount": "", "Daily Budget": str(daily_budget), "Lifetime Budget": "", "Start Date": datetime.datetime.now().strftime("%Y-%m-%d"), "End Date": "", "Ad Set Status": "ACTIVE", "Targeting Age Min": "18", "Targeting Age Max": "65+", "Targeting Gender": "All", "Targeting Locations": "United States", "Targeting Custom Audiences": "", "Targeting Excluded Custom Audiences": "", "Targeting Interests": "", "Targeting Behaviors": "", "Targeting Languages": "en", "Placements": "Automatic", "Platform Positions": "Facebook,Instagram", "Optimization Window": "1_DAY", "Attribution Setting": "7_DAY_CLICK_1_DAY_VIEW", "Conversion Window": "", "Promoted Object Pixel ID": "", "Promoted Object Custom Event Type": "", "Destination Type": "WEBSITE", # Ad Level "Ad Name": ad_name, "Ad Status": "ACTIVE", "Body": ad_copy.get('body', ''), "Title": ad_copy.get('title', ''), "Caption": ad_copy.get('caption', ''), "Link": target_url, "Call To Action": CallToAction.LEARN_MORE.value, "Image Hash": image_hash or "", "Image File": image_file or "", "Video ID": "", "Video File": "", "Website URL": target_url, "Display Link": "", "UTM Source": "facebook", "UTM Medium": "paid_social", "UTM Campaign": campaign_name.lower().replace(" ", "_"), "UTM Term": "", "UTM Content": ad_name.lower().replace(" ", "_"), "Dynamic Creative": "FALSE", "Additional Body 1": "", "Additional Body 2": "", "Additional Body 3": "", "Additional Title 1": "", "Additional Title 2": "", "Additional Title 3": "", "Additional Image 1": "", "Additional Image 2": "", "Use Page Post": "FALSE", "Page Post ID": "", "Instagram Actor ID": "", "Facebook Page ID": "", # Carousel (empty for single image) "Carousel Card 1 Title": "", "Carousel Card 1 Body": "", "Carousel Card 1 Link": "", "Carousel Card 1 Image": "", "Carousel Card 2 Title": "", "Carousel Card 2 Body": "", "Carousel Card 2 Link": "", "Carousel Card 2 Image": "", # Multi-language (empty for single language) "Language 1 Code": "", "Language 1 Body": "", "Language 1 Title": "", "Language 1 Link": "", "Language 2 Code": "", "Language 2 Body": "", "Language 2 Title": "", "Language 2 Link": "", # Partnership (empty) "Partnership Ad Code": "", "Creator Account ID": "", } return [row] def write_csv(self, rows: List[Dict], filename: str): """Write rows to CSV file""" with open(filename, 'w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=self.ALL_FIELDS) writer.writeheader() writer.writerows(rows) print(f"āœ… CSV generated: {filename}") print(f" Rows: {len(rows)}") print(f" Fields: {len(self.ALL_FIELDS)}") # Example usage if __name__ == "__main__": generator = FBAdsCSVGenerator() # Generate a sample campaign for Jake's brand campaign_rows = generator.generate_traffic_campaign( campaign_name="OpenClaw Launch Campaign", ad_set_name="Cold Traffic - Tech Enthusiasts", ad_name="OpenClaw Hero Ad v1", daily_budget=50.00, target_url="https://openclaw.com/?utm_source=facebook&utm_medium=paid_social", ad_copy={ 'body': "Meet OpenClaw: The AI assistant that actually gets stuff done. No prompts, no hand-holding. Just tell it what you want and watch it work. Join 10,000+ users automating their workflows.", 'title': "OpenClaw - AI That Actually Works", 'caption': "Start Your Free Trial Today" }, image_file="openclaw_hero_1080x1080.jpg" ) # Write to CSV output_file = "facebook_ads_bulk_upload.csv" generator.write_csv(campaign_rows, output_file) print("\nšŸ“‹ Next Steps:") print("1. Upload your creative (openclaw_hero_1080x1080.jpg) to your computer") print("2. Go to Meta Ads Manager") print("3. Click the ā‹® menu > Import & Export > Import Ads") print(f"4. Upload {output_file}") print("5. Upload your creative file when prompted") print("6. Review and publish!") print("\nšŸŽÆ Field Validation Rules:") print("- Campaign Status: ACTIVE or PAUSED") print("- Dates: YYYY-MM-DD format") print("- Budget: Positive numbers only (no $ symbol)") print("- Age: 13-65 or '65+'") print("- Gender: All, Male, or Female") print("- Image Hash: Get from existing library or leave blank for new upload") print("- UTM parameters: Auto-generated for tracking") print("\nšŸ’” Pro Tips:") print("- For multiple ads: duplicate the row and change Ad Name + creative") print("- For carousel: fill Carousel Card 1-10 fields") print("- For DLO: fill Language 1-5 Code/Body/Title/Link fields") print("- For dynamic creative: set Dynamic Creative=TRUE and fill Additional fields")