Documentation

Ad Configuration

CLI Full Reference

This is the complete reference for the Ads Uploader CLI. For an introduction and getting started guide, see CLI Configuration.

Commands

Authentication

CommandWhat it does
ads loginAuthenticate via browser (opens your default browser)
ads logoutClear stored credentials
ads whoamiShow the currently logged-in user
ads configShow configuration (account, API URL, credentials path)

Browsing

CommandWhat it does
ads accountsList all ad accounts connected to your Meta account
ads account <id>Set a default ad account for future commands
ads pagesList Facebook Pages you can advertise with, including any linked Instagram account, for use with profile overrides
ads campaignsList active campaigns
ads campaigns --status allInclude paused and archived campaigns
ads campaigns --search "text"Filter campaigns by name
ads campaign <id>Show the ad sets inside a campaign
ads adsets --campaign <id>List ad sets in a campaign (supports --search, --status)
ads adset <id>Show the ads inside an ad set
ads ad <id>View full ad details including creative settings
ads presetsList your saved API presets
ads presets <id>Show details for a specific preset
ads presets:save --from-ad <adId> --name "Preset Name"Save an existing ad as an API preset
ads text-presetsList your saved text presets
ads text-presets <id>Show details for a specific text preset
ads uploadsList recent upload batches
ads uploads <batchId>Show batch details (files, variants, hashes)

Media Upload

CommandWhat it does
ads upload <files...>Upload images and videos to your ad account
ads upload ./directory/Upload an entire directory

Files are staged in parallel, and transient network failures are retried automatically with backoff. You rarely need to touch these, but they're available:

Upload flagDescription
--concurrency <n>Number of files staged in parallel, 1-6 (default: 4). Large videos are throttled automatically to stay within memory.
--upload-timeout <ms>Per-file upload timeout (default: 120000)
--api-timeout <ms>API request timeout in milliseconds (default: 60000)

Ad Creation

CommandWhat it does
ads create spec.jsonCreate ads from a spec file
ads create:preview spec.jsonDry run showing what would be created
ads create:interactiveGuided wizard (accepts all create flags)

Job Management

CommandWhat it does
ads jobs <jobId>Check the status of a job
ads jobs <jobId> --followStream live progress updates
ads jobs cancel <jobId>Cancel a running job

Create Flags

These flags apply to ads create, ads create:preview, and ads create:interactive. They can be used instead of or alongside a spec file.

FlagDescription
--account <id>Override default ad account
--preset <id>Use a saved API preset (alternative to spec file)
--text-preset <id>Load a saved text preset
--copy-from <adId>Copy settings from an existing ad
--upload <batchId>Specify the upload batch ID
--status <PAUSED|ACTIVE>Set ad status (default: ACTIVE)
--pause-at <level>Pause level: ad (default), adSet, or campaign
--daily-budget <amount>Override daily budget per ad set (currency units, e.g. 50 for $50)
--bid-amount <amount>Override bid/cost cap per ad set (currency units)
--page <id>Use this Facebook Page instead of the template's (see Profile Options)
--instagram <id>Use this Instagram account instead of the template's
--threads <id>Use this Threads profile instead of the template's
--text-file <path>Load text configuration from a JSON file
--expandedShow full headline, primary text, and description values in previews

Browse Flags

These flags are available on campaigns, adsets, adset, and campaign:

FlagDescription
--status <status>active (default) or all
--inactiveShorthand for --status all (on campaigns)
--search <text>Filter by name (on campaigns, adsets)

Detail Flags

These flags are available on ad:

FlagDescription
--expandedShow full headline, primary text, and description values

Common Flags

FlagDescription
--account <id>Override default ad account for any command
--jsonOutput raw JSON (available on most commands, intended for scripting)

Spec File Format

The JSON spec file controls every aspect of ad creation. Only two fields are required: a template source (adPresetId or copyFromAd) and uploadId.

Minimal Spec

{
  "adPresetId": "your_preset_id",
  "uploadId": "batch_abc123"
}

Full Example

{
  "adPresetId": "preset_id_here",
  "uploadId": "batch_abc123",
  "adSet": {
    "name": "My New Ad Set",
    "dailyBudget": 50
  },
  "adNamePattern": "{filename}",
  "texts": {
    "perAd": {
      "hero.jpg": {
        "headlines": ["Main Headline"],
        "bodies": ["Ad copy here."],
        "descriptions": ["Short description"],
        "cta": "SHOP_NOW",
        "link": "https://example.com/landing"
      }
    }
  },
  "creativeEnhancements": "none",
  "options": {
    "status": "PAUSED",
    "pauseAt": "adSet"
  }
}

Template Source

You need one of these to tell the CLI which ad configuration to use as a base.

FieldDescription
adPresetIdA saved API preset ID. Locks in the campaign, ad set, and ad config.
copyFromAdA Facebook ad ID to copy settings from.

When using copyFromAd, provide the upload batch and optionally the campaign and ad set:

{
  "copyFromAd": "120233848667930472",
  "uploadId": "batch_abc123",
  "campaign": { "id": "120233848666410472" },
  "adSet": { "id": "120233848666620472" }
}

To find the right ad ID, drill through your account: ads campaigns then ads campaign <id> then ads adset <id> then ads ad <id>.

Profile Options

By default, new ads inherit the Facebook Page, Instagram account, and Threads profile from the template ad or preset. This is the same Profile Options control available via the Defaults panel in the web app. Override any of them with a profile block (or the --page / --instagram / --threads flags, which take precedence over the spec file):

{
  "copyFromAd": "120233848667930472",
  "uploadId": "batch_abc123",
  "profile": {
    "pageId": "123456789012345",
    "instagramId": "17841400000000000",
    "threadsId": "987654321098765"
  }
}

Run ads pages to list the Page IDs you can use, along with each Page's linked Instagram account.

Important: if you override only the page, the Instagram and Threads profiles are reset (they belonged to the old page) - they are not carried across. To move the page and keep a specific Instagram or Threads profile, set them explicitly. You can also change just the Instagram or Threads profile without touching the page by setting only those fields.

Per-campaign profiles for multi-campaign launches are not supported in CLI specs yet.

Campaign Structure

By default, ads go into the template ad's campaign. You can create a new campaign by providing campaign.name.

For multi-campaign modes, use campaign.mode with a campaigns array:

{
  "campaign": {
    "mode": "duplicate",
    "campaigns": [
      { "name": "Campaign A" },
      { "name": "Campaign B" }
    ]
  }
}
ModeBehavior
"single"Default. One campaign.
"duplicate"All media is duplicated into each campaign.
"split"Media is split evenly across campaigns.

Ad Set Modes

By default, ads go into the template ad's existing ad set. The following modes give you control over how ads are distributed across ad sets.

Create a new ad set:

{ "adSet": { "name": "My Ad Set" } }

Use an existing ad set by ID:

{ "adSet": { "id": "120233848666620472" } }

One ad set per uploaded file:

{ "adSet": { "mode": "perUpload" } }

Auto-group into ad sets of a fixed size:

{ "adSet": { "mode": "autoGroup", "adsPerAdSet": 5 } }

Custom groups with full control over which files go where:

{
  "adSet": {
    "groups": [
      { "name": "Images - April 10", "media": ["hero.jpg", "banner.jpg"] },
      { "name": "Videos - April 10", "media": ["promo.mp4"] }
    ]
  }
}

Ad set naming pattern for multi-ad-set modes:

{ "adSet": { "mode": "perUpload", "namePattern": "Ad Set {index:01}" } }

Variant grouping groups ads by variation identifier into the same ad set:

{ "adSet": { "mode": "autoGroup", "groupVariations": true, "variationIdentifier": "-" } }

Budget and Bid Override

Override the daily budget and/or bid amount on new ad sets. Values are in your account's currency units (e.g. 50 for $50 or 50 euros).

dailyBudget and bidAmount are independent Meta fields:

  • ABO campaigns (budget lives on the ad set): you can set dailyBudget, bidAmount, or both. Bid-cap strategies like COST_CAP, LOWEST_COST_WITH_BID_CAP, and TARGET_COST require a bidAmount alongside the budget.
  • CBO campaigns (budget lives on the campaign): don't set dailyBudget on the ad set - Meta rejects that because budget already comes from the campaign. For bid-cap strategies, set only bidAmount.
{ "adSet": { "dailyBudget": 50 } }
{ "adSet": { "bidAmount": 5 } }
{ "adSet": { "dailyBudget": 50, "bidAmount": 5 } }

Also available as CLI flags: --daily-budget 50, --bid-amount 5, or both.

Text Configuration

Common text applies the same copy to all ads:

{
  "texts": {
    "common": {
      "headlines": ["Headline 1", "Headline 2"],
      "bodies": ["Primary text"],
      "descriptions": ["Description"]
    },
    "strategy": "flexible"
  }
}

Per-ad text lets you set unique copy for each file:

{
  "texts": {
    "perAd": {
      "hero.jpg": {
        "headlines": ["Hero Headline"],
        "bodies": ["Hero copy"],
        "descriptions": ["Hero desc"],
        "cta": "LEARN_MORE",
        "link": "https://example.com/hero",
        "urlTags": "utm_content=hero"
      },
      "banner.jpg": {
        "headlines": ["Banner Headline"],
        "bodies": ["Banner copy"]
      }
    }
  }
}

Per-ad keys are filenames (not full paths). Each entry supports: headlines, bodies, descriptions, cta, link, displayUrl, urlTags. Fields you don't specify inherit from the template ad.

Text presets let you load a saved text configuration:

{ "textPresetId": "preset_id_here" }

You cannot combine textPresetId with texts.

Strategy options control how multiple text variations are handled:

  • "flexible" (default) lets Meta optimize across your text variations. Multiple headlines and bodies become options that Facebook mixes and matches.
  • "separate" creates a separate ad for each text combination.

A top-level CTA applies to all ads. Per-ad CTAs in texts.perAd override it.

{
  "cta": {
    "type": "SHOP_NOW",
    "link": "https://example.com",
    "displayUrl": "example.com"
  },
  "urlTags": "utm_source=facebook&utm_medium=paid"
}

Standard CTA types: LEARN_MORE, SHOP_NOW, SIGN_UP, SUBSCRIBE, GET_OFFER, CONTACT_US, DOWNLOAD, ORDER_NOW, BUY_NOW, BOOK_NOW, APPLY_NOW, GET_QUOTE, GET_IN_TOUCH, WATCH_MORE

Objective-specific CTAs are inherited from the template ad and should not be set manually. Setting these on the wrong campaign type will cause a Facebook API error.

CTARequired Campaign Objective
MESSAGE_PAGEMessenger destination
WHATSAPP_MESSAGEWhatsApp destination
INSTAGRAM_MESSAGEInstagram DM destination
CALL_NOWCall campaign

URL Split-Test (Split Destination)

Provide 2–5 destination URLs under texts.urlVariants and every generated ad set is duplicated once per URL so Meta optimizes each ad and landing-page combination independently.

{
  "texts": {
    "common": { "headlines": ["Hero"], "bodies": ["Copy"] },
    "urlVariants": [
      { "link": "https://example.com/homepage", "label": "homepage" },
      { "link": "https://example.com/quiz", "label": "quiz-v2" }
    ]
  }
}
  • label is optional. When omitted, the last URL path slug is used (/quiz-v2quiz-v2), falling back to the hostname for root URLs.
  • In single-ad-set mode, each variant's label becomes the full ad set name.
  • In perUpload and autoGroup modes, each duplicated ad set's name appends _{label} to the pattern (or substitutes a {destination} token if you include one).
  • The {date} token in a label resolves to today's date (e.g., launch-{date}launch-2026-04-27).
  • Each destination URL must be unique. Identical URLs (or trailing-slash / case variants of the same URL) collapse to one entry, so make sure you have at least 2 distinct destinations.
  • Your ad set budget is multiplied by the number of variants, since each duplicate is its own ad set.
  • Not compatible with special-destination source ads (lead form, Messenger, WhatsApp, Instagram DM, Call). The CLI rejects this combination with a clear error - those formats don't route via cta.link.
  • Per-ad link overrides in texts.perAd lose to the variant URL when both are set.

Creative Enhancements

Control Advantage+ creative enhancements:

{ "creativeEnhancements": "none" }
ValueEffect
omittedAll features off
"metaDefaults"Deprecated alias for "none"
"all"All features on
"none"All features off
["feature1", "feature2"]Only listed features on, rest off

Available features: text_translation, inline_comment, enhance_cta, text_optimizations, reveal_details_over_time, image_brightness_and_contrast, image_touchups, video_auto_crop, video_filtering, image_animation, image_templates, adapt_to_placement, product_extensions, description_automation, add_text_overlay, music, carousel_to_video, carousel_dynamic_description, multi_share_end_card, multi_share_optimized

When cherry-picking features, only list ones relevant to the media type. Video features (video_auto_crop, video_filtering) only apply to video ads. Carousel features (carousel_to_video, carousel_dynamic_description, multi_share_end_card, multi_share_optimized) only apply to carousel ads.

Group uploaded files into carousel ads with per-card text and optional overall carousel text. cardTexts controls individual cards. Overall carousel text can be colocated on the carousel object or set in texts.perAd using the carousel name; colocated fields win when both are present.

{
  "carousel": [
    {
      "name": "My Carousel",
      "cards": ["slide1.jpg", "slide2.jpg", "slide3.jpg"],
      "headlines": ["Overall Carousel Headline"],
      "bodies": ["Overall primary text"],
      "descriptions": ["Overall description"],
      "cta": "SHOP_NOW",
      "link": "https://example.com/carousel",
      "urlTags": "utm_content=my_carousel",
      "cardTexts": [
        { "headline": "Slide 1", "description": "First card", "link": "https://example.com/1" },
        { "headline": "Slide 2", "description": "Second card", "link": "https://example.com/2" }
      ]
    }
  ]
}

Alternative overall-text shape:

{
  "texts": {
    "perAd": {
      "My Carousel": {
        "headlines": ["Overall Carousel Headline"],
        "bodies": ["Overall primary text"],
        "descriptions": ["Overall description"],
        "cta": "SHOP_NOW",
        "link": "https://example.com/carousel",
        "urlTags": "utm_content=my_carousel"
      }
    }
  },
  "carousel": [
    {
      "name": "My Carousel",
      "cards": ["slide1.jpg", "slide2.jpg", "slide3.jpg"]
    }
  ]
}

Cards must reference filenames from the upload batch. Minimum 2 cards per carousel. Files claimed by a carousel are removed from the standard ad list.

Flexible Ads

Group multiple assets into a single flexible ad where Meta picks the best asset per placement:

{
  "flexible": [
    {
      "name": "Multi-Asset Ad",
      "assets": ["hero.jpg", "promo.mp4", "banner.jpg"]
    }
  ]
}

Minimum 2 assets per group. Files claimed by a flexible group are removed from the standard ad list.

Multi Media Ads

Group 2-10 uploaded images or videos into a Meta Multi Media ad:

{
  "multimedia": [
    {
      "name": "Mixed Media Ad",
      "assets": ["hero.jpg", "promo.mp4", "banner.jpg"],
      "assetTexts": [
        {
          "headline": "Hero headline",
          "primaryText": "Hero primary text",
          "description": "Hero description",
          "link": "https://example.com/hero",
          "displayUrl": "example.com/hero"
        }
      ]
    }
  ]
}

Assets must reference filenames from the upload batch. assetTexts is optional and lines up with assets by index; each field is a single override for that asset and blank fields fall back to the ad's main text and URLs. The primary rendered asset uses the ad's main text and URL, and any primary asset text override becomes the first main text option. Videos need a cached public thumbnail URL from upload processing. Files claimed by a Multi Media group are removed from the standard ad list.

Ad Naming

Customize how your ads are named:

{ "adNamePattern": "{filename} - {date}" }
PlaceholderWhat it inserts
{filename}Original filename without extension
{index:01}Zero-padded index (01, 02, 03...)
{variation}Variation identifier if variant grouping is on
{campaign}Campaign name
{date}Current date (YYYY-MM-DD)
{date:short}Short date (MM-DD)
{timestamp}Unix timestamp

Options

{
  "options": {
    "status": "PAUSED",
    "pauseAt": "adSet",
    "schedule": {
      "startTime": "2026-04-01T09:00:00",
      "endTime": "2026-04-30T23:59:59"
    }
  }
}
FieldValuesDescription
status"PAUSED", "ACTIVE"Ad launch status (default: ACTIVE)
pauseAt"ad", "adSet", "campaign"Which level to pause at (default: ad)
schedule.startTimeISO 8601 stringScheduled start time (uses ad account timezone)
schedule.endTimeISO 8601 stringScheduled end time (optional)

Upload and Variant Detection

Variant groups are detected automatically from filename conventions, just like in the web application. See Aspect Ratio Variations for full details on naming conventions.

Ratio suffixes: hero_1x1.jpg + hero_4x5.jpg + hero_9x16.jpg + hero_16x9.jpg + hero_1.91x1.jpg are grouped as one variant ad. Up to 5 ratios per group.

Token position: the ratio token can appear at the end (hero_4x5.jpg), in the middle (hero_4x5_v2.jpg), or at the start (4x5_hero.jpg).

Legacy word suffixes: hero.jpg + hero_vertical.jpg + hero_horizontal.jpg still work and map to 9x16 and 16x9.

The default delimiter is _. You can change it (or accept multiple) in Account > Defaults > Placements > Filename Separator.

Common Patterns

Upload and create with a preset

ads upload ./creatives/hero.jpg ./creatives/banner.jpg
ads create:preview spec.json
ads create spec.json

Where spec.json contains:

{ "adPresetId": "PRESET_ID", "uploadId": "BATCH_ID" }

Copy settings from an existing ad

Browse your account to find the ad:

ads campaigns
ads campaign 120233848666410472
ads adset 120233848666620472
ads ad 120233848667930472

Then create a spec referencing it:

{
  "copyFromAd": "120233848667930472",
  "uploadId": "BATCH_ID"
}

The source ad must have inline creative settings. If it was built from an existing Page post, the CLI will reject it before submitting a create request.

Save an API preset from an existing ad

ads presets:save --from-ad 120233848667930472 --name "Spring Purchase Template"
ads create --preset PRESET_ID --upload BATCH_ID

This saves the same API preset shape used by the web app. The source ad must have inline creative settings; Page-post-backed ads cannot be saved as API presets.

Per-ad text with unique copy per file

{
  "adPresetId": "PRESET_ID",
  "uploadId": "BATCH_ID",
  "texts": {
    "perAd": {
      "hero.jpg": {
        "headlines": ["Summer Sale Now On"],
        "bodies": ["Save up to 50% on all items"],
        "cta": "SHOP_NOW",
        "link": "https://example.com/summer"
      },
      "banner.jpg": {
        "headlines": ["New Collection Available"],
        "bodies": ["Browse our latest styles"],
        "cta": "LEARN_MORE",
        "link": "https://example.com/new"
      }
    }
  }
}

Auto-group into multiple ad sets

{
  "adPresetId": "PRESET_ID",
  "uploadId": "BATCH_ID",
  "adSet": { "mode": "autoGroup", "adsPerAdSet": 3 }
}

Important Notes

  1. Always preview first. create:preview catches config errors before touching Facebook.
  2. Ads are active by default. Use --status PAUSED or "status": "PAUSED" in the spec to create them paused.
  3. uploadId comes from the upload output. It is the batch ID returned by ads upload.
  4. Uploads are tied to an ad account. Files are uploaded directly to the selected account's Facebook media library. The batch ID can only be used with the same account.
  5. copyFromAd needs uploadId. You must provide the upload batch. Optionally provide campaign.id and adSet.id to control placement.
  6. Per-ad text keys are filenames. Use "hero.jpg", not "/path/to/hero.jpg".
  7. textPresetId and texts are mutually exclusive. Use one or the other, not both.
  8. Objective-specific CTAs are inherited from the template. Do not set MESSAGE_PAGE, WHATSAPP_MESSAGE, etc. manually.