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

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.

You just need to copy your static files to sys.prefix/share/jupyterhub/static folder. Here is an example of our custom spawner 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!!

1 Like

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.


ARG JUPYTERHUB_K8S_VERSION=1.1.3-n721.h0acbcf43

USER root

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

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


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.

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

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

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:

  extraConfig: |
      from tornado import web
      from jupyterhub.handlers.base import BaseHandler

      # add route for terms & conditions
      class FaqHandler(BaseHandler):
          async def get(self):
              user = self.current_user
              auth_state = await user.get_auth_state()
              html = await self.render_template(

      c.JupyterHub.extra_handlers = [