# Custom JupyterHub Services with JupyterHub's "Look and Feel"

**URL:** <https://discourse.jupyter.org/t/custom-jupyterhub-services-with-jupyterhubs-look-and-feel/36963>\
**Category:** JupyterHub\
**Created:** [July 22, 2025, 7:18am UTC](https://discourse.jupyter.org/t/custom-jupyterhub-services-with-jupyterhubs-look-and-feel/36963 "2025-07-22T07:18:33Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![Paul2708](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/paul2708/32/14207_2.png) [@Paul2708](https://discourse.jupyter.org/u/Paul2708)\
**Post date:** [July 22, 2025, 7:18am UTC](https://discourse.jupyter.org/t/custom-jupyterhub-services-with-jupyterhubs-look-and-feel/36963/1 "2025-07-22T07:18:33Z")

</div>

Hey there,

I am curious about how to deal with custom JupyterHub services (ranging from static HTML pages to more complex UIs) that should obtain the same look and feel of the JupyterHub.  
By “look and feel”, I’m referring to JupyterHub’s template customizations.  
So if I create a new JupyterHub service, I want the original modifications (e.g., modified navigation bar, new footer, …) to be applied to the templates of the JupyterHub service as well. Since the JupyterHub service is spawned as a separate process, it cannot access JupyterHub’s template rendering, or am I missing something?

Currently, if we want to add a new navbar item to the JupyterHub, we have to edit the JupyterHub template, but also every JupyterHub service template to create the impression that the service is part of the JupyterHub.

As a workaround, an extra handler can be registered to the JupyterHub. However, it is deprecated.  
So, what is the proposed method for creating services with a consistent look and feel?

Best regards,  
Paul

---

<div class="post-metadata">

**Author:** ![mahendrapaipuri](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/mahendrapaipuri/32/6800_2.png) [@mahendrapaipuri](https://discourse.jupyter.org/u/mahendrapaipuri)\
**Post date:** [July 22, 2025, 7:36am UTC](https://discourse.jupyter.org/t/custom-jupyterhub-services-with-jupyterhubs-look-and-feel/36963/2 "2025-07-22T07:36:20Z")

</div>

You can always extend the `page.html` of JupyterHub for your services. Here is an [example](https://gitlab.com/idris-cnrs/jupyter/jupyter-services/jupyterhub-server-manager/-/blob/main/templates/manager.html?ref_type=heads).

---

<div class="post-metadata">

**Author:** ![Paul2708](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/paul2708/32/14207_2.png) [@Paul2708](https://discourse.jupyter.org/u/Paul2708)\
**Post date:** [July 22, 2025, 7:57am UTC](https://discourse.jupyter.org/t/custom-jupyterhub-services-with-jupyterhubs-look-and-feel/36963/3 "2025-07-22T07:57:36Z")

</div>

Sure! That’s currently how we do it. But we face two main issues here:

- **Duplicated code** : Besides the JupyterHub page modification, every other service has to be modified as well. Thus, even small changes (e.g., fixing a type in the footer) result in a change affecting all services.
- **Missing information** : In JupyterHub’s page template, we display information stored in the auth state. It can be accessed directly using `user.get_auth_state()["oauth_user"]["xyz"]`. However, the auth state is not present in the services, so we must send a separate API request to retrieve the auth state and display the same information. Therefore, we cannot reuse the same template 1:1 but have to apply additional modifications. This doesn’t feel right to me.

---

<div class="post-metadata">

**Author:** ![mahendrapaipuri](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/mahendrapaipuri/32/6800_2.png) [@mahendrapaipuri](https://discourse.jupyter.org/u/mahendrapaipuri)\
**Post date:** [July 22, 2025, 8:23am UTC](https://discourse.jupyter.org/t/custom-jupyterhub-services-with-jupyterhubs-look-and-feel/36963/4 "2025-07-22T08:23:41Z")

</div>

> [@Paul2708](#):
>
> So if I create a new JupyterHub service, I want the original modifications (e.g., modified navigation bar, new footer, …) to be applied to the templates of the JupyterHub service as well.

Well, I imagine you have a custom “page.html” (with your modifications), say “mypage.html”, for JupyterHub. In your service you will have to extend that `mypage.html` instead of `page.html` so all your modifications on JupyterHub will be available for your service as well.

> [@Paul2708](#):
>
> Since the JupyterHub service is spawned as a separate process, it cannot access JupyterHub’s template rendering, or am I missing something?

It is possible to access JupyterHub’s data files. Look at this [snippet](https://gitlab.com/idris-cnrs/jupyter/jupyter-services/jupyterhub-server-manager/-/blob/main/jupyterhub_server_manager/manager.py?ref_type=heads#L86-110)

> [@Paul2708](#):
>
> However, the auth state is not present in the services, so we must send a separate API request to retrieve the auth state and display the same information. Therefore, we cannot reuse the same template 1:1 but have to apply additional modifications.

You can define a variable in your custom template to set it to true when the template is being used by a service. Using that variable you can get `auth_state` either accessing user object or API request. Would it work for your case?

---

<div class="post-metadata">

**Author:** ![Paul2708](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/paul2708/32/14207_2.png) [@Paul2708](https://discourse.jupyter.org/u/Paul2708)\
**Post date:** [July 22, 2025, 11:09am UTC](https://discourse.jupyter.org/t/custom-jupyterhub-services-with-jupyterhubs-look-and-feel/36963/5 "2025-07-22T11:09:41Z")

</div>

> It is possible to access JupyterHub’s data files. Look at this [snippet](https://gitlab.com/idris-cnrs/jupyter/jupyter-services/jupyterhub-server-manager/-/blob/main/jupyterhub_server_manager/manager.py?ref_type=heads#L86-110)

I’ll give it a try, thank you!

> You can define a variable in your custom template to set it to true when the template is being used by a service. Using that variable you can get `auth_state` either accessing user object or API request. Would it work for your case?

If that’s the proposed way, it would work out, for sure.
