Fjord for Craft CMS

Flows & steps

A flow is a URI and a list

A flow has a base URI — funnels/spring — and an ordered list of steps. Each step gets a slug, and is served at funnels/spring/<slug>. A hit on the base URI itself sends the visitor to the first step.

A flow can be scoped to one site or served on all of them. Routes are built from the database, so renaming a slug moves the page immediately.

The six step types

TypeWhat it isReachable when
LandingAn ordinary page at the front of the funnelalways
Opt-inCollects an email onto the cart before the checkoutalways
CheckoutStocks the cart and shows any order bumpsalways
UpsellA post-purchase offer, charged with one clickthe order is complete and paid, the offer is eligible, the window is open
DownsellThe offer shown after the first is declinedthe same, for its own offer
Thank youThe end of the funnelthe order is complete

Upsell and downsell are Pro. A Lite install will not serve them even if the rows exist.

Guarding

Every request goes through one resolver, so there is exactly one answer to "may this visitor see this page". A visitor who types a thank-you URL before ordering is not shown an empty page or an error — they are walked forward to the first step they can see.

The same resolver answers the offer endpoints, which is why a hand-crafted POST cannot charge an offer whose window has closed. The button and the charge ask the same question.

Stocking the cart

A checkout step lists what the funnel sells, by SKU. Cart Mode decides what happens to anything already in the basket:

  • Add to the cart — the funnel's products go in alongside whatever was there.
  • Replace the cart — the funnel sells only its own products.
  • Leave the cart alone — the step sells whatever the visitor arrived with.

Stocking happens once per session. A visitor who reloads the checkout, or comes back to it from a failed payment, does not end up with two of everything.

SKUs are used rather than an element picker on purpose: not every Commerce purchasable is a product variant, and a SKU means the same thing in every environment — which is what makes export and import safe.

Where "next" goes

By default, the next enabled step in order — skipping any the visitor cannot see. That is how a declined upsell falls through to its downsell and on to the thank-you page without anyone wiring the chain by hand.

Pro adds conditional routes. A step can carry a list of {"stepHandle": "…", "condition": {…}} evaluated in order when the visitor leaves it; the first match wins. The condition is a Commerce order condition, plus one rule Commerce does not have: Customer's Previous Orders, which counts completed orders excluding the one being evaluated — the exclusion is the whole point, or every first-time buyer looks like a returning one.

A flow can also carry entry rules, which decide where a hit on the base URI lands. A returning customer can skip the landing page and go straight to the checkout.

Sessions

A visitor's place in a funnel lives in a Fjord session, keyed by a cookie — not Craft's user session, because a funnel has to work for someone who has never logged in. It carries the cart, the completed order, the split-test arm they were given, which steps have stocked the cart, and the payment context captured during checkout.

Sessions expire after the configured lifetime (72 hours by default) and are pruned by garbage collection. Their recorded events survive: the analytics are the reason the sessions existed.

The store checkout

One flow can be marked Store Checkout. That does not intercept any of Commerce's URLs — a Craft store owns its own checkout path and may have put it anywhere. Instead the flow becomes what craft.fjord.storeCheckoutUrl points at, so a cart template can write:

<a href="{{ craft.fjord.storeCheckoutUrl ?? url('shop/checkout') }}">Checkout</a>

and work whether or not a funnel has taken it over. Marking a second flow as the store checkout demotes the first, so the store never has two.