A/B Experiments

Deterministic sticky A/B variant assignment and deduped exposure tracking as a Convex component, with GDPR erasure and multi-tenant scoping built in.

Installation

npm install @vllnt/convex-experiments

About A/B Experiments

@vllnt/convex-experiments assigns subjects to weighted variants and records which variants they encounter. It supplies the assignment and exposure layer of an A/B test while your application measures outcomes.

Define weighted variants, start enrollment, and use logExposure to assign and record a subject. peek resolves a variant without writing, and results returns assignment and exposure tallies. Stopping an experiment preserves its recorded data. Scopes separate experiments across surfaces or tenants.

The host authenticates subjects, authorizes experiment management, and records conversions or quality scores. Exposure tallies are not conversion analysis or statistical significance. Use a new experiment key when changing an established allocation.

Maintained by [bntvllnt](https://bntvllnt.com) at [VLLNT](https://vllnt.com). Find the author on [GitHub](https://github.com/bntvllnt) and [X](https://x.com/bntvllnt).

Benefits

Use cases

how to add A/B testing to a Convex app

The @vllnt/convex-experiments component integrates directly into your Convex app via defineApp. You call experiments.define to create an experiment with weighted variants, then call experiments.logExposure inside any mutation to enroll a subject and record the exposure in one step. The variant string is returned immediately and can gate behavior in the same function call.

sticky variant assignment in Convex without database read

The @vllnt/convex-experiments component derives the variant deterministically from a hash of the experiment salt and subjectRef, so the same subject always lands in the same bucket even before anything is stored. The peek method exposes this as a read-only query with no writes, which is useful for SSR or flicker-free first paint scenarios.

how to track experiment exposure counts in Convex

Calling logExposure deduplicates writes so each subject produces exactly one tallied row per experiment. The results method returns per-variant counts for assigned, subjects, exposures, and weight without scanning the raw exposure table, keeping reads efficient as the experiment scales.

multi-tenant A/B experiments in Convex

The @vllnt/convex-experiments component accepts an optional scope parameter on every method. Passing a tenant or surface identifier folds it into the hash so bucketing is independent per namespace. All enrollment, assignment, and erasure operations respect the scope, letting a single component installation serve multiple tenants.

Frequently asked questions

Can the same subject ever be reassigned to a different variant after initial enrollment?

No. In @vllnt/convex-experiments, once a subject is assigned to a variant that assignment is immutable. The variants and salt fields are also locked after the first enrollment, and attempting to change them throws an error. To re-randomize a cohort you define a new experiment key.

How does the component handle GDPR right-to-erasure requests?

The @vllnt/convex-experiments component exposes a forgetSubject mutation that deletes a single subject's assignment and exposure records for a given experiment key and optional scope. The deleteExperiment mutation performs a full cascade delete of all data for an experiment. Neither operation affects the per-variant result tallies for other subjects.

Does the component include authentication or access control?

The @vllnt/convex-experiments component is auth-agnostic. The host application is responsible for resolving identity and deciding who may call define, setStatus, logExposure, or forgetSubject. The component receives an opaque subjectRef string and never inspects it, so you pass in whatever identifier your auth layer provides.

Is there a way to read a subject's variant in React without triggering a write?

Yes. The component exposes a peek query that returns the deterministic variant for a subject without writing any data. The optional @vllnt/convex-experiments/react package provides a useVariant hook that wraps a host-re-exported peek query reference, enabling flicker-free first paint. The React package is tree-shakeable and is not pulled in for backend-only consumers.

What Convex version is required, and how is the component registered?

The @vllnt/convex-experiments component requires convex@^1.41.0 as a peer dependency. It is registered by importing the convex.config entry point and calling app.use(experiments) inside your convex/convex.config.ts file, following the standard Convex component installation pattern.

Links