Fixing 1Password Classic form-filling on modern Firefox
Authored by Claude 4.8 Opus.
📄 Want an AI assistant to do this for you? Download this guide as Markdown and hand it to Claude Code (or any agent) — it has everything needed to find, patch, and install the extension.
Problem: On Firefox 147 and later, the legacy 1Password Classic extension
(the “1Password 4 / Mini” add-on, onepassword4@agilebits.com, used with the
1Password 7 desktop app) silently stops filling forms. The console shows:
TypeError: ... initKeyEvent is not a function
Cause: 1Password Classic simulates typing by calling the deprecated, non-standard
KeyboardEvent.initKeyEvent(). Firefox 147 disabled it and Firefox 149 removed it
(Bugzilla 2013772,
2015079,
1842988). The extension is
unmaintained, so there is no upstream fix.
This guide patches the extension to use the standard new KeyboardEvent()
constructor instead — the same change Bitwarden made for the identical breakage — and
installs the patched copy so it keeps working on current Firefox. It is durable:
because the patched extension no longer calls the removed API, future Firefox versions
can’t re-break it.
⚠️ Security note: 1Password Classic is unmaintained and has known unfixed issues. This keeps it working, not secure. The long-term-safe options are upgrading to a current 1Password app/extension or another maintained password manager. Use this if you have a deliberate reason to stay on Classic + the 1Password 7 desktop app.
TL;DR
- Find your installed
onepassword4@agilebits.com.xpi. - Unzip it, change one line in
injected.min.js, rezip it. - Install the patched copy on Firefox Developer Edition (release Firefox refuses
modified/unsigned extensions) with
xpinstall.signatures.required = false. - Keep the original extension ID so the 1Password desktop app still connects.
Why the “easy” fixes don’t work (so you don’t waste time)
about:config→dom.keyboardevent.init_key_event.enabled_in_addons = trueWorks only on Firefox 147–148. On 149+ the method is gone, not just disabled.- A separate “polyfill” extension that re-adds
initKeyEvent. Impossible: each Firefox extension runs in its own isolated content-script sandbox. 1Password calls the API inside its sandbox; your extension can’t reach it. The shim must live inside 1Password’s own code — i.e. you must patch the extension itself. - Self-signing the patched extension via addons.mozilla.org so it runs on release Firefox. AMO won’t sign under AgileBits’ add-on ID, so you’d have to change the ID. But the 1Password desktop app’s helper actively reverts its native-messaging allow-list to the original ID every time it runs, so a renamed extension can’t keep talking to the app. Keep the original ID and use a Firefox build that allows unsigned extensions (below).
Prerequisites
- The 1Password Classic extension currently (or previously) installed in Firefox.
- The 1Password 7 desktop app (this extension is just a bridge to it).
- Ability to unzip/rezip a file and edit text.
python3makes it easy (script below), but any zip tool + text editor works.
Profile and native-messaging locations by OS:
| Firefox profiles | Native-messaging hosts | |
|---|---|---|
| macOS | ~/Library/Application Support/Firefox/Profiles/ | ~/Library/Application Support/Mozilla/NativeMessagingHosts/ |
| Windows | %APPDATA%\Mozilla\Firefox\Profiles\ | (registry / app-managed) |
| Linux | ~/.mozilla/firefox/ | ~/.mozilla/native-messaging-hosts/ |
The extension file is extensions/onepassword4@agilebits.com.xpi inside your profile.
Step 1 — Get the extension file
Copy your installed xpi somewhere to work on it (don’t edit it in place):
# macOS example — adjust the profile folder name (yours ends in .default-release)
PROFILE=~/Library/"Application Support"/Firefox/Profiles/*.default-release
mkdir -p ~/op-fix && cp "$PROFILE"/extensions/onepassword4@agilebits.com.xpi ~/op-fix/op.xpi
Step 2 — Apply the one-line patch
Inside injected.min.js there is a helper that builds a keyboard event with the
removed API. It looks like this (variable letters may differ slightly by version):
// BEFORE — uses the removed initKeyEvent
function H(a){var b;b=document.createEvent('KeyboardEvent');b.initKeyEvent(a,!0,!1,null,!1,!1,!1,!1,0,0);return b}
Replace it with the standard constructor (behaviour-identical: bubbles=true, cancelable=false, all other args default):
// AFTER — standard, works on every Firefox
function H(a){return new KeyboardEvent(a,{bubbles:!0,cancelable:!1})}
Do it with a script (recommended)
Save as patch.py next to your op.xpi, then run python3 patch.py op.xpi:
import re, shutil, sys, zipfile
from pathlib import Path
src = Path(sys.argv[1])
work = src.with_suffix(".unpacked")
out = src.with_name("onepassword4-patched.xpi")
if work.exists(): shutil.rmtree(work)
with zipfile.ZipFile(src) as z: z.extractall(work)
inj = work / "injected.min.js"
text = inj.read_text(encoding="utf-8")
pat = re.compile(
r"function (\w+)\((\w+)\)\{var \w+;\w+=document\.createEvent\('KeyboardEvent'\);"
r"\w+\.initKeyEvent\(\2,![01],![01],null,![01],![01],![01],![01],0,0\);return \w+\}")
new, n = pat.subn(lambda m: f"function {m.group(1)}({m.group(2)})"
f"{{return new KeyboardEvent({m.group(2)},{{bubbles:!0,cancelable:!1}})}}", text)
if n != 1 or "initKeyEvent" in new:
sys.exit(f"Expected exactly 1 call site, found {n}. Inspect injected.min.js by hand.")
inj.write_text(new, encoding="utf-8")
# CRITICAL: kill auto-update. The manifest's update_url points at AgileBits'
# server, which still serves the ORIGINAL (broken) extension. Left in place,
# Firefox periodically prompts to re-install it over your patch and re-breaks
# autofill. Remove update_url and bump the version so nothing can replace it.
import json
mp = work / "manifest.json"
man = json.loads(mp.read_text(encoding="utf-8"))
man.get("applications", {}).get("gecko", {}).pop("update_url", None)
man["version"] = man.get("version", "4.7.5.90") + ".1"
mp.write_text(json.dumps(man), encoding="utf-8")
# The original Mozilla signature no longer matches — remove it.
shutil.rmtree(work / "META-INF", ignore_errors=True)
# Repack. IMPORTANT: keep the original add-on ID (do not touch the manifest's id/key).
if out.exists(): out.unlink()
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z:
for f in sorted(work.rglob("*")):
if f.is_file(): z.write(f, f.relative_to(work).as_posix())
print("wrote", out)
Or do it by hand
- Rename
op.xpitoop.zip, extract it. - Open
injected.min.js, findinitKeyEvent, make the one-line replacement above. - In
manifest.json, delete theupdate_urlline (underapplications.gecko) and bump theversion(e.g.4.7.5.90→4.7.5.90.1). This stops Firefox from re-installing the original broken extension over your patch — see the auto-update note below. Keep theidunchanged. - Delete the
META-INFfolder (its signature is now invalid). - Re-zip the contents (not a parent folder) and name it
onepassword4-patched.xpi.
Why the
update_urlremoval matters. The extension manifest tells Firefox to auto-update from AgileBits’ server, which still serves the original,initKeyEvent-broken extension. If you leaveupdate_urlin, Firefox will periodically pop up “do you want to install?” and, if applied, silently replace your patch with the broken version (blank toolbar icon, autofill dead again). Removingupdate_urland bumping the version prevents this permanently.
Keeping the original ID is essential: the 1Password desktop app only authorizes that exact ID to connect, and it resets that list on its own. A renamed extension won’t be able to talk to the app.
Step 3 — Install on a Firefox that allows unsigned extensions
Release/Beta Firefox refuse modified extensions. Firefox Developer Edition, Nightly, ESR, and Unbranded builds let you turn signature enforcement off. Developer Edition is the simplest and stays current:
-
Install Firefox Developer Edition (separate app and profile — your existing Firefox is untouched).
-
Launch it, open
about:config, setxpinstall.signatures.requiredtofalse. Also setextensions.update.enabledtofalseas a safety net against auto-update. To make both permanent across pref resets, add a file nameduser.jsin the Dev Edition profile folder containing:user_pref("xpinstall.signatures.required", false); user_pref("extensions.update.enabled", false); user_pref("extensions.update.autoUpdateDefault", false); -
Open
about:addons→ gear icon → Install Add-on From File… → chooseonepassword4-patched.xpi. -
Pin it: click the puzzle-piece (Extensions) toolbar icon → pin 1Password.
Step 4 — Verify
- Click the 1Password toolbar button, or press your fill shortcut (e.g. Cmd/Ctrl-\).
- It should open the 1Password Mini and fill username/password fields.
- If you want to watch for errors:
about:debugging→ This Firefox → the extension → Inspect → Console. A healthy run shows noinitKeyEventerror and no repeatedNo such native applicationdisconnects.
Troubleshooting
- “could not be installed because it could not be verified.”
You’re on release/Beta Firefox, or signature enforcement is still on. Use Developer
Edition (or Nightly/ESR/Unbranded) and set
xpinstall.signatures.required = false. - Toolbar icon is greyed out /
No such native application ...repeats in the console. The desktop app isn’t connecting. Make sure you kept the original extension ID (don’t rename it / don’t self-sign under a new ID). Confirm the 1Password 7 app is running. The native-host manifest (2bua8c4s2c.com.agilebits.1password.json) should listonepassword4@agilebits.cominallowed_extensions— the app maintains this itself; don’t fight it. - Fills the field but the website’s form doesn’t react. The constructor patch dispatches standard events; this should behave like the original. If a specific site misbehaves, file it as a separate issue.
- It worked, then broke again: blank toolbar icon, a “do you want to install?”
popup, autofill dead but Cmd-\ search still works.
Firefox auto-updated the extension from AgileBits’ server back to the original broken
version. Fix: rebuild the patch with
update_urlremoved and the version bumped (Step 2), setextensions.update.enabled = false(Step 3), then remove and reinstall the extension. Click Cancel on any “do you want to install?” prompt — that’s the upstream broken build. Confirmabout:addonsshows your bumped version (e.g.4.7.5.90.1), not the upstream4.7.5.90. - 1Password updates the extension later. Re-apply the patch to the new xpi. The script aborts loudly if the call site changed, so you’ll know to re-inspect rather than ship a broken build.
Optional — move your bookmarks/history/data to Dev Edition (no cloud)
Developer Edition uses a fresh profile. To bring your data locally (no Firefox Sync):
- Quit both Firefox and Firefox Developer Edition completely.
- Bookmarks and history live in
places.sqlite; icons infavicons.sqlite. Copy them (and optionallycookies.sqlite,logins.json+key4.db,formhistory.sqlite,permissions.sqlite,search.json.mozlz4) from your old profile into the Dev Edition profile (*.dev-edition-default). - Do not copy the
extensions/folder or extension state — reinstall add-ons from AMO instead, so the patched 1Password setup stays intact. - Do not point both Firefox builds at the same profile — that can corrupt it.
Back up the destination first so it’s reversible.
Using an AI assistant (Claude Code, etc.) to do this for you
If you’d rather have an LLM agent perform the steps, paste it something like:
I’m on Firefox (version ___, macOS/Windows/Linux). The 1Password Classic extension (
onepassword4@agilebits.com, used with the 1Password 7 desktop app) stopped filling forms withinitKeyEvent is not a function. Please:
- Find my installed
onepassword4@agilebits.com.xpiin my Firefox profile and copy it to a work folder.- In
injected.min.js, replace thedocument.createEvent('KeyboardEvent') + initKeyEvent(...)helper with the standardnew KeyboardEvent(type,{bubbles:true, cancelable:false}). Keep the original add-on ID. Inmanifest.jsonremove theupdate_urland bump theversion(so Firefox can’t auto-update back to the broken upstream build). Remove the now-invalidMETA-INFsignature and repack asonepassword4-patched.xpi.- Walk me through installing it on Firefox Developer Edition with
xpinstall.signatures.required=false, and verify filling works with the desktop app.Don’t rename the extension ID or self-sign it — the desktop app only authorizes the original ID and resets its allow-list itself.
The agent should be able to locate the file, make the single-line change, and guide the Developer Edition install. Review its changes before installing.