System-managed experimentsStay organized with collectionsSave and categorize content based on your preferences.
System-managed experiments are used to A/B test changes by creating one or more
modifiable treatment campaigns that run alongside an original control campaign.
This workflow is supported for the followingExperimentTypevalues:
SEARCH_CUSTOM: A custom experiment consisting of search campaigns.
DISPLAY_CUSTOM: A custom experiment consisting of display campaigns.
HOTEL_CUSTOM: A custom experiment consisting of hotel campaigns.
YOUTUBE_CUSTOM: A custom experiment consisting of Video campaigns.
PMAX_REPLACEMENT_SHOPPING: An experiment to test how your Shopping
campaigns perform compared to Performance Max.
Setup
The setup for system-managed experiments follows these steps:
Create anExperiment.
CreateExperimentArmresources for control and treatment.
Modify thein_design_campaignsin the treatment arms.
1. Create an experiment
The first step in running an experiment using the Google Ads API is to create anExperiment. This resource defines some key
information about the experiment you want to run. You don't specify any of the
campaigns involved in the experiment at this step.
Here's an overview of some key fields for anExperiment:
name: Each experiment must have a unique name.
description: An optional field that you can use to reference later. Does
not affect how the experiment runs.
suffix: The suffix will be appended to the end of the names of the
treatment campaigns so you can distinguish them from the control campaign.
These concepts will be explained further instep
2.
type: What type of experiment to run. For system-managed experiments, useSEARCH_CUSTOM,DISPLAY_CUSTOM,HOTEL_CUSTOM,YOUTUBE_CUSTOM, orPMAX_REPLACEMENT_SHOPPING.
status: When creating an experiment, set this field toSETUP. Later on,
once you begin the experiment, this field will let you check the current
status.
start_dateandend_date: Specify when the experiment should start and
end.
sync_enabled: Disabled by default. If set totrue, changes made to the
original campaign while your experiment is running are automatically copied
to the experiment campaign.Learn
more.
defcreate_experiment_resource(client:GoogleAdsClient,customer_id:str)->str:"""Creates a new experiment resource.Args:client: an initialized GoogleAdsClient instance.customer_id: a client customer ID.Returns:the resource name for the new experiment."""experiment_operation:ExperimentOperation=client.get_type("ExperimentOperation")experiment:Experiment=experiment_operation.createexperiment.name=f"Example Experiment #{uuid.uuid4()}"# We specify SEARCH_CUSTOM to create a standard search campaign experiment.# This type uses a standard draft-based workflow where the system automatically# creates a draft/in-design campaign for the treatment arm.experiment.type_=client.enums.ExperimentTypeEnum.SEARCH_CUSTOMexperiment.suffix="[experiment]"experiment.status=client.enums.ExperimentStatusEnum.SETUPexperiment_service:ExperimentServiceClient=client.get_service("ExperimentService")response:MutateExperimentsResponse=experiment_service.mutate_experiments(customer_id=customer_id,operations=[experiment_operation])experiment_resource_name:str=response.results[0].resource_nameprint(f"Created experiment with resource name{experiment_resource_name}")returnexperiment_resource_name
defcreate_experiment_resource(client,customer_id)operation=client.operation.create_resource.experimentdo|e|# Name must be unique.e.name="Example Experiment#{(Time.new.to_f*1000).to_i}"e.type=:SEARCH_CUSTOMe.suffix='[experiment]'e.status=:SETUPendresponse=client.service.experiment.mutate_experiments(customer_id:customer_id,operations:[operation],)experiment=response.results.first.resource_nameputs"Created experiment with resource name#{experiment}."experimentend
subcreate_experiment_resource{my($api_client,$customer_id)=@_;my$experiment=Google::Ads::GoogleAds::V24::Resources::Experiment->new({# Name must be unique.name=>"Example Experiment #".uniqid(),type=>SEARCH_CUSTOM,suffix=>"[experiment]",status=>SETUP});my$operation=Google::Ads::GoogleAds::V24::Services::ExperimentService::ExperimentOperation->new({create=>$experiment});my$response=$api_client->ExperimentService()->mutate({customerId=>$customer_id,operations=>[$operation]});my$resource_name=$response->{results}[0]{resourceName};printf"Created experiment with resource name '%s'.\n",$resource_name;return$resource_name;}
Next, createExperimentArmresources to define
the control and treatment groups for the experiment. All arms must be created in
a single request.
Each experiment must have exactly one control arm and one or more treatment
arms. The control arm identifies the base campaign for comparison, and each
treatment arm results in a new campaign where you can make changes to test.
You must also specifytraffic_split, which is the percentage of traffic
directed to each arm. The sum of traffic splits across all arms must be 100.
defcreate_experiment_arms(client:GoogleAdsClient,customer_id:str,base_campaign_id:str,experiment:str,)->str:"""Creates a control and treatment experiment arms.Args:client: an initialized GoogleAdsClient instance.customer_id: a client customer ID.base_campaign_id: the campaign ID to associate with the control arm ofthe experiment.experiment: the resource name for an experiment.Returns:the resource name for the new treatment experiment arm."""operations:List[ExperimentArmOperation]=[]campaign_service:CampaignServiceClient=client.get_service("CampaignService")# The "control" arm references an already-existing campaign.operation_1:ExperimentArmOperation=client.get_type("ExperimentArmOperation")exa_1:ExperimentArm=operation_1.createexa_1.control=Trueexa_1.campaigns.append(campaign_service.campaign_path(customer_id,base_campaign_id))exa_1.experiment=experimentexa_1.name="control arm"exa_1.traffic_split=40operations.append(operation_1)# In standard campaign experiments, creating the treatment arm automatically# generates a draft campaign that you can modify before starting the experiment.operation_2:ExperimentArmOperation=client.get_type("ExperimentArmOperation")exa_2:ExperimentArm=operation_2.createexa_2.control=Falseexa_2.experiment=experimentexa_2.name="experiment arm"exa_2.traffic_split=60operations.append(operation_2)experiment_arm_service:ExperimentArmServiceClient=client.get_service("ExperimentArmService")request:MutateExperimentArmsRequest=client.get_type("MutateExperimentArmsRequest")request.customer_id=customer_idrequest.operations=operations# We want to fetch the draft campaign IDs from the treatment arm, so the# easiest way to do that is to have the response return the newly created# entities.request.response_content_type=(client.enums.ResponseContentTypeEnum.MUTABLE_RESOURCE)response:MutateExperimentArmsResponse=(experiment_arm_service.mutate_experiment_arms(request=request))# Results always return in the order that you specify them in the request.# Since we created the treatment arm second, it will be the second result.control_arm_result:Any=response.results[0]treatment_arm_result:Any=response.results[1]print(f"Created control arm with resource name{control_arm_result.resource_name}")print(f"Created treatment arm with resource name{treatment_arm_result.resource_name}")returntreatment_arm_result.experiment_arm.in_design_campaigns[0]
defcreate_experiment_arms(client,customer_id,base_campaign_id,experiment)operations=[]operations<<client.operation.create_resource.experiment_armdo|ea|# The "control" arm references an already-existing campaign.ea.control=trueea.campaigns<<client.path.campaign(customer_id,base_campaign_id)ea.experiment=experimentea.name='control arm'ea.traffic_split=40endoperations<<client.operation.create_resource.experiment_armdo|ea|# The non-"control" arm, also called a "treatment" arm, will automatically# generate draft campaigns that you can modify before starting the# experiment.ea.control=falseea.experiment=experimentea.name='experiment arm'ea.traffic_split=60endresponse=client.service.experiment_arm.mutate_experiment_arms(customer_id:customer_id,operations:operations,# We want to fetch the draft campaign IDs from the treatment arm, so the# easiest way to do that is to have the response return the newly created# entities.response_content_type::MUTABLE_RESOURCE,)# Results always return in the order that you specify them in the request.# Since we created the treatment arm last, it will be the last result.control_arm_result=response.results.firsttreatment_arm_result=response.results.lastputs"Created control arm with resource name#{control_arm_result.resource_name}."puts"Created treatment arm with resource name#{treatment_arm_result.resource_name}."treatment_arm_result.experiment_arm.in_design_campaigns.firstend
subcreate_experiment_arms{my($api_client,$customer_id,$base_campaign_id,$experiment)=@_;my$operations=[];push@$operations,Google::Ads::GoogleAds::V24::Services::ExperimentArmService::ExperimentArmOperation->new({create=>Google::Ads::GoogleAds::V24::Resources::ExperimentArm->new({# The "control" arm references an already-existing campaign.control=>"true",campaigns=>[Google::Ads::GoogleAds::V24::Utils::ResourceNames::campaign($customer_id,$base_campaign_id)],experiment=>$experiment,name=>"control arm",trafficSplit=>40})});push@$operations,Google::Ads::GoogleAds::V24::Services::ExperimentArmService::ExperimentArmOperation->new({create=>Google::Ads::GoogleAds::V24::Resources::ExperimentArm->new({# The non-"control" arm, also called a "treatment" arm, will automatically# generate draft campaigns that you can modify before starting the# experiment.control=>"false",experiment=>$experiment,name=>"experiment arm",trafficSplit=>60})});my$response=$api_client->ExperimentArmService()->mutate({customerId=>$customer_id,operations=>$operations,# We want to fetch the draft campaign IDs from the treatment arm, so the# easiest way to do that is to have the response return the newly created# entities.responseContentType=>MUTABLE_RESOURCE});# Results always return in the order that you specify them in the request.# Since we created the treatment arm last, it will be the last result.my$control_arm_result=$response->{results}[0];my$treatment_arm_result=$response->{results}[1];printf"Created control arm with resource name '%s'.\n",$control_arm_result->{resourceName};printf"Created treatment arm with resource name '%s'.\n",$treatment_arm_result->{resourceName};return$treatment_arm_result->{experimentArm}{inDesignCampaigns}[0];}
Thetraffic_splitmust add up to 100 across all arms.
The control arm must specify exactly one campaign in itscampaignsfield.
3. Modify treatment campaigns
When you create a treatment arm (wherecontrolisfalse), the API
automatically creates a draft campaign based on the control campaign and
populates its resource name in thein_design_campaignsfield of the treatment
arm. You can treat these in-design campaigns as regular campaigns and modify
them with the changes you want to test. The control campaign won't be affected.
These changes are materialized into a real, servable campaign when you schedule
the experiment.
At least one change must be made to an in-design campaign before you can
schedule the experiment.
To retrieve thein_design_campaignsfor a treatment arm, you can queryGoogleAdsService:
After creating the experiment and arms and modifying the treatment draft
campaigns, you can schedule the experiment usingExperimentService.ScheduleExperiment.
This is an asynchronous operation that materializes the in-design campaigns into
actual campaigns, ready to serve once the experiment'sstart_datearrives. SeeAsynchronous errorsfor details on handling long-running
operations.
Report on the experiment
Once the experiment is running, you can query for metrics to compare
performance. See theReporting guidefor details.
Graduate, promote, or end the experiment
After letting the experiment run for a sufficient time, you can choose to end,
promote, or graduate it usingExperimentService.
End: If you want to stop the experiment without applying changes, useEndExperiment. The
treatment campaigns stop serving, but are not removed. This is a synchronous
operation.
Promote: If you are satisfied with the treatment arm's performance and
want to incorporate the changes into your original campaign, usePromoteExperiment.
This copies changes from the treatment arm to the control campaign and stops
the treatment arm from serving. This is an asynchronous operation; seeAsynchronous errorsfor details. Note: Experiments of typePMAX_REPLACEMENT_SHOPPINGcannot be promoted.
Graduate: If you liked the changes, but want them to exist separately
from the original campaign, useGraduateExperiment.
This converts the treatment campaign into a standard, independent campaign
that continues to serve outside the context of the experiment. The control
campaign is not modified. This is a synchronous operation.
Type-specific requirements
PMAX_REPLACEMENT_SHOPPING:
Requires exactly two arms (one control, one treatment).
The control arm must specify one Shopping campaign in itscampaignsfield.
For the treatment arm, you have two options for setting up the Performance
Max campaign:
To create anewPMax campaign: Leave the treatment arm'scampaignsfield empty. The API automatically creates an in-design campaign based
on the control Shopping campaign, as described inStep
3.
To use anexistingPMax campaign: Specify the resource name of the
PMax campaign in the treatment arm'scampaignsfield. If you select
this option, no in-design campaign is created, and you can skipStep 3.
It is recommended to discard the first 7 days of statistics from your
evaluation to give the campaign time to finish its ramp-up and learning
phase.
Cannot be promoted.
YOUTUBE_CUSTOM:
Supports a maximum of 10 arms.
To test assets, setcreative_asset_testing_infoon the experiment arm.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2026-06-10 UTC."],[],[]]