Skip to content

DRAFT: empty template for embedding custom HTML and inline javascript - #1763

Draft
kevinpapst wants to merge 1 commit into
getfider:mainfrom
kevinpapst:custom-html
Draft

kevinpapst wants to merge 1 commit into
getfider:mainfrom
kevinpapst:custom-html

Conversation

@kevinpapst

@kevinpapst kevinpapst commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ THIS PR IS MEANT AS DISCUSSION STARTER ⚠️

Many support requests ask for either Inline Javascript or Custom HTML, most talk about custom analytics tags.

I created the simplest solution I could come up with and which works for my use-case (including Umami tracking script). I know GoogleTag-Manager is supported, but GT is a privacy nightmare.

Lets start with the cons: my solution has the clear drawback that it is tailored to Docker users and does not support multi-tenants. But I wanted to have a minimalistic working solution to start the discussion.

You @mattwoberts mentioned and I agree:

We'd need to be careful about malicious use of something like this though

At first I thought about adding a textfield (like the custom css) to the admin section, but:

  • the CSP headers are rendered before the Tenant is known, so hard to patch
  • it allows to inject code at runtime, so a bug that exposes this setting could lead to XSS

I decided against it for security and complexity reasons and because I believe the PR already solves > 90% of all requests (even stuff like this could be handled).

That's why I chose to follow the existing approach where the system admin mounts a template (like the terms and privacy pages) via docker:

services:
  fider:
    environment:
      CSP_ALLOWED_HOSTS: "https://stats.example.com"
    volumes:
      - ./custom.html:/app/views/custom.html:ro

The custom.html then can contain any HTML code, but I believe it will mostly carry inline javascript or script tags like this:

<script nonce="{{ .public.contextID }}" src="https://stats.example.com/t.js"></script>

The {{ .public.contextID }} is announced in the CSP header, so it needs to be added to the script tag. If a script is loaded from an external source, the new ENV variable CSP_ALLOWED_HOSTS also needs to be configured and point to the full URL of the script host (which will be injected in the CSP header).

As this is only done by the server administrator it is a "safe" approach.

Obviously I would add documentation for this feature, if it is accepted in one way or another.


Checklist

@kevinpapst kevinpapst changed the title empty template for embedding custom HTML and inline javascript DRAFT: empty template for embedding custom HTML and inline javascript Nov 5, 2026
@kevinpapst
kevinpapst marked this pull request as draft November 5, 2026 16:18
@mattwoberts

mattwoberts commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

@kevinpapst I honestly don't think I would have come up with the docker solution, that's pretty neat.

The thing is, I'd love to offer this for the hosted version. But that means "Custom script" admin area. And obviously that's very risky indeed. I think I'd have to put constraints on it like own-domains only, and possibly making it paid-for only (and self-host ofc).

Another option would be we go ahead with your solution, and introduce something too that's a more user-friendly hosted-compatible way to add support for various analytics services... Plausible etc. That's possible and less open to abuse, but less flexible and also means we need to keep on top of that list of supported analytics and the script to use etc.

@kevinpapst

kevinpapst commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Yeah, I fully understand.

I also fiddled around a bit with a text box, but deleted my draft as there are two issues that needs to be discussed first:

CSP header

Your very strict CSP header needs to know every domain that is contacted. Which is positive, don't get me wrong.
But the problem is the moment when the header it is created: before the tenant data is available.
The current GoogleTagManager code is a good example of that problem (if I am not wrong). It needs to be set as ENV so we can squeeze it into the header very early on.

So one of the issues to solve is the question: when do we want the header to be created? Headers can be set multiple times and the last one wins, so we could overwrite it if necessary later on, but that's a smell.

I'd recommend to move the header creation to the moment when we can access the tenant data.

What do you want to support

Having the option for custom javascript is honestly an amazing feature. Yes, it can break things, but obviously that is an Admin only feature and "with great power comes great responsibility". It would be the most powerful approach, but that question is strategic and only you can decide how far you want to open up Fider.
I do offer custom CSS and custom Javascript in Kimai SaaS and it allows for so many small customisations, you'd be surprised what people build if they get this option. It can solve so many small requests, which you don't want to build into the core product.

The next one is definitely more convenient for the end-user, is compatible with the hosted version and could easily be moved into the premium feature section: adding support for multiple analytics solutions in the Admin area.

All self-hosted analytic solutions I have seen need an identifier and the script URL, e.g. with a self-hosted Umami (great tool if you don't know it) the script looks like this:

<script defer src="https://t.example.com/script.js" data-website-id="1234-5678-9012-1234-786587658765"></script>

Matomo also needs URL and ID. Hosted solutions usually need an ID and optionally a URL So we have one or two separate textfields (ID and sometimes URL) and can parse the host from the URL and add it to the CSP very easy.

I would offer to move the current Google solution and add Umami and Matomo as a starter to the admin area.


Just to make sure we are on the same page: the second solution has a better UX, but it is not really "safer" than the first approach. In the example above nothing stops me from hosting my own code at https://t.example.com/script.js instead of the Umami script 👿

Let me know what you prefer and I will build a new prototype. I just would prefer to add my analytics script "upgrade compatible" instead of mounting a patched /base.html:/app/views/base.html:ro version into my docker image 🙈


Edit: if you feel uncomfortable with any of the solutions close the issue. I think its worth to explore the feature, but I don't mind if you reject it for now!

@mattwoberts

Copy link
Copy Markdown
Contributor

I'm not sure I've got the head space to dig into a fully admin-controlled javascript area right now - it's tempting, but at the same time hard to get a feel for if people are chomping at the bit for it - there are the few things you've found in the feedback site, but I wonder how many of them are hosted.

Given it might take a while for me / us to get round to something "proper", I do wonder if the best way for now is to support your docker solution, if this is an issue for you (and indeed others) - it feels to me like the people who want the analytics the most are probably the people who are self hosting, so this solves it for now...

Here's my not-at-all-generated-by-claude-honest review notes from what you put together in your draft PR, assuming we go down this route:

Thanks Kevin, I like this. A mounted template matches how we already handle the terms and privacy pages. It's for server admins only, and it gives people a supported hook instead of having to fork [base.html](https://github.com/getfider/fider/pull/base.html). I'd like to take it forward with a few changes:

  1. Validate CSP_ALLOWED_HOSTS. The value goes straight into the Content-Security-Policy header. A stray ; or , (a typo, or a copy-paste from a docs page) would inject or break directives without any warning. Could env.Reload() reject values containing ; or ,? A clear error at startup is fine.

  2. A broken [custom.html](https://github.com/getfider/fider/pull/custom.html) takes the whole site down. If the mounted file has a template syntax error, tpl.GetTemplate panics on every page render. The failed parse is never cached, so every request returns a 500. I'd rather it failed fast. Could we parse [base.html](https://github.com/getfider/fider/pull/base.html) together with [custom.html](https://github.com/getfider/fider/pull/custom.html) once at startup, so a bad file stops the server from booting instead of running broken? If that turns out to be awkward, a loud warning in the docs would be the minimum.

  3. Explain script-src vs connect-src in the docs. A nonce already allows an external <script src>, so for a simple tag like Umami's, connect-src is what actually needs the extra host (for the tracking beacons). Keeping the host in script-src too is right, because loaders that inject child scripts without the nonce need it. The docs should say why both are there, so people don't think the nonce is optional.

  4. Docs PR in fider-docs. Please cover:

    • the mount path ([/app/views/custom.html](https://github.com/app/views/custom.html))
    • the nonce="{{ .public.contextID }}" requirement for inline and external scripts
    • CSP_ALLOWED_HOSTS and its format (space-separated, full origin incl. https://)
    • that in multi-tenant mode the file is included for every tenant
    • that it's added at the end of <body> (head-only things like site-verification meta tags aren't covered)
  5. Linking. I'll mark https://feedback.fider.io/posts/622/script-injection-in-headers as accepted. Could you link that from the PR description?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants