← Chrome 151 reference

v151 · html · web api · shipped

Capability Elements: <usermedia> MVP

A new <usermedia> HTML element: a browser-controlled, user-activated button that brokers camera and microphone access — running the permission flow and the getUserMedia() call for you, then handing the resulting MediaStream to the page. Ships in Chrome 151 on desktop and Android, enabled by default.

Mixed stability — read before copying The <usermedia> element itself is enabled by default in Chrome 151 (runtime feature UserMediaElement, status stable). The legacy migration surface documented below (type attribute, isValid, permissionStatus, prompt events) is experimental — gated by the UserMediaElementLegacy flag, testable via chrome://flags or --enable-blink-features=UserMediaElementLegacy — exists only to migrate origin-trial users of the earlier <permission> element, and is slated for deprecation. Do not build new code on it.

at a glance

Shipped inChrome 151 (desktop, Android) — milestone listing: “Enabled by default”
StatusEnabled by default (runtime feature UserMediaElement, stable)
Origin trial ranChrome 144–148, extended twice per the ChromeStatus extension records: to Chrome 152 (April 2026) and then to Chrome 155 (July 2026)
SpecMedia Capture and Streams Extensions — Media capture HTML elements
ExplainerMedia Capture Elements explainer (w3c/mediacapture-extensions)
TAG revieww3ctag/design-reviews #1218
ChromeStatus4926233538330624 — Capability Elements <usermedia> MVP

why it exists

The traditional camera/microphone flow is fully imperative: call navigator.mediaDevices.getUserMedia() from script, handle the permission prompt asynchronously, then attach the stream to a <video> element. This creates two long-standing problems:

<usermedia> makes the element itself the trusted control. Clicking it is an unambiguous user gesture on a browser-rendered button; the browser runs the permission UX inline, performs the getUserMedia() call, and delivers the MediaStream to the page. Because the browser controls the element's rendering, a previously-denied user gets an in-page recovery path instead of a dead end. The feature evolved out of the Page-Embedded Permission Control (PEPC) work: the generic <permission> element was origin-trialled first, then split into capability-specific elements (<usermedia> here; <camera> and <microphone> are a separate, still-experimental feature).

Source: Media Capture Elements explainer; chromestatus.com/feature/4926233538330624.

Syntax

<!-- Declarative: no required attributes. Children are fallback content. -->
<usermedia>Camera and microphone access required</usermedia>

<script>
  const el = document.querySelector("usermedia");
  // Optional: configure the getUserMedia() call the element will make.
  el.setConstraints({ video: { width: 1280 }, audio: {} });
</script>

The element's interface is HTMLUserMediaElement (the tag is usermedia). The normative IDL from the specification, matching Chromium's shipping IDL:

[Exposed=Window]
interface HTMLUserMediaElement : HTMLElement {
  [HTMLConstructor] constructor();

  readonly attribute MediaStream? stream;
  readonly attribute DOMException? error;
  undefined setConstraints(optional HTMLMediaStreamConstraints constraints = {});

  attribute EventHandler onstream;
  attribute EventHandler onerror;
  attribute EventHandler oncancel;
};

dictionary HTMLMediaStreamConstraints {
  MediaTrackConstraintSet video;
  MediaTrackConstraintSet audio;
};

Chromium implements the same surface split across html_user_media_element.idl (plus the HTMLMediaCaptureElementBase mixin in html_media_capture_element_base.idl) and two partial mixins in modules/mediastream (stream, setConstraints), gated by RuntimeEnabled=UserMediaElementstable in runtime_enabled_features.json5.

Content model: the element belongs to the flow, phrasing, interactive, and palpable content categories; it can appear wherever phrasing content is expected, and any children serve as fallback content (rendered when the element can't operate — for example in a non-secure context). Presentation is a user-agent-controlled button whose label and icon reflect the capture state.

Source: Media Capture and Streams Extensions — The <usermedia> HTML element.

Inputs

The element takes no required content attributes<usermedia></usermedia> alone requests both camera and microphone with default settings. Three input channels exist:

Source: Specification — constraint filter and content model; Explainer — constraints configuration.

Outputs

Three observable outputs:

The browser-rendered button itself is also an output: its label and icon track the underlying stream state (requesting, live, muted, ended) without any page code.

Source: Specification — activation and event model; Explainer — user journey and interaction model.

Errors

Failures never throw synchronously from markup or clicks; they surface through the error property and the cancel/error events. The exact mapping, from the specification and Chromium's implementation:

Failure paths — what fires and what error holds
CauseEventerror value
User dismisses or denies the permission promptcancelNotAllowedError (“Permission dismissed” / “Permission denied”)
Activation without a genuine user gesture (no transient activation)errorInvalidStateError
Stream creation fails (no device, device busy, track start failure)errorOperationError (“Stream creation failed”)
Underlying getUserMedia() rejects (device busy, no device, over-constrained after filtering, …)errorThe rejection DOMException (e.g. NotFoundError, NotReadableError, OverconstrainedError)
Non-secure contextnoneThe element renders its fallback content instead (Chromium also reports a DevTools audits issue)

Recovery is always the same shape: read el.error in the terminal event handler, show your own recovery UI, and let the user click the element again — the element re-arms after a failed attempt.

Source: Specification — activation behavior and event queueing; Chromium html_media_capture_element_base.cc; Chromium user_media_request_provider_impl.cc.

Context

Exposure: HTMLUserMediaElement is [Exposed=Window] — no worker exposure. Like getUserMedia(), the element only operates in a secure context; elsewhere it shows fallback content. Feature-detect with "HTMLUserMediaElement" in window — on unsupported engines the tag parses as an unknown element and your fallback content renders.

Activation requirements: acquisition starts only from a genuine user gesture — the click must be a trusted event with transient user activation; programmatic or synthetic clicks are rejected (an error event with InvalidStateError). The browser, not the site, mediates the camera/microphone permission, and may skip the prompt entirely when the user has already granted it.

Default capture: with no configuration the element requests both camera and microphone (Chromium's ApplyDefaultConstraints() installs VIDEO_CAPTURE + AUDIO_CAPTURE descriptors when nothing else is set).

Source: Specification — shared activation algorithms; Chromium html_user_media_element.cc.

Lifecycle

The element is a small state machine:

  1. Request statestream is null. A trusted click starts acquisition: the embedded permission flow runs first (prompt only if needed), then the browser performs the getUserMedia() call with the configured constraints.
  2. Terminal event — exactly one of stream / cancel / error fires per attempt (queued as an element task). On success stream is populated before the stream event fires.
  3. Active state — while the associated MediaStream is active, further clicks do not start a new request; unlike <camera>/<microphone>, the MVP <usermedia> deliberately does not become a mute/unmute toggle, because coordinating independent audio/video track states is ambiguous. Track muting stays with your own UI or MediaStreamTrack.enabled.
  4. Reset — the element monitors its tracks (including external track.stop()); once the stream ends it returns to the request state and the next click starts a fresh acquisition. A failed attempt also re-arms the element immediately.
Source: Explainer — user journey and interaction model; Specification — activation behavior; Chromium user_media_request_provider_impl.cc.

Examples

<usermedia id="capture">This page needs your camera and microphone.</usermedia>
<video id="preview" autoplay playsinline muted></video>

<script>
  const el = document.getElementById("capture");
  const video = document.getElementById("preview");

  // Optional configuration; call before the user interacts.
  el.setConstraints({
    video: { width: 1280 },
    audio: { echoCancellation: true }
  });

  el.addEventListener("stream", () => {
    // Success: el.stream is a live MediaStream.
    video.srcObject = el.stream;
  });
  el.addEventListener("cancel", () => {
    console.log("Prompt dismissed or denied:", el.error?.message);
  });
  el.addEventListener("error", () => {
    console.error("Capture failed:", el.error?.name, el.error?.message);
  });
</script>

No getUserMedia() call, no promise handling, no permission re-prompt logic — the browser owns the entire acquisition flow.

Live demo on chrome-platform-showcase — best in Chrome 151+ with a camera/microphone. Open the demo full-page.
Source: Explainer — examples.

Compatibility

<usermedia> element — checked 2026-07-26
Engine / runtimeSupportNotes
Chrome / Edge (desktop)151Enabled by default per the milestone listing and the stable runtime feature
Chrome (Android)151Per the ChromeStatus feature detail (desktop + Android 151); not supported on Android WebView (requires permission-manager support)
FirefoxNot supportedVendor position “Under consideration” (mozilla/standards-positions #1392)
SafariNot supportedNo signal; dedicated position request WebKit/standards-positions #651 unanswered; earlier discussion in WICG/PEPC #62
BCDNo entryNo api/HTMLUserMediaElement.json in browser-compat-data as of 2026-07-26
WPTUpstream WPT coverage existshtml/semantics/permission-element/usermedia (idlharness, setConstraints combinations, cancel-prompt, iframe, untrusted-click, attribute-handler tests); Chromium also unit-tests the element (core, modules)

MDN has no page for the element or HTMLUserMediaElement as of 2026-07-26, which is why this reference exists. Always feature-detect ("HTMLUserMediaElement" in window) and keep meaningful fallback content inside the tag.

Source: ChromeStatus API feature record (milestones + vendor positions).

Security and privacy

Source: Explainer — privacy and security considerations; Specification — shared algorithms (trusted events); TAG design review #1218.

Legacy migration surface — syntax

For sites migrating from the origin-trial <permission> element, Chrome also ships an experimental legacy surface (runtime feature UserMediaElementLegacy, status experimental). Adding a type attribute switches the element into legacy mode, where it behaves like the old <permission> element:

// Chromium html_user_media_element.idl — every member below is
// [RuntimeEnabled=UserMediaElementLegacy] (experimental, not for new code):
[Reflect] attribute DOMString type;            // "camera", "microphone", or "camera microphone"
static boolean isTypeSupported(DOMString type);
readonly attribute boolean isValid;
readonly attribute DOMString invalidReason;
readonly attribute PermissionState initialPermissionStatus;
readonly attribute PermissionState permissionStatus;
attribute EventHandler onpromptaction;
attribute EventHandler onpromptdismiss;
attribute EventHandler onvalidationstatuschange;
Source: Chromium html_user_media_element.idl.

Legacy migration surface — inputs

The type content attribute is the only input: a space-separated list taking camera, microphone, or both (either order). It takes effect once, when first set — later changes or removal are ignored by the implementation. Any other token, or three or more tokens, makes the element invalid: Chromium reports an “invalid type” DevTools audits issue and the element falls back to its inert fallback rendering. isTypeSupported(type) lets migration code probe a type string before stamping it into markup.

Source: Chromium html_user_media_element.cc — ParseType and AttributeChanged.

Legacy migration surface — outputs

In legacy mode the element behaves as a permission gate, not a stream broker: it exposes the permission state machine directly through isValid (the element is usable), invalidReason (why not — for example an unsupported type or a policy block), initialPermissionStatus and permissionStatus (PermissionState: "granted" / "denied" / "prompt"), and the promptaction / promptdismiss / validationstatuschange events. It does not populate stream — in legacy mode the error getter even hard-returns null, and media acquisition stays with your own getUserMedia() call.

Source: Chromium html_user_media_element.cc — IsLegacyMode branching.

Legacy migration surface — errors

Legacy-mode failures are reported through state, not exceptions: an invalid type flips isValid to false with a machine-readable invalidReason, and validationstatuschange fires when validity changes. Because the legacy branch bypasses the media-capture event model, the error property always reads null there — do not rely on it when type is present. Reading any legacy member without the experimental flag simply yields undefined (the members don't exist), which is itself a usable probe.

Source: Chromium html_user_media_element.idl; Chromium html_user_media_element.cc.

Legacy migration surface — context

The legacy members exist only under the experimental UserMediaElementLegacy runtime feature, which shares the origin-trial token name UserMediaElement so origin-trial pages keep working while they migrate. It is testable locally with chrome://flags or --enable-blink-features=UserMediaElementLegacy. The stated intent in the IDL is to deprecate and remove type once <usermedia> adoption is stable — tracked as crbug.com/493632110. Treat the entire surface as temporary migration scaffolding.

Source: Chromium runtime_enabled_features.json5; Chromium html_user_media_element.idl.

Legacy migration surface — lifecycle

The legacy branch is decided the moment the type attribute is first parsed; from then on every virtual hook (AttributeChanged, permission-status changes, embedded-permission decisions, activation) dispatches to the legacy <permission>-element code path instead of the media-capture path. There is no transition back: removing type does not restore MVP behavior, you must replace the element. initialPermissionStatus captures the state at setup; permissionStatus live-tracks grants and revocations after that.

Source: Chromium html_user_media_element.cc — AttributeChanged and IsLegacyMode.

Legacy migration surface — examples

// Migration probe (requires the experimental flag to do anything):
const el = document.querySelector("usermedia");
if ("isTypeSupported" in HTMLUserMediaElement) {
  // Legacy surface present (flag on). Ask before stamping markup:
  console.log(HTMLUserMediaElement.isTypeSupported("camera microphone")); // true
  console.log(HTMLUserMediaElement.isTypeSupported("geolocation"));       // false
}

// Legacy usage (origin-trial carry-over; do NOT use in new code):
// <usermedia type="camera microphone"></usermedia>
// el.isValid, el.permissionStatus, el.onpromptaction now behave like the
// old <permission> element; el.stream is never populated for you.
Source: Chromium html_user_media_element.idl.

Legacy migration surface — compatibility

Legacy (type) surface — checked 2026-07-26
Engine / runtimeSupportNotes
Chrome 151 (default)Not enabledUserMediaElementLegacy is experimental; on via flag or the UserMediaElement origin-trial token
Firefox / SafariNot supportedNo legacy <permission>-element implementation exists outside Chromium
Spec statusNot specifiedAbsent from the Media Capture HTML elements spec section — Chromium-only migration aid
FutureDeprecatingIDL TODO: deprecate type once adoption stabilizes (crbug.com/493632110)
Source: Chromium runtime_enabled_features.json5.

Legacy migration surface — security and privacy

The legacy members expose fine-grained permission state (initialPermissionStatus, permissionStatus) to script — information the Permissions API already provides for camera/microphone, so no new disclosure class, but it makes permission probing marginally easier, one more reason it is flag-gated. The trust model is unchanged from the MVP surface: UA-rendered control, trusted-gesture activation, no site access to the prompt internals. Migrating off type removes this surface from your pages entirely.

Source: Chromium html_user_media_element.idl; Explainer — security considerations.

Superseded draft design

The original WICG/PEPC usermedia_element.md explainer described a different shape: constraints as JSON inside a <script type="permissionconstraints"> child, an autostart attribute, a settable stream property, an enabled property with click-to-mute, and streamready / userchangedenabled events. None of that ships in Chrome 151. The implemented MVP uses setConstraints(), a read-only stream, and stream / cancel / error events; it has no autostart (the newer explainer proposes one, but it is not in the shipping IDL) and no mute-toggle behavior. If you followed early-origin-trial documentation, update your integration.

Source: WICG/PEPC usermedia_element.md (superseded); Media Capture Elements explainer (current); Chromium html_user_media_element.idl (shipping IDL).

member references

see also