WEBM File Documentation
Summary
A WebM file is an open, royalty-free video container that Google launched through the WebM Project in 2010 for the HTML5 <video> tag. It wraps VP8, VP9 or AV1 video with Vorbis or Opus audio, and it is a constrained subset of Matroska built on EBML. The extension is .webm and the MIME type is video/webm. It plays natively in Chrome, Firefox, Edge and Opera, and in VLC on any operating system.
Technical details
| Feature | Value |
|---|---|
| Full name | WebM Video |
| File extension | .webm |
| MIME type | video/webm (audio-only: audio/webm) |
| Format type | EBML-based multimedia container (binary) |
| Base format | Matroska / EBML subset |
| Developer | Google — the WebM Project |
| Introduced | 2010 |
| Standard / spec | WebM Project container guidelines |
| Open standard | Yes — royalty-free |
| Byte order | Big-endian element IDs (EBML) |
| Magic number | 1A 45 DF A3 |
| DocType | webm |
| Video codecs | VP8, VP9, AV1 |
| Audio codecs | Vorbis, Opus |
| Subtitles | WebVTT |
| Native playback | Chrome, Firefox, Edge, Opera, Android, VLC |
| Related extensions | .mkv, .mka, .mp4, .ogg |
| Specification | webmproject.org/docs/container/ |
What is a WebM file?
WebM is an open, royalty-free video container that Google released through the WebM Project in 2010. The goal was practical: give the HTML5 <video> tag a format that any browser could ship without paying patent licences. A .webm file carries a video stream, one or more audio streams, and optionally subtitles, all wrapped in a single binary file with the MIME type video/webm.
The word container matters. WebM is not itself a codec, and it does not describe how the pixels or audio samples are compressed. It is a wrapper. Inside it sit separately encoded streams, and the container's job is to store those streams, label them, and interleave them so that a player knows which bytes belong to which track and at what point in time they should appear. The compression is done by the codecs (VP8, VP9 or AV1 for video; Vorbis or Opus for audio), and every one of those is open and royalty-free. That constraint is the entire reason WebM exists as a separate thing.
EBML: the binary structure underneath
WebM is built on EBML, the Extensible Binary Meta Language. EBML is a binary encoding that behaves like a tree of nested elements, much the way XML is a tree of nested tags, except everything is expressed in bytes rather than angle brackets. Every element in the file follows the same three-part pattern: an Element ID, a size, then the element's data.
Both the ID and the size are variable-length integers (VINTs), and the trick that makes them parseable is a length descriptor packed into the leading bits. A parser reads the first byte and counts the leading zero bits before the first 1 bit. That count tells it how many bytes the field occupies. A value whose first byte is 1xxx xxxx is one byte long; 01xx xxxx is two bytes; 001x xxxx is three; and so on. This is the same idea as UTF-8's leading-byte scheme. Because of it, a reader never needs to know an element's length in advance: the bytes announce their own width.
Element = [ Element ID (VINT) ][ Size (VINT) ][ Data ]
first byte 1xxx xxxx -> field is 1 byte
first byte 01xx xxxx -> field is 2 bytes
first byte 001x xxxx -> field is 3 bytes
first byte 0001 xxxx -> field is 4 bytes
The data of an element can itself be more elements (a master element) or a plain value: an unsigned integer, a signed integer, a float, a UTF-8 string, a date, or raw binary. Because unknown IDs carry their own size, a player built for an older revision can skip an element it does not recognise and keep going. That is how the format stays extensible without breaking existing readers. Element IDs are stored big-endian, which is why a hex dump of a WebM file reads in a fixed, predictable order.
The EBML header and DocType
Every WebM file opens with an EBML header, and the very first four bytes are the Element ID of that header: 1A 45 DF A3. This is the magic number that a file-type detector keys on. It is not unique to WebM, though. Matroska files start with the exact same four bytes, because Matroska is also an EBML document. The header is where the two formats diverge.
Inside the EBML header sit a handful of small elements that describe the document as a whole: EBMLVersion and EBMLReadVersion (which revision of EBML the file uses), EBMLMaxIDLength and EBMLMaxSizeLength (the widest VINTs the file will use), and then the decisive pair: DocType, a UTF-8 string, together with DocTypeVersion. For a WebM file the DocType string is webm. For a general Matroska file it is matroska. A parser reads this string and, from it, knows which set of rules and which codecs to expect. Cross-reference the MKV format, whose only structural difference at this point is that one string.
Offset 0
1A 45 DF A3 EBML header (element ID)
.. header size (VINT)
42 86 81 01 EBMLVersion = 1
42 F7 81 01 EBMLReadVersion = 1
42 82 84 77 65 62 6D DocType = "w e b m"
42 87 81 02 DocTypeVersion = 2
42 85 81 02 DocTypeReadVersion = 2
The bytes 77 65 62 6D in that dump are the ASCII for webm. Swap them for 6D 61 74 72 6F 73 6B 61 (matroska) and, structurally, you would be looking at an MKV. This is why renaming a file, or reading only its first four bytes, cannot tell WebM and MKV apart: the distinguishing data is a string a few dozen bytes in.
WebM as a constrained Matroska subset
Matroska is a general container. It will hold almost any codec: H.264, HEVC, AC-3, DTS, PCM, FLAC, and dozens more. WebM deliberately narrows that list. It permits VP8, VP9 and AV1 for video, and Vorbis and Opus for audio, and nothing else. Every one of those codecs is free of licensing fees, which is what lets a browser include a WebM decoder without a per-copy royalty. A WebM file, then, is a Matroska file that agrees to stay inside a fenced-off set of codecs.
Each track records its codec as a CodecID string in its TrackEntry. The mapping is fixed:
| Type | Codec | CodecID |
|---|---|---|
| Video | VP8 | V_VP8 |
| Video | VP9 | V_VP9 |
| Video | AV1 | V_AV1 |
| Audio | Vorbis | A_VORBIS |
| Audio | Opus | A_OPUS |
| Subtitles | WebVTT | S_TEXT/WEBVTT |
A player reads the CodecID, loads the matching decoder, and feeds it the raw frames pulled out of the container. If a file claims V_MPEGH/ISO/HEVC it is simply not valid WebM, even if it otherwise parses as EBML.
The Segment and its top-level children
After the EBML header comes the Segment, the single top-level element that holds all the actual content. Everything else is a child of the Segment. A few of those children carry structure and metadata, and one of them (the Cluster, covered below) carries the media itself.
EBML header DocType = "webm"
Segment
├─ SeekHead index of where the other top-level elements live
├─ Info TimestampScale, Duration, MuxingApp, WritingApp
├─ Tracks
│ ├─ TrackEntry TrackNumber, TrackType, CodecID (video)
│ │ └─ Video PixelWidth, PixelHeight, ...
│ └─ TrackEntry TrackNumber, TrackType, CodecID (audio)
│ └─ Audio SamplingFrequency, Channels, ...
├─ Cluster Timestamp + SimpleBlock / BlockGroup (media data)
├─ Cluster ...
└─ Cues seek index: time -> Cluster position
The SeekHead is a lookup table that records the byte position of the other top-level elements, so a player can jump straight to Tracks or Cues without scanning. The Info element holds the TimestampScale (usually 1,000,000, meaning timestamps are counted in nanosecond-scaled units so the base tick is one millisecond) and the Duration. Tracks contains one TrackEntry per stream: its TrackNumber, its TrackType (1 for video, 2 for audio, 17 for subtitles), its CodecID, and a nested Video or Audio element carrying dimensions or sample rate.
Clusters and Blocks
The media itself lives in Cluster elements. A file has many of them, laid out in playback order. Each Cluster begins with a Timestamp element that fixes the cluster's position on the timeline, expressed in TimestampScale units. Everything inside that cluster is then timed relative to this base, so individual frames store only a small signed offset rather than an absolute time. Keeping the base local to each cluster keeps the per-frame timestamps small and lets a player start decoding from any cluster.
The frames sit in SimpleBlock elements, or in a BlockGroup when extra information (like a block duration or a reference to another frame) is needed. A block header carries the track number it belongs to, its relative timestamp, and flags. One of those flags marks a keyframe: a frame that decodes on its own without depending on earlier frames. Keyframes are the points a player can safely jump to. Blocks can also use lacing, which packs several small frames of the same track into one block to cut per-frame overhead, common for audio where frames are tiny and frequent.
Cues: the seek index
Random access needs an index, and that is the Cues element, usually written near the start or end of the Segment. Each CuePoint pairs a CueTime with a CueTrackPositions block that records which Cluster holds that moment and where in the file that Cluster begins. When a viewer drags the scrub bar to 90 seconds, the player looks up the nearest cue at or before 90 seconds, seeks the file to the byte offset it names, and starts decoding from the keyframe there. Without Cues a player would have to walk every cluster from the beginning to find a given time, which is why a well-formed WebM meant for seeking always includes them.
Container versus codec: remux and re-encode
Because WebM is a container and not a codec, moving a WebM into another container is sometimes cheap and sometimes expensive, and the difference comes down to whether the destination container accepts the same codecs.
WebM to MKV is a lossless remux. WebM is already a Matroska subset, so its VP9 or AV1 video and its Opus or Vorbis audio copy straight into a full MKV without touching a single compressed frame. The command ffmpeg -i in.webm -c copy out.mkv just rewraps the streams; it finishes almost instantly and loses no quality. The reverse (MKV to WebM) only works if the MKV happens to hold WebM-legal codecs, since WebM refuses everything else.
WebM to MP4 is a re-encode, not a remux. An MP4 container expects H.264 or H.265 video and AAC audio; it does not carry VP9 or AV1 in the way players commonly support. So the video has to be decoded and re-compressed into a new codec, which takes real CPU time and gives up a little quality. Copying the streams does not work here; the codecs are wrong for the target. This is the practical caveat behind every “webm to mp4” conversion.
WebM also shows up as a replacement for animated GIF. A short muted clip encoded as VP9 in a WebM is a fraction of the size of the equivalent GIF, and it supports far more than 256 colours, which is why many sites serve looping clips as WebM (or MP4) instead. Audio-only WebM exists too, using the audio/webm MIME type and typically an Opus track, closely related to what an OGG Opus file carries. The full container rules are published in the WebM Project container guidelines.
Why does a WebM start with the same bytes as an MKV?
Because both are EBML documents, and 1A 45 DF A3 is the Element ID of the EBML header, not a WebM-specific marker. Every EBML file begins with it. The two formats separate a little further into the header, at the DocType element: a WebM sets it to the string webm, a Matroska file to matroska. A detector that reads only the first four bytes will call both “EBML”; to tell them apart it has to parse into the header and read that string.
References
- The WebM Project — container guidelines
- MDN — Media container formats
- Matroska — EBML element specification
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.