API in Capabilities
WebOTP API
Published
The WebOTP API extends Credential Management so a site can request an SMS one-time code that is bound to its domain. After the user consents, navigator.credentials.get() returns an OTPCredential whose code can fill a verification form (WebOTP API, MDN).
Support
Section titled “Support”MDN marks WebOTP as limited availability and restricted to secure contexts. Its compatibility data records OTPCredential from Chrome 93 on desktop and Chrome 84 on Android, with support mirrored to Edge, Opera, Samsung Internet, and Meta Quest Browser; it records no support in Firefox, Safari, or Android WebView (WebOTP API, MDN). A field with autocomplete="one-time-code" preserves manual entry and lets Safari offer its separate SMS-code autofill (Verify phone numbers on the web with the WebOTP API, Chrome for Developers).
Syntax
Section titled “Syntax”const credential = await navigator.credentials.get({ otp: { transport: ['sms'] }, signal,});The SMS must end with the calling domain and code. The domain has no scheme or port, and the domain-bound line is the last line (WebOTP API, MDN).
Parameters
Section titled “Parameters”Pass an otp option whose transport contains "sms". An optional AbortSignal cancels a pending request. When the request succeeds, the returned OTPCredential exposes the received value through code (WebOTP API, WICG).
Exceptions
Section titled “Exceptions”A policy can disable the API with the otp-credentials Permissions Policy directive. Its default allowlist is self; a request blocked by policy rejects with SecurityError (WebOTP API, MDN). The request can also reject when its abort signal is triggered, so keep the input and submit button usable.
Examples
Section titled “Examples”The first example shows the server-produced SMS shape; the second keeps a manual verification path while requesting the code.
Format a domain-bound SMS
Section titled “Format a domain-bound SMS”For https://www.example.com, MDN gives this message shape:
Your verification code is 123456.
@www.example.com #123456The blank line separates the explanatory text from the final domain-bound line.
Fill the code while retaining manual entry
Section titled “Fill the code while retaining manual entry”This form keeps the standard input available whether or not WebOTP exists. Submitting manually aborts the pending credential request.
<form id="otp-form" action="/verify" method="post"> <label>Verification code <input name="code" autocomplete="one-time-code" inputmode="numeric" required></label> <button>Verify</button></form><script> const form = document.querySelector('#otp-form'); const input = form.elements.code;
if ('OTPCredential' in window) { const controller = new AbortController(); form.addEventListener('submit', () => controller.abort(), { once: true });
navigator.credentials.get({ otp: { transport: ['sms'] }, signal: controller.signal, }).then((otp) => { input.value = otp.code; form.requestSubmit(); }).catch(() => { input.focus(); }); } else { input.focus(); }</script>The fallback focuses the same field, so unsupported browsers retain the manual verification path.
See also
Section titled “See also”- Credential Management API
- WebAuthn and passkeys
- WebOTP API (wicg.github.io)
- WebOTP API (MDN)
Specifications
| Specification | Status |
|---|---|
| WebOTP API | WICG draft |