All posts
ASOJune 2, 20267 min read

Picking your experiment type: a five-value taxonomy walkthrough

Every ASOLOOP experiment resolves to one experiment type — five values when this walkthrough shipped, seven today — and the type is not cosmetic. It decides what the experiment can actually measure post-install, what identifiers you need to have ready, and what the create form will refuse to save. Here is the practical walkthrough.

A

ASOLOOP team

Field notes

Editor's note (2026-08-27). The note that stood here from 2026-07-29 was wrong: Google Play never retired its native store-listing experiment. What actually changed was ASOLOOP's picker, which temporarily defaulted Android experiments away from native_sle — the surface itself stayed live, and native_sle has been the Android default again since 2026-08-02. Google Play exposes both organic lanes: Google SLE (up to 5 concurrent localized experiments × 3 treatments) and Custom Store Listings (CSL) — up to 5 active, up to 50 live custom pages. The rest of this walkthrough — how the type decides what you can measure post-install — is unchanged.

Editor's note (2026-09-02). The create-time picker no longer shows these five values as a menu. Since 2026-07-29 the Type step shows platform-scoped cards with a subtype layer — on an iOS app: Apple PPO and Apple CPP (live), plus Apple Search Ads, Google Ads and Custom Workflows (Coming soon); on an Android app: Google SLE and Google CSL (live), plus Google Ads and Custom Workflows (Coming soon). Each card maps onto a wire value (Apple PPO → native_ppo, Google SLE → native_sle, Apple CPP → cpp_before_after, Google CSL → csl_organic — those last two joined the enum after this post shipped, so the taxonomy is seven values today, not the five walked through below; the paid cards → cpp_paid / csl_paid when they go live), and the former Custom deeplink card is retired from the picker — owned_routed survives only as the wire value on imported and API-created experiments. Questions 1 and 2 below are rewritten to that picker, and four downstream claims were corrected in the same pass because runtime refuted them: the native_sle scope note, the keyword-cluster constraint (it gates the Google CSL create lane, not the csl_paid wire value), the claim that the create form discloses a precision tier before launch (it shows a plain-words Measured by line instead), and a "More options" expander that does not exist in the product. The identifier table and the per-value semantics are unchanged.

Real question, near-verbatim from a Manager onboarding their first app last quarter:

Which of these do I pick? It defaults to native_ppo but I'm running this through Apple Search Ads — does that matter?

It matters. That is what this walkthrough is for.

The hub on per-app, per-audience design covers the why of the five-value taxonomy — why a global-model tool collapses these into a single "experiment" type, why ASOLOOP refuses that compression, and what the per-app commitment buys you in exchange. This piece is the how. What each value means in plain English. How to pick the right one for the experiment in front of you. What the create form will catch if you pick wrong.


The five values, in plain English

There were five enum values when this post shipped — seven today, per the editor's note above; the two newer ones (cpp_before_after, csl_organic) are organic store lanes in the same measurement class as the native pair — captured at experiment-create time, one required selection.

Two of them are native organic — your experiment runs on the store's own experimentation surface against organic browse and search traffic.

  • native_ppo — Apple Product Page Optimization. iOS. App Store Connect's native experimentation surface. Traffic source is App Store organic browse and search.
  • native_sle — Google Store Listing Experiment. Android. Play Console's native experimentation surface. Traffic source is Play Store organic browse and search. An SLE runs against your default listing; a custom-listing experiment is a different card (Google CSL) with its own enum value, csl_organic.

Three of them are routed — the traffic carries a unique identifier that lets the platform tie an install back to a specific variant.

  • cpp_paid — Apple Custom Product Page surfaced via paid traffic. iOS. The traffic carries a ppid (the Custom Product Page identifier). Common acquisition paths: Apple Search Ads creative-set routing, paid social ads with ppid deep links, paid search routed to a CPP.
  • csl_paid — Google Custom Store Listing surfaced via paid traffic. Android. The traffic carries utm_content and Install Referrer parameters. Common acquisition path: Google Ads with UTM-tagged campaigns routed to a CSL.
  • owned_routed — any iOS or Android traffic routed through your own MMP tracker. Email, push, social, web banners, anything where the link goes through OneLink, Branch, Adjust, or equivalent. The identifier is the tracker URL or the OneLink ID.

That is the taxonomy. Two native organic, three routed. Pick one per experiment.


How to pick: three questions per app

Walk these in order. Each question is independent of the others.

Question 1 — Which platform is this experiment on?

Filters the cards. ASOLOOP's picker scopes the Type step by the connected app's platform: an iOS app shows the App Store Connect cards (Apple PPO, Apple CPP) and an Android app shows the Google Play Console cards (Google SLE, Google CSL), each with the paid and Workflow cards greyed out as Coming soon. Under the hood native_ppo and cpp_paid are iOS only, native_sle and csl_paid are Android only; owned_routed is the one value that lives on both platforms, but it is no longer a card you can pick — it arrives only on imported or API-created experiments.

Question 2 — Is the traffic for this experiment organic store traffic, paid acquisition, or owned channels?

This is where most picks resolve.

  • If the experiment runs against your normal App Store or Google Play traffic with no campaign assignment — pick the native card for the platform (Apple PPO → native_ppo on iOS, Google SLE → native_sle on Android). This is the default and the safe choice when in doubt. A before-and-after read on a custom page is the other organic route: Apple CPP (cpp_before_after) or Google CSL (csl_organic).
  • If the experiment routes Apple Search Ads creative sets to a specific Custom Product Page — that is cpp_paid, behind the Apple Search Ads card once it goes live (it renders Coming soon today).
  • If the experiment routes Google Ads campaigns to a Custom Store Listing — that is csl_paid, behind the Google Ads card once it goes live (also Coming soon today).
  • If the experiment routes an owned channel (your own email list, push notifications, in-product banners, your website) through OneLink / Branch / Adjust — that is owned_routed, which the picker no longer offers as a card; it reaches ASOLOOP through the experiment import or the API. Both platforms.

Question 3 — Do you have the routed identifier ready?

For the three routed values, you cannot save the experiment without the identifier. The create form is strict by design. If you are not sure what identifier the campaign carries, do not pick a routed type yet — go set up the routing first, then come back.

The decision is per app, per experiment. Per app, per audience is a core ASOLOOP commitment — and that commitment shows up here as the rejection of a single portfolio-wide default. You will be running native_ppo on one app's organic baseline test, cpp_paid on the same app's Search Ads-routed variant test, and owned_routed on a third app's email-driven cycle, all at the same time. The taxonomy is built to support that.


What you need to have ready before you save

For the three routed types, ASOLOOP captures one specific identifier per experiment. The format constraints are checked at save time.

Experiment typeIdentifier fieldFormat constraint
cpp_paidppidApple Custom Product Page ID — 10 to 20 alphanumeric characters
cpp_paid (Apple Search Ads creative routing)asa_creative_idApple Search Ads creative identifier — integer
csl_paidutm_contentURL-safe ASCII, up to 256 characters
owned_routedtracker_urlHTTPS URL with parseable AppsFlyer / Adjust / Branch host
owned_routedonelink_idAlphanumeric subdomain segment, 6 to 24 characters

One identifier per experiment. The mutual exclusivity rule is strict: a single experiment carries exactly one identifier value at a time. If your campaign needs to track a ppid and an asa_creative_id separately, run two experiments, not one.

For native_ppo and native_sle, the routed_identifier field stays null. If you pick a native type and then paste a tracker URL into the identifier field, the create form blocks with routed_identifier_unexpected_for_native_type. The reverse — picking a routed type and leaving the identifier null — blocks with routed_identifier_missing.

One more constraint on the Google CSL lane: a Custom Store Listing needs a non-empty keyword cluster, and the create form will not let you save a Google CSL experiment — or author a CSL page — with an empty target_keywords[] field. (Apple CPP does NOT require keywords — only Google CSL does. The gate lives on the CSL create lane itself, not on the csl_paid wire value.)

The validation is not a UX flourish. The platform mutation will fail downstream if the inputs are wrong; ASOLOOP blocks at create time so the failure surface is the picker, not the Playwright submission.


What changes downstream once you pick

The reason the picker is up-front is that the experiment type decides what kind of signal the experiment can produce.

cpp_paid, csl_paid, and owned_routed route through identifiers the platform can resolve at install time — per-variant attribution is available. ASOLOOP's signal contribution from these experiments carries the full per-variant weight.

native_ppo and native_sle route through organic store traffic that the platform does not expose at the variant level. ASOLOOP uses cohort-window matching against a baseline window to approximate the result. The cohort-window contribution carries reduced weight relative to per-variant deterministic. What the create form shows you before launch is the lane's measurement basis in plain words — each subtype row states what it is Measured by — rather than a named precision tier; the tier itself is resolved at read time from consent and cohort size, and surfaces with the result. Either way the commitment is the same: show the work on what the experiment can and cannot prove.

For iOS routed types on ATT-denied devices, ASOLOOP falls back to a modeled tier — same routed type, but the per-variant signal is derived from SKAdNetwork aggregates instead of deterministic attribution. The contribution carries a confidence_flag: modeled and the UI shows "modeled, not measured" so you read the tier honestly.

There is also a floor. If the cohort window has fewer than roughly 500 installs, ASOLOOP writes no signal contribution from that experiment — the tier collapses to tier_c_insufficient_sample. You see the experiment result; you do not see a false-precision signal computed off a thin base. The picker does not preview this — you find out at finalization — but the feasibility gate on the create form will warn you if the projected install volume is borderline.

The point of disclosing all of this is not to discourage native_ppo. Most ASO experiments are native_ppo or native_sle. The point is that the precision tier you get is set the moment you pick. Picking it deliberately — instead of accepting a default — is the operator-side of the per-app commitment.


A few things the system will not let you do

The create form is not just a save button. Three rules to know.

Mutual exclusivity on the routed identifier. One value per experiment. The system rejects attempts to populate both ppid and asa_creative_id on the same experiment row, even though both are valid cpp_paid identifier types. If your campaign has two routing dimensions you want to attribute separately, that is two experiments.

Null behavior is strict on both sides. Routed types require the identifier (NOT NULL). Native types require the identifier to be null. The form blocks save in either violating direction with a specific error code, so the failure is loud and the recovery is obvious.

Legacy experiments cannot be reclassified. Any experiment created before the type-capture flow shipped is backfilled to the safe default — native_ppo on iOS, native_sle on Android — and the type field is then frozen. You cannot retroactively mark a historical experiment as cpp_paid even if you remember running it through Search Ads at the time. The reason is downstream: the cohort logic treats every backfilled experiment as Tier B regardless of any later evidence that suggests routed traffic, because reclassifying historical experiments after the fact corrupts the agreement-rate calibration the signal model depends on.

These three rules look pedantic on the page. In practice they are the system catching the three patterns that produce silent data corruption — split identifiers that fragment evidence, null-on-routed that makes the routed signal unattributable, and post-hoc reclassification that breaks the calibration metric. The create form's strictness is the price of the per-app commitment being trustable.


The honest summary

The picker is one click. The taxonomy was five values at publication and is seven today. The decision underneath is: what kind of experiment is this, exactly? — and what precision class do you accept for what it can measure?

For operators who only ever run native organic experiments, the native card is the platform default and is pre-selected — Apple PPO on iOS, Google SLE on Android — so the shortest path is to accept it and move on. The other cards stay visible rather than hidden behind an expander.

For ICP 1 operators running mixed portfolios — some apps in pure organic, some routed through Search Ads, some on owned channels — the picker is where the per-app, per-experiment posture shows up at the UX level. You will pick differently for different apps and different campaigns on the same day. That is the design.

The full long-form on why ASOLOOP is built this way lives in per-app, per-audience design. The full long-form on the tier disclosure surface — why the system shows you the precision class up front instead of burying it in fine print — lives in what "inconclusive" actually means.

Other tools advise · ASOLOOP operates

Stop running ASO by hand.

Point ASOLOOP at your app, set your autonomy level, and read the receipts. Seven days, your apps, real experiments running on both stores.

Credit card required to start · Not billed during the trial · Cancel anytime

Your store credentials and your own AI-model keys — inspectable, revocable any time.