# RFC: Platform-level UTM attribution support for authenticated MFE users

**URL:** <https://discuss.openedx.org/t/rfc-platform-level-utm-attribution-support-for-authenticated-mfe-users/19182>\
**Category:** Development\
**Created:** [July 30, 2026, 1:57pm UTC](https://discuss.openedx.org/t/rfc-platform-level-utm-attribution-support-for-authenticated-mfe-users/19182 "2026-07-30T13:57:58Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![Varun](https://avatars.discourse-cdn.com/v4/letter/v/e9bcb4/32.png) [@Varun](https://discuss.openedx.org/u/Varun)\
**Post date:** [July 30, 2026, 1:57pm UTC](https://discuss.openedx.org/t/rfc-platform-level-utm-attribution-support-for-authenticated-mfe-users/19182/1 "2026-07-30T13:57:58Z")

</div>

Hello community,

I’d like to start a discussion around a gap I’ve encountered with UTM attribution for authenticated users in MFEs.

**Background**

Open edX currently captures UTM parameters for many unauthenticated user flows because Django receives the initial request.

However, when an already-authenticated learner lands directly on an MFE page from an external campaign link, the page is rendered client-side and Django never sees the initial page load. As a result, UTM parameters present in the URL may never reach backend services.

In this scenario:

1. The user is already logged in.

2. The MFE loads directly.

3. UTM parameters exist in the browser URL.

4. No platform-level mechanism currently transports those values to backend services.

**Current Workarounds**

The options today seem to be:

- Implement UTM capture separately in each MFE.

- Rely solely on third-party analytics platforms.

- Propagate query parameters through internal navigation.

Each approach introduces additional maintenance and duplication.

**GitHub Issue**

I’ve opened a GitHub issue with more detailed context and a proposed implementation approach:

GitHub Issue: [Add platform-level UTM capture support for authenticated MFE users · Issue #892 · openedx/frontend-platform · GitHub](https://github.com/openedx/frontend-platform/issues/892)

**Questions for the Community**

- Does this seem like a platform-level concern that belongs in `frontend-platform`?
- Are there existing attribution-related initiatives I should be aware of?
- Would a generic capture-and-forward mechanism be acceptable, leaving storage and attribution modeling to backend implementations?
- Are there privacy, security, or architectural concerns with this approach?

I’d appreciate feedback from maintainers and operators who have dealt with marketing attribution, learner acquisition campaigns, or analytics integrations.

Thanks!

---

<div class="post-metadata">

**Author:** ![arbrandes](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/arbrandes/32/158_2.png) [@arbrandes](https://discuss.openedx.org/u/arbrandes)\
**Post date:** [August 11, 2026, 4:00pm UTC](https://discuss.openedx.org/t/rfc-platform-level-utm-attribution-support-for-authenticated-mfe-users/19182/2 "2026-08-11T16:00:01Z")

</div>

Thanks for bringing up the proposal for public discussion! I commented [in the issue](https://github.com/openedx/frontend-platform/issues/892#issuecomment-5221273435), but it’s worth it bringing up my points here, too.

> [@Varun](#):
>
> - Does this seem like a platform-level concern that belongs in `frontend-platform`?

At this point, frontend-platform should be considered legacy code (though not officially deprecated yet). Any new features should target [frontend-base](https://github.com/openedx/frontend-base/) first. If and when the feature is merged there, we can consider backporting it to frontend-platform and family.

Furthermore, when in doubt if a feature is “core” to Open edX or not, an attempt can be made to implement it as a plugin. And if the extension points don’t exist for the feature, we should discuss creating them:

Can we imagine a generic framework to allow implementing this kind of thing as a plugin, instead of implementing this _specific_ thing? Maybe that’s what you meant by a “a generic capture-and-forward mechanism”, but I’d like to understand how generic it would be in practice.

---

<div class="post-metadata">

**Author:** ![Varun](https://avatars.discourse-cdn.com/v4/letter/v/e9bcb4/32.png) [@Varun](https://discuss.openedx.org/u/Varun)\
**Post date:** [September 9, 2026, 5:32am UTC](https://discuss.openedx.org/t/rfc-platform-level-utm-attribution-support-for-authenticated-mfe-users/19182/3 "2026-09-09T05:32:17Z")

</div>

@arbrandes Thank you for the clear direction. Apologies as it took us long to come up with the proposal. We have revised the proposal based on both your points

**On targeting** `frontend-base` – agreed. The revised proposal targets `frontend-base` exclusively and does not touch `frontend-platform`.

**On the generic plugin framework** – this is exactly what we went with. Rather than adding UTM-specific logic into the core, we are now proposing a generic `AppLifecyclePlugin` interface for `frontend-base`, a new extension point for non-UI, service/behaviour plugins that need to hook into app boot, modify the HTTP client or respond to auth events like logout. UTM attribution is then implemented as a separate standalone package (`@openedx/frontend-plugin-utm-attribution`) that implements this interface. The core framework itself is reusable for consent management, feature flag SDK boot, analytics SDK and similar use cases, UTM being the just reference implementation.

We have put together a full revised RFC document covering the interface design, the plugin, the integration points in initialize(), the standalone plugin package and the Django-side persistence.

Would you and the community be willing to take a look and share feedback – particularly on whether the `AppLifecyclePlugin` interface is the right API surface before we open a draft PR against `frontend-base`?

[OpenEdX-RFC-LifecyclePlugin-UTM.docx](https://discuss.openedx.org/uploads/short-url/tmxo76cF6jrx2wSCD5zjsP2gD85.docx) (37.2 KB)

---

<div class="post-metadata">

**Author:** ![arbrandes](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/arbrandes/32/158_2.png) [@arbrandes](https://discuss.openedx.org/u/arbrandes)\
**Post date:** [September 21, 2026, 6:10pm UTC](https://discuss.openedx.org/t/rfc-platform-level-utm-attribution-support-for-authenticated-mfe-users/19182/4 "2026-09-21T18:10:30Z")

</div>

Thanks for the revision. I don’t think the generic layer is needed, though, because frontend-base already has most of it:

The initialization sequence publishes lifecycle events (`SITE_CONFIG_INITIALIZED`, `SITE_AUTH_INITIALIZED`, `SITE_READY`, `AUTHENTICATED_USER_CHANGED`) that any module can subscribe to. That covers `onAppInit` and `onUserSessionStart`. `initialize()` also accepts an `authMiddleware` array ([ADR 0006](https://github.com/openedx/frontend-base/blob/main/docs/decisions/0006-middleware-support-for-http-clients.rst)) that is applied to every HTTP client the auth service creates, including the cached ones, which covers both HTTP client hooks. And the `App` interface has a per-app `externalScripts` field ([ADR 0014](https://github.com/openedx/frontend-base/blob/main/docs/decisions/0014-per-app-external-scripts.rst)) that is exactly the “run some code at boot with this app’s config” shape; consent banners were the motivating example in that ADR. Section 2 compares against frontend-plugin-framework’s `PluginSlot`, but frontend-base has its own slot system plus these non-UI hooks, so the claim that there is no extension point for any of this doesn’t hold.

`onLogout` has no frontend counterpart to hook. Logout is still just a redirect to the LMS logout URL, so there is no client-side step where the hook could fire. The server-side cookie deletion you already propose is the right place for that. It does edit the logout view, though, which should be reconciled with the “no existing LMS code is modified” claim.

The one thing missing is that `authMiddleware` is only reachable as an `initialize()` option, and the shell calls `initialize()` with just messages, so operators can’t set it from `site.config.tsx`. That is the frontend-base change I’d propose instead: a middleware field on `App`, so that multiple apps compose rather than clobber each other, following the same reasoning that led [ADR 0014](https://github.com/openedx/frontend-base/blob/main/docs/decisions/0014-per-app-external-scripts.rst) to reject a site-level array.

With that in place, UTM attribution becomes an ordinary `App` in its own package: `externalScripts` for the URL capture and cookie write, the new middleware field for the header interceptor, and `App.config` for cookie name and domain. Operators add it to `apps` in `site.config.tsx` like any other app. No new manager, no new plugin type, and the framework-level part of the RFC shrinks to the middleware field.

A few smaller things to adjust when reworking: site config keys are camelCase (`lmsBaseUrl`) and there is no cookie domain key, so the app needs to carry that in its own config; IDs use reverse-domain notation (`org.openedx.frontend.app.*`); and the rollout should be framed around a single shell rather than independently upgraded MFEs.

If the next version comes as a PR that adds an ADR to frontend-base instead of a Word document, that would be ideal!

---

<div class="post-metadata">

**Author:** ![Varun](https://avatars.discourse-cdn.com/v4/letter/v/e9bcb4/32.png) [@Varun](https://discuss.openedx.org/u/Varun)\
**Post date:** [October 2, 2026, 9:07am UTC](https://discuss.openedx.org/t/rfc-platform-level-utm-attribution-support-for-authenticated-mfe-users/19182/5 "2026-10-02T09:07:22Z")

</div>

@arbrandes thank you, this is exactly the clarification we needed. A few things we missed about what frontend-base already provides.

We will drop the custom lifecycle plugin manager entirely and rework UTM attribution as a standard App — using externalScripts for the boot-time URL capture and cookie write, and a new middleware field on App (following the same per-app reasoning as [ADR 0014](https://github.com/openedx/frontend-base/blob/main/docs/decisions/0014-per-app-external-scripts.rst)) for the axios header interceptor.

The onLogout hook and the “no existing LMS code is modified” claim in the RFC were both incorrect — we will drop the former and correct the latter.

We will submit this as a draft PR against frontend-base with an ADR, referencing [ADR 0006](https://github.com/openedx/frontend-base/blob/main/docs/decisions/0006-middleware-support-for-http-clients.rst) and [ADR 0014](https://github.com/openedx/frontend-base/blob/main/docs/decisions/0014-per-app-external-scripts.rst) as precedent.
