# 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](https://www.modifee.com/docs/install/html).

## Steps

Use the wizard step **Install the widget**, or **Settings → Widget install**.

### Site without login

1. 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`.

2. 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

1. Set **Authentication** to **App with login** and save.
2. 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](https://www.modifee.com/docs/vercel/signing-secret) and [Secret and redeploy](https://www.modifee.com/docs/troubleshooting/secret-and-redeploy).
3. 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](https://www.modifee.com/docs/install/nextjs-app-router) and [Next.js Pages Router](https://www.modifee.com/docs/install/nextjs-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."

4. **Install automatically** for Flask templates `templates/base.html`, `templates/layout.html`, or `app/templates/base.html` writes `modifee_identity.py` and 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

1. Deploy the page that contains `widget.js` and this project's key.
2. Open **Settings → Widget install**.
3. Press **Check installation**.
4. "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 on` the saved site URL. `Paste the snippet just before </body> and check again.`
- `The widget on this page belongs to another project.`
- `We couldn't reach` the 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 from` the 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](https://www.modifee.com/docs/troubleshooting/widget-not-showing).
- [MDF-AUTH-002](https://www.modifee.com/docs/errors/MDF-AUTH-002) and [Email not allowed](https://www.modifee.com/docs/troubleshooting/email-not-allowed).
- [Secret and redeploy](https://www.modifee.com/docs/troubleshooting/secret-and-redeploy).
