# Custom CSS/Images/Scripts and how to add to Jupyterhub

**URL:** <https://discourse.jupyter.org/t/custom-css-images-scripts-and-how-to-add-to-jupyterhub/20087>\
**Category:** JupyterHub\
**Tags:** jupyterhub, help-wanted\
**Created:** [June 23, 2023, 10:47am UTC](https://discourse.jupyter.org/t/custom-css-images-scripts-and-how-to-add-to-jupyterhub/20087 "2023-06-23T10:47:26Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![LeafLikeApple](https://avatars.discourse-cdn.com/v4/letter/l/c77e96/32.png) [@LeafLikeApple](https://discourse.jupyter.org/u/LeafLikeApple)\
**Post date:** [June 23, 2023, 10:47am UTC](https://discourse.jupyter.org/t/custom-css-images-scripts-and-how-to-add-to-jupyterhub/20087/1 "2023-06-23T10:47:26Z")

</div>

Hello together,  
I hope you can help me.  
I have reworked some html templates from Jupyterhub. Via ‘JupyterHub.template\_paths’ I was able to include them successfully. But I use custom css files, javascript files and images.  
In the tutorial “Working with templates and UI” also nowhere else, I found a solution how the reworked templates can access the custom files.  
And secondly I have new routes (new html files), how can they be included?  
Thanks in advance for your help.

---

<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:** [June 23, 2023, 12:51pm UTC](https://discourse.jupyter.org/t/custom-css-images-scripts-and-how-to-add-to-jupyterhub/20087/2 "2023-06-23T12:51:20Z")

</div>

You just need to copy your static files to `sys.prefix/share/jupyterhub/static` folder. Here is an example of our [custom spawner](https://gitlab.com/idris-cnrs/jupyter/jupyter-jeanzay-spawner/-/tree/migrate_to_pyproject/) that installs necessary css, js and images.

For new HTML files, you can include them in your custom templates folder and render them using Jinja? Maybe there is a more efficient solution for this!!

---

<div class="post-metadata">

**Author:** ![LeafLikeApple](https://avatars.discourse-cdn.com/v4/letter/l/c77e96/32.png) [@LeafLikeApple](https://discourse.jupyter.org/u/LeafLikeApple)\
**Post date:** [June 26, 2023, 12:31pm UTC](https://discourse.jupyter.org/t/custom-css-images-scripts-and-how-to-add-to-jupyterhub/20087/3 "2023-06-26T12:31:33Z")

</div>

Thanks for the quick reply.  
I copied the files to “/usr/local/share/jupyterhub/static”, only this does not seem to work for me.  
When I go to the Jupyterhub page, it still can’t find the CSS and co.  
I also didn’t use a custom spawner, but use the kubespawner without changing it further.  
Currently I use a dockerfile to build the hub to copy the templates and static folder there.  
I also checked if the files are in the right places.

**Dockerfile**

```auto
ARG JUPYTERHUB_K8S_VERSION=1.1.3-n721.h0acbcf43
ARG BASE_IMAGE=jupyterhub/k8s-hub:${JUPYTERHUB_K8S_VERSION}
FROM ${BASE_IMAGE}

USER root

RUN mkdir -p /templates
COPY templates/* /templates/

COPY static/* /usr/local/share/jupyterhub/static/

USER ${NB_USER}

```

---

<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:** [June 26, 2023, 1:15pm UTC](https://discourse.jupyter.org/t/custom-css-images-scripts-and-how-to-add-to-jupyterhub/20087/4 "2023-06-26T13:15:09Z")

</div>

Are you sure that your custom templates are generating correct URLs for the static content? Maybe you can check browser console to see if you are getting any 404s while trying to load static content.

If I am not wrong, your rendered HTML pages should look at `/hub/static/{css,js,images}` for static content, assuming that you are not running JupyterHub at any base prefix.

---

<div class="post-metadata">

**Author:** ![LeafLikeApple](https://avatars.discourse-cdn.com/v4/letter/l/c77e96/32.png) [@LeafLikeApple](https://discourse.jupyter.org/u/LeafLikeApple)\
**Post date:** [June 26, 2023, 2:33pm UTC](https://discourse.jupyter.org/t/custom-css-images-scripts-and-how-to-add-to-jupyterhub/20087/5 "2023-06-26T14:33:19Z")

</div>

Yes, thank you.  
The files were there, but I hadn’t noticed that they weren’t in the sub directory “images/css…”. After I adjusted the copy, it worked correctly. (I only had to remove the \*).

Now I’m still missing that the new HTML pages should be displayed, I do not really understand how I can implement this. for example /hub/faq

---

<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:** [June 26, 2023, 6:47pm UTC](https://discourse.jupyter.org/t/custom-css-images-scripts-and-how-to-add-to-jupyterhub/20087/6 "2023-06-26T18:47:39Z")

</div>

There is `extra_handlers` config option where you can register new endpoints. But I think it is deprecated in favour of services.

You can create services that will be available at `/services/faq`. You can check examples on how to implement them [here](https://github.com/jupyterhub/jupyterhub/tree/main/examples)

---

<div class="post-metadata">

**Author:** ![LeafLikeApple](https://avatars.discourse-cdn.com/v4/letter/l/c77e96/32.png) [@LeafLikeApple](https://discourse.jupyter.org/u/LeafLikeApple)\
**Post date:** [June 30, 2023, 9:36am UTC](https://discourse.jupyter.org/t/custom-css-images-scripts-and-how-to-add-to-jupyterhub/20087/7 "2023-06-30T09:36:24Z")

</div>

Thanks again, with the extra\_handlers I got it working. I find that with the services it would only be more complicated and clutter the config file even more.  
It would be best if the Jupyterhub team would come up with something similar to the templates.

If anyone has the same problem in the future, this is what an extra\_handler for a new route could look like:

```auto
hub:
  extraConfig:
    new-routes.py: |
      from tornado import web
      from jupyterhub.handlers.base import BaseHandler

      # add route for terms & conditions
      class FaqHandler(BaseHandler):
          @web.authenticated
          async def get(self):
              user = self.current_user
        
              auth_state = await user.get_auth_state()
              html = await self.render_template(
                  'faq.html',
                  auth_state=auth_state,
                  user=user,
              )
              self.finish(html)

      c.JupyterHub.extra_handlers = [
          (
              r'/faq',
              FaqHandler,
          ),
      ]

```
