# Per-Tenant Theming with eox-tenant

**URL:** <https://discuss.openedx.org/t/per-tenant-theming-with-eox-tenant/19127>\
**Category:** Site Operations Help\
**Tags:** how-to, tutor\
**Created:** [June 26, 2026, 4:21pm UTC](https://discuss.openedx.org/t/per-tenant-theming-with-eox-tenant/19127 "2026-06-26T16:21:04Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![Ronak\_Pansuriya1](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/ronak_pansuriya1/32/6909_2.png) [@Ronak\_Pansuriya1](https://discuss.openedx.org/u/Ronak_Pansuriya1)\
**Post date:** [June 26, 2026, 4:21pm UTC](https://discuss.openedx.org/t/per-tenant-theming-with-eox-tenant/19127/1 "2026-06-26T16:21:04Z")

</div>

Hi everyone,

I have a working multi-tenant Open edX setup using **[eox-tenant](https://github.com/eduNEXT/eox-tenant/)** and **[tutor-contrib-mfe-extensions](https://github.com/eduNEXT/tutor-contrib-mfe-extensions)**. Now I want to apply different branding (colors, logos, header/footer) for each tenant.

My questions:

- Is it possible to use a different **Indigo theme per tenant** , or what’s the recommended approach for per-tenant theming on the LMS side?
- For the **MFEs** , since all tenants share the same build, how can we apply different branding per tenant at runtime? Is `PARAGON_THEME_URLS` the right way to go?

Would love to hear how others are handling this. Thanks!

---

<div class="post-metadata">

**Author:** ![Le00m\_1](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/le00m_1/32/9655_2.png) [@Le00m\_1](https://discuss.openedx.org/u/Le00m_1)\
**Post date:** [July 2, 2026, 10:14pm UTC](https://discuss.openedx.org/t/per-tenant-theming-with-eox-tenant/19127/2 "2026-07-02T22:14:53Z")

</div>

Hi Friend!

Good questions — this comes up a lot as more people move multi-tenant deployments onto the MFE architecture. Here’s how I’d break it down, LMS side and MFE side separately, since they use completely different mechanisms.

## LMS side (Indigo per tenant)

Yes, this is doable, and you don’t need a separate deploy per tenant. The combo that works well is `eox-tenant` + `eox-theming`:

1. Make sure `USE_EOX_TENANT` is enabled and your themes are already available under `COMPREHENSIVE_THEME_DIRS` (each theme folder can be a fork/variant of Indigo, or a lightweight theme that just overrides a few SCSS variables and images on top of Indigo).
2. With `eox-theming` installed, you add a `THEME_OPTIONS` block to each tenant’s config (via the eox-tenant admin or API):

json

```auto
{
  "THEME_OPTIONS": {
    "theme": {
      "name": "tenant-a-theme",
      "parent": "indigo",
      "grandparent": "base-theme"
    }
  }
}

```

`eox-theming` does a hierarchical template lookup (name → parent → grandparent), so you only need to override what’s actually different per tenant — logo, colors, maybe a couple of templates — and let everything else fall back to Indigo. Switching a tenant’s theme is just a config change, no rebuild/redeploy needed on the LMS side.

One gotcha to watch for: on newer Open edX releases (Teak+), `_make_mako_template_dirs` needs a `settings` argument now, so if you’re on an older `eox-theming` snippet from the docs, double check that against your release.

## MFE side (shared build, different branding per tenant)

This is the part where people usually get stuck, and yes — `PARAGON_THEME_URLS` is the right mechanism, but it’s important to understand _why_ it works and where its limits are.

**Why you can’t just install two brand packages:** brand packages (`@edx/brand-*`) and Indigo’s MFE brand are npm packages baked into the build at compile time. You physically can’t have two of them active in the same build — this has been confirmed multiple times in the forum (see the “[Apply multiple different brands at the same openedx page](https://discuss.openedx.org/t/apply-multiple-different-brands-at-the-same-openedx-page/18412)” thread). So the “one MFE build, N tenants” requirement rules out the classic npm-alias branding approach entirely.

**What actually works: CSS variables + runtime URLs.** Since Paragon v23 (Teak release), Paragon and brand packages moved from SCSS variables (resolved at build time) to CSS custom properties (resolved at _runtime_). That means you can build one base Paragon theme, and then swap out the stylesheet that defines the variables without touching the MFE build at all. This is exactly what `PARAGON_THEME_URLS` is for:

json

```auto
{
  "PARAGON_THEME_URLS": {
    "core": {
      "urls": {
        "default": "https://cdn.jsdelivr.net/npm/@openedx/paragon@$paragonVersion/dist/core.min.css"
      }
    },
    "defaults": { "light": "light" },
    "variants": {
      "light": {
        "urls": {
          "default": "https://cdn.jsdelivr.net/npm/@openedx/paragon@$paragonVersion/dist/light.min.css",
          "brandOverride": "https://your-cdn.example.com/tenant-a/light.min.css"
        }
      }
    }
  }
}

```

The `core`/`variant` split keeps the base Paragon CSS shared (better caching) while `brandOverride` layers just your tenant’s tokens on top. You compile each tenant’s token set with `paragon build-scss`, host the resulting `dist` files wherever (S3, CDN, even served statically by `tutor-mfe` itself), and just point `brandOverride` to the right file.

**Making it per-tenant at runtime** is the piece `tutor-contrib-mfe-extensions` and `mfe_config_api` solve together:

- `mfe_config_api` lets each Open edX site/tenant serve its _own_ `MFE_CONFIG` (logos, `PARAGON_THEME_URLS`, feature flags, etc.) based on the requesting domain — this is the actual per-tenant switch.
- `tutor-contrib-mfe-extensions` is useful here mainly for routing: it lets you serve MFEs by path under each tenant’s own LMS domain (`https://tenant-a.example.com/learning`) instead of a single shared `apps.` subdomain, which is what makes “MFE reads config for _its own_ domain” work cleanly in a multi-tenant setup.

So the practical flow is: request hits `tenant-a.example.com` → Caddy/your routing serves the shared MFE build → the MFE calls `mfe_config_api` on `tenant-a.example.com` → gets back tenant A’s `PARAGON_THEME_URLS.variants.light.urls.brandOverride` and logo URLs → applies them at runtime. No rebuild per tenant.

## Things that will NOT work at runtime

Worth being upfront about scope, since I’ve seen people burn time here:

- **Structural header/footer differences** (different nav items, different layout, not just colors/logo) aren’t achievable this way — `frontend-component-header`/`footer` are still npm-aliased at build time. If tenants need genuinely different header/footer _structure_ (not just branding), you’re back to separate MFE builds per tenant, or a header/footer component that reads tenant config and conditionally renders — more engineering effort.
- Logos and simple text (site name, some copy) usually go through `MFE_CONFIG` directly (e.g. `LOGO_URL`) rather than through Paragon tokens — check whichever plugin you’re using (`tutor-contrib-branding` from Aulasneo is a good reference implementation if you want something more turnkey than hand-rolling it).
- Comprehensive theming (SCSS-based, build-time) is being phased out in favor of design tokens going forward, so it’s worth building your tenant theming pipeline around `PARAGON_THEME_URLS`/tokens now rather than investing further in the old SCSS override approach.

We’ve been running something close to this exact setup in production for a while now, and the above is what we’ve observed works reliably. Happy to go deeper on any part of it if useful.

---

<div class="post-metadata">

**Author:** ![Ronak\_Pansuriya1](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/ronak_pansuriya1/32/6909_2.png) [@Ronak\_Pansuriya1](https://discuss.openedx.org/u/Ronak_Pansuriya1)\
**Post date:** [July 3, 2026, 7:42am UTC](https://discuss.openedx.org/t/per-tenant-theming-with-eox-tenant/19127/3 "2026-07-03T07:42:12Z")

</div>

Hi @Le00m_1

Thank you so much for the detailed explanation! Really appreciate it.

We’ll try the different theming approaches and get back to you if we have questions.

Thanks again!

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/e/e0015f89e879bc75925b71fd11133ab15fe98297.png) [@system](https://discuss.openedx.org/u/system)\
**Post date:** [October 1, 2026, 7:43am UTC](https://discuss.openedx.org/t/per-tenant-theming-with-eox-tenant/19127/4 "2026-10-01T07:43:12Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
