Updated 2026-09-25
Other frameworks
Add the widget script on Vite, Astro, Flask, Django, Laravel, or Rails, then confirm Widget install is complete.
Prerequisites
- A Modifee project linked to the repository.
- Site URL saved under Settings → Connection.
- Authentication under Settings → Client access. Site without login is the default (wizard: Public site, no login). App with login (wizard: Site with login) needs a server that can sign a token.
HTML, WordPress, Webflow, and Shopify use the one-line script in HTML and static sites.
Steps
Use the wizard step Install the widget, or Settings → Widget install.
Site without login
- Open Install manually → HTML and paste this in the shared layout, just before
</body>.
<script src="https://www.modifee.com/widget.js" data-project="YOUR_PROJECT_KEY" async></script>
YOUR_PROJECT_KEY is the data-project value on that tab. It is also shown under Settings → Danger zone. Copy src from the tab if your dashboard does not use https://www.modifee.com/widget.js.
This mode does not use MODIFEE_SECRET.
- Or press Install automatically. The pull request is titled "Install Modifee widget" on branch
modifee/install-widget. The file depends on the repo:
| Repo | File edited | Tag |
| --- | --- | --- |
| Vite, with index.html and package.json beside vite.config, and react in dependencies | index.html | HTML script above |
| Astro, with src/layouts/Layout.astro, or exactly one file in src/layouts, or only src/pages/index.astro | that file | is:inline script below |
| Flask | templates/base.html, templates/layout.html, or app/templates/base.html | HTML script above |
| Django (manage.py and templates/base.html) | templates/base.html | HTML script above |
| Rails | app/views/layouts/application.html.erb | HTML script above |
| Laravel | resources/views/layouts/app.blade.php or resources/views/app.blade.php | HTML script above |
Astro tag, placed just before </body>:
<script is:inline src="https://www.modifee.com/widget.js" data-project="YOUR_PROJECT_KEY" async></script>
YOUR_PROJECT_KEY is the same key as in the HTML tag. Place the tag just before </body>.
Vite without React in package.json does not get a pull request: "This is a Vite app, but React is not a dependency next to vite.config. Paste the snippet above. No pull request was opened."
App with login
- Set Authentication to App with login and save.
- Copy Settings → Client access → Signing secret. The code block is labeled MODIFEE_SECRET. On a host that is not Vercel, the page says "Add this value as the MODIFEE_SECRET environment variable on your host. The agency app signs the logged-in user's email with HMAC-SHA256." On Vercel, connect the project and wait for "MODIFEE_SECRET set on Vercel ✓", then redeploy. See Signing secret and Secret and redeploy.
- Open Install manually. The tabs are Next.js (App Router), Next.js (Pages Router), Flask, Django, Laravel, and Rails. Flask is below. Next.js is in Next.js App Router and Next.js Pages Router.
import hmac, hashlib, base64, json, time, os
def modifee_token(email, name=None):
raw = json.dumps(
{"email": email, "name": name, "exp": int(time.time()) + 86400},
separators=(",", ":"),
).encode()
payload = base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
sig = base64.urlsafe_b64encode(
hmac.new(os.environ["MODIFEE_SECRET"].encode(), payload.encode(), hashlib.sha256).digest()
).rstrip(b"=").decode()
return f"{payload}.{sig}"
@app.context_processor
def inject_modifee_token():
if not current_user.is_authenticated:
return {"modifee_token": None}
return {
"modifee_token": modifee_token(current_user.email, getattr(current_user, "name", None)),
}
{# templates/base.html #}
{% if current_user.is_authenticated %}
<script src="https://www.modifee.com/widget.js" data-project="YOUR_PROJECT_KEY" data-token="{{ modifee_token }}" async></script>
{% endif %}
YOUR_PROJECT_KEY in that block is the key from the tab. The hint is "Pass the token from a context processor. The tag renders only when current_user is authenticated." Then "Store the secret as MODIFEE_SECRET in your server environment." and "Sign the email on the server. The browser only receives the signature."
- Install automatically for Flask templates
templates/base.html,templates/layout.html, orapp/templates/base.htmlwritesmodifee_identity.pyand this block before</body>:
"""Server-only. Sign the logged-in user's email. Never send MODIFEE_SECRET to the browser.
Pass the result into the template as modifee_token, for example from the signed-in user:
token = modifee_token(current_user.email, getattr(current_user, "name", None))
"""
import base64
import hashlib
import hmac
import json
import os
import time
def modifee_token(email, name=None):
raw = json.dumps(
{"email": email, "name": name, "exp": int(time.time()) + 86400},
separators=(",", ":"),
).encode()
payload = base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
signature = base64.urlsafe_b64encode(
hmac.new(os.environ["MODIFEE_SECRET"].encode(), payload.encode(), hashlib.sha256).digest()
).rstrip(b"=").decode()
return f"{payload}.{signature}"
{% if modifee_token %}
<script src="https://www.modifee.com/widget.js" data-project="YOUR_PROJECT_KEY" data-token="{{ modifee_token }}" async></script>
{% endif %}
The pull request says "Set modifee_token from the signed-in user before rendering the template. Store the secret as MODIFEE_SECRET." If modifee_identity.py already exists and differs, install stops with the file path, then already exists and was left unchanged. Paste the snippet above. No pull request was opened.
Vite, Astro, and other static files get "This site has no server file where a token can be signed. Switch to Site without login to install with one line, or paste the signed-token snippet on your server. No pull request was opened."
Django, Laravel, and Rails use the same token shape as Next.js and Flask: base64url JSON { email, name, exp }, then base64url HMAC-SHA256 of that payload with MODIFEE_SECRET, joined by .. exp is the current unix time plus 86400 seconds. The widget attribute is data-token. Select the matching tab:
import hmac, hashlib, base64, json, time, os
def modifee_token(email, name=None):
raw = json.dumps(
{"email": email, "name": name, "exp": int(time.time()) + 86400},
separators=(",", ":"),
).encode()
payload = base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
sig = base64.urlsafe_b64encode(
hmac.new(os.environ["MODIFEE_SECRET"].encode(), payload.encode(), hashlib.sha256).digest()
).rstrip(b"=").decode()
return f"{payload}.{sig}"
def modifee_token_context(request):
user = getattr(request, "user", None)
if user is None or not user.is_authenticated:
return {"modifee_token": None}
name = user.get_full_name() or None
return {"modifee_token": modifee_token(user.email, name)}
# settings.py — TEMPLATES[0]["OPTIONS"]["context_processors"]
# "myapp.context_processors.modifee_token_context"
{# templates/base.html #}
{% if user.is_authenticated %}
<script src="https://www.modifee.com/widget.js" data-project="YOUR_PROJECT_KEY" data-token="{{ modifee_token }}" async></script>
{% endif %}
function modifee_token(string $email, ?string $name = null): string
{
$json = json_encode([
'email' => $email,
'name' => $name,
'exp' => time() + 86400,
], JSON_UNESCAPED_SLASHES);
$payload = rtrim(strtr(base64_encode($json), '+/', '-_'), '=');
$sig = rtrim(strtr(base64_encode(hash_hmac('sha256', $payload, env('MODIFEE_SECRET'), true)), '+/', '-_'), '=');
return $payload.'.'.$sig;
}
@auth
<script src="https://www.modifee.com/widget.js" data-project="YOUR_PROJECT_KEY" data-token="{{ modifee_token(auth()->user()->email, auth()->user()->name) }}" async></script>
@endauth
# app/helpers/modifee_helper.rb
require "base64"
require "json"
require "openssl"
def modifee_token(email, name = nil)
payload = Base64.urlsafe_encode64(
{ email: email, name: name, exp: Time.now.to_i + 86_400 }.to_json,
padding: false
)
signature = Base64.urlsafe_encode64(
OpenSSL::HMAC.digest("SHA256", ENV.fetch("MODIFEE_SECRET"), payload),
padding: false
)
"#{payload}.#{signature}"
end
<%# app/views/layouts/application.html.erb %>
<% if user_signed_in? %>
<script src="https://www.modifee.com/widget.js" data-project="YOUR_PROJECT_KEY" data-token="<%= modifee_token(current_user.email, current_user.name) %>" async></script>
<% end %>
Render that HTML on each request for the signed-in user. Flask, Django, Laravel, and Rails must not cache the full page for a logged-in user. A cached page can send one user's token to someone else.
A stack the tabs do not list still signs the same way. Replace TOKEN with the signature. Do not put MODIFEE_SECRET in the browser.
// Your server must generate the token; never put the secret in the browser.
// payload = base64url(JSON { email, name, exp })
// token = payload + "." + base64url(HMAC_SHA256(MODIFEE_SECRET, payload))
<script src="https://www.modifee.com/widget.js" data-project="YOUR_PROJECT_KEY" data-token="TOKEN" async></script>
Verify
- Deploy the page that contains
widget.jsand this project's key. - Open Settings → Widget install.
- Press Check installation.
- "Found on the site." and "Widget found on the site." The mark beside Widget install turns green.
For App with login, the tag is in the HTML only for a signed-in response. The checker fetches the site URL without logging in. If the tag is inside an auth block, the check can report the widget missing while a signed-in browser still loads it. Open the site while logged in so the widget can ping. A failed check still leaves the mark yellow. The tooltip is "Widget not found on the site".
Troubleshooting
- "Add a site URL before checking."
We couldn't find the widget onthe saved site URL.Paste the snippet just before </body> and check again.The widget on this page belongs to another project.We couldn't reachthe saved site URL.Connection failed. The widget will be marked installed as soon as it loads on the site.This page is rendered in the browser, so the widget script is not in the HTML we fetched fromthe saved site URL.Open the site after you paste the snippet. We'll mark this done as soon as the widget loads.- "This repo has more than one Vite app. Paste the snippet above. No pull request was opened."
- "Vite is configured, but index.html and package.json are not both next to vite.config. Paste the snippet above. No pull request was opened."
- "This repo has more than one Astro site. Paste the snippet above. No pull request was opened."
- "This Astro site has several layouts and no src/layouts/Layout.astro. Paste the snippet above. No pull request was opened."
- "This Astro site has no layout we can edit safely. Paste the snippet above. No pull request was opened."
- "This site has no server file where a token can be signed. Switch to Site without login to install with one line, or paste the signed-token snippet on your server. No pull request was opened."
- "MODIFEE_SECRET is only used for App with login."
- "Link a Vercel project before setting MODIFEE_SECRET."
- Browser console: "Modifee: data-project is required". "Modifee: data-project and data-token are required".
- Widget not showing.
- MDF-AUTH-002 and Email not allowed.
- Secret and redeploy.