Skip to content

Implement core logic of dynamic funnels support - #6762

Merged
zoldar merged 26 commits into
masterfrom
dynamic-funnel-core
Oct 20, 2026
Merged

zoldar merged 26 commits into
masterfrom
dynamic-funnel-core

Conversation

@zoldar

@zoldar zoldar commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Changes

Follow-up to #6761. Implements support for configuring steps dynamically via core funnels API.

There are no user facing changes at this stage yet. The PR is focused on introducing core logic for supporting querying and configuring funnels with dynamic steps.

The key addition is dynamic_steps embed introduced in Plausible.Funnel schema. This field, along with core API: changeset/2 with extended put_steps/1, goals/1, steps/1, as_goal/1 and swap/1, provides the core abstraction for manipulating steps of the funnel without having to explicitly handle division between goal-backed, static steps and goal-less dynamic steps.

The DynamicStep struct embed merges properties of a step with those of the Goal struct, reusing significant part of its validation logic too.

The put_steps stage in the changeset now ensures that steps passed as params are seamlessly split into static and dynamic ones, preserving their original ordering.

The funnel steps must always be retrieved via steps/1 function to ensure both types of steps are returned in a consistent shape and order.

Similarly, goal/1 returns a consistent list of step goals. In the case of dynamic steps, the Goal structs are populated on the fly. Additionally, there's as_goal to retrieve respective Goal struct on the basis of a given step.

Finally, swap/2 is exposed for a pretty specific case of swapping funnel steps in place without parsing through params. It ensures consistent step ordering is ensured. It's current only use is calculating revenue metrics when querying funnel stats.

The steps of different types can be mixed freely within a single funnel as long as constraints like minimum and maximum steps count are met.

Funnels.list/1 and Funnels.with_goals_query/1, which are base functions for querying funnel definitions, are also updated to gracefully handle fully dynamic funnels (steps joins getting switched to LEFT) and return accurate data (steps_count accounting for steps being spread among 2 columns).

One last significant change is how ephemeral funnel is built in Live.FunnelSettings.Form. Until now, it only considered event_name and page_path fields of the Goal as all the other were filtered out. This meant that the numbers from funnel evaluation in form view did not reflect what an actual funnel would return. With scroll_threshold and custom_props taken into consideration, the evaluation result has much higher fidelity. The currency is not strictly necessary as the evaluation does not query for revenue. Regardless, it was added for completeness' sake - the cost of sending it over the wire is not that significant, given it's at most 3 extra bytes + JSON wrapping.

The rest is pretty much a sweeping scan of the codebase and replacing direct references to the funnel steps with usage of newly introduced API functions mentioned earlier.

There's one consequence of introducing the notion of dynamic steps to consider - the funnel steps might no longer be unique within a single funnel. Even though the steps join table has a unique constraint on funnel_id and goal_id combination, there's currently nothing that would stop somebody (once the API is exposed to the users) from using a goal step and a dynamic step with exact same parameters, or multiple dynamic steps with exact same parameters.

The follow-up PR #6741 introducing final batch of changes to the FunnelSettings form will still filter out goals used in previous steps from the suggestions for further steps. However, this can't be enforced via database constraints anymore - and it's not explicitly disallowed on the changeset level either.

Tests

  • Automated tests have been added

@zoldar
zoldar force-pushed the dynamic-funnel-core branch from 5618ffa to c744fab Compare October 8, 2026 10:23
@zoldar
zoldar changed the base branch from master to dynamic-funnel-migration October 8, 2026 10:24
@zoldar
zoldar force-pushed the dynamic-funnel-core branch 2 times, most recently from 3fbe6de to 82ccead Compare October 14, 2026 11:42
Base automatically changed from dynamic-funnel-migration to master October 14, 2026 11:54
@zoldar
zoldar force-pushed the dynamic-funnel-core branch from 82ccead to 4133094 Compare October 14, 2026 12:33
@zoldar zoldar added the preview label Oct 14, 2026
@github-actions

Copy link
Copy Markdown
Preview environment👷🏼‍♀️🏗️
PR-6762

@zoldar
zoldar requested a review from a team October 14, 2026 15:56
@zoldar
zoldar marked this pull request as ready for review October 14, 2026 15:57
@zoldar
zoldar force-pushed the dynamic-funnel-core branch 2 times, most recently from d5752c3 to 8c34891 Compare October 15, 2026 11:06

@apata apata left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elegant, nice work! Some minor comms below:

Comment thread extra/lib/plausible/funnel.ex Outdated
Comment thread extra/lib/plausible/funnels.ex Outdated
Comment thread extra/lib/plausible/funnel.ex Outdated
Comment thread extra/lib/plausible/funnels.ex
@zoldar
zoldar force-pushed the dynamic-funnel-core branch from 4659a74 to 9df904b Compare October 20, 2026 08:11
@zoldar
zoldar added this pull request to the merge queue Oct 20, 2026
Merged via the queue into master with commit 4becfc1 Oct 20, 2026
21 checks passed
@zoldar
zoldar deleted the dynamic-funnel-core branch October 20, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants