MoQT explained

The MoQT SUBSCRIBE message

A SUBSCRIBE in Media over QUIC Transport is not a one-shot GET — it is a standing request for a track. Its structure answers three questions: which track (Track Namespace + Track Name), from where (the Location Filter), and how (Forward, Subscriber Priority, Group Order — all carried as parameters). The relay replies with exactly one SUBSCRIBE_OK or REQUEST_ERROR, and objects then flow on their own streams.

Field layout below reflects draft-ietf-moq-transport-18 and -19. For byte-exact ordering in one draft, see the message-type reference.

SUBSCRIBE fields

Field Type What it does
Request ID varint · body Client-assigned identifier for this request. The SUBSCRIBE_OK / REQUEST_ERROR, later REQUEST_UPDATEs, and teardown are all keyed to it. draft-19 allows several concurrent subscriptions to one track, each with its own Request ID.
Track Namespace tuple · body Ordered tuple of byte-string components identifying the namespace, e.g. ["sports","live"].
Track Name bytes · body Byte string naming the specific track within the namespace, e.g. "video". Matched exactly.
Subscriber Priority param · 0x20 SUBSCRIBER_PRIORITY parameter. How this subscription ranks against the subscriber’s other subscriptions on the same connection. Lower value = higher priority (0 is highest).
Group Order param · 0x22 GROUP_ORDER parameter. Requested delivery order: Ascending (0x1, oldest-first) or Descending (0x2, newest-first). SUBSCRIBE_OK reports what was actually chosen.
Forward param · 0x10 FORWARD parameter. Initial Forward State: 1 = deliver objects now; 0 = establish the subscription but send no objects until flipped with REQUEST_UPDATE.
Location Filter param · 0x21 LOCATION_FILTER parameter (named SUBSCRIPTION_FILTER before draft-19). Where in the track to start: Largest Object, Next Group Start, AbsoluteStart, or AbsoluteRange (see below). Absolute filters carry explicit Group/Object location fields.
Other parameters KVP[] Authorization Token (0x03), delivery-timeout parameters, and — in draft-19 — Range Filter parameters (0x25–0x29) that further restrict which objects pass.

Only Request ID, Track Namespace and Track Name are fixed fields in the message body. Since draft-15, Subscriber Priority, Group Order, Forward and the filter are carried as parameters (draft-13/14 had them as fixed fields), and the Track Alias is assigned by the publisher in SUBSCRIBE_OK — it is not sent in SUBSCRIBE.

The two identifiers people most often confuse are Track Namespace vs Track Name. The namespace is a tuple that groups tracks; the name addresses one track inside it.

The Filter Type: where delivery starts

The filter is the most consequential field. It answers from which point in the track do I want objects? — in terms of groups, because a group boundary is the only safe place to start decoding.

Filter Code Start location End Use it when
Largest Object 0x2 {Largest.Group, Largest.Object + 1} open Join a live track now and get everything from here on
Next Group Start 0x1 {Largest.Group + 1, 0} open Join live at the next clean group boundary (next IDR)
AbsoluteStart 0x3 explicit {Group, Object} open Everything from a known point forward
AbsoluteRange 0x4 explicit {Group, Object} explicit End Group A bounded window of the track

The filter travels in the LOCATION_FILTER parameter (0x21) — renamed from SUBSCRIPTION_FILTER in draft-19. The 0x2 / 0x1 filter names have been Largest Object / Next Group Start since well before draft-17 (much older drafts called 0x2 Latest Object). draft-19 adds five Range Filter parameters (0x25–0x29) that further restrict which objects within the range are delivered. The full filter walkthrough is in How MoQT subscriptions work.

A SUBSCRIBE, sketched

SUBSCRIBE                              (Type 0x3)
  Request ID       = 4
  Track Namespace  = ["sports","live","game42"]
  Track Name       = "video"
  Parameters:
    SUBSCRIBER_PRIORITY (0x20) = 0
    GROUP_ORDER         (0x22) = Ascending (0x1)
    FORWARD             (0x10) = 1
    LOCATION_FILTER     (0x21) = Next Group Start (0x1)
    DELIVERY_TIMEOUT           = 2000ms

  ─► SUBSCRIBE_OK                      (Type 0x4)
       Track Alias      = 1
       Group Order      = Ascending
       Largest Location = {group 812, object 4}
       Expires          = 0

How SUBSCRIBE changed across drafts

draft-15

Moved Subscriber Priority, Group Order, Forward, and the Filter out of fixed SUBSCRIBE fields and into parameters (draft-13/14 carried them as fixed fields). From here on the SUBSCRIBE body is just Request ID + Track Namespace + Track Name + Parameters, and the Track Alias is assigned by the publisher in SUBSCRIBE_OK.

draft-17

Added a Required Request ID Delta field to every request message, including SUBSCRIBE.

draft-18

Removed Required Request ID Delta again (every request is one byte shorter). Split namespace subscription into SUBSCRIBE_NAMESPACE + SUBSCRIBE_TRACKS.

draft-19

Added Range Filter parameters (SUBGROUP_FILTER 0x25, OBJECTID_FILTER 0x26, PRIORITY_FILTER 0x27, OBJECT_PROPERTY_FILTER 0x28, TRACK_PROPERTY_FILTER 0x29) that select which objects within the subscription pass. Renamed the SUBSCRIPTION_FILTER parameter to LOCATION_FILTER (0x21). Allowed multiple concurrent subscriptions to the same track.

Full per-draft diffs: draft-18 changes and draft-19 changes.

Frequently asked questions

What is the structure of the MoQT SUBSCRIBE message?
In draft-18 and draft-19 the SUBSCRIBE body carries just a Request ID, the Track Namespace (a tuple) and Track Name (bytes) that identify the track, and a set of parameters. The knobs that used to be fixed fields are now parameters: Subscriber Priority (SUBSCRIBER_PRIORITY, 0x20), Group Order (GROUP_ORDER, 0x22), the Forward flag (FORWARD, 0x10), and the Location Filter (LOCATION_FILTER, 0x21). The Track Namespace and Track Name together identify which track; the Location Filter decides where in that track delivery starts. The Track Alias is assigned by the publisher in the SUBSCRIBE_OK response — it is not sent in SUBSCRIBE.
What is the difference between Track Namespace and Track Name in a SUBSCRIBE?
Both appear in every SUBSCRIBE. The Track Namespace is an ordered tuple that groups related tracks (e.g. ["sports","live"]); the Track Name is a byte string naming one track within it (e.g. "video"). The namespace supports prefix matching in discovery messages, but inside a SUBSCRIBE both are used to address exactly one track.
How did the SUBSCRIBE message change in draft-18 and draft-19?
draft-18 removed the Required Request ID Delta field that draft-17 had added (making every request one byte shorter) and split namespace subscription into SUBSCRIBE_NAMESPACE and SUBSCRIBE_TRACKS. draft-19 added Range Filter parameters (types 0x25–0x29) that select which objects within a subscription pass, renamed the SUBSCRIPTION_FILTER parameter to LOCATION_FILTER (0x21), and allowed multiple concurrent subscriptions to the same track. (The filter values themselves — Largest Object 0x2, Next Group Start 0x1, AbsoluteStart 0x3, AbsoluteRange 0x4 — have been stable since well before draft-17.)
What are the MoQT subscription filter types?
There are four Location Filters that decide where delivery starts: Largest Object (0x2) starts at the next object the publisher emits; Next Group Start (0x1) starts at the next group boundary; AbsoluteStart (0x3) starts at an explicit {group, object}; and AbsoluteRange (0x4) requests a bounded window with an explicit End Group. draft-19 adds Range Filters on top, which further restrict which objects within that range are delivered.
What does the relay send back to a SUBSCRIBE?
Exactly one SUBSCRIBE_OK or REQUEST_ERROR. SUBSCRIBE_OK assigns the Track Alias that the data streams will use for this track, and is also a status snapshot: the Largest Location the publisher has produced (where "now" is), an Expires value, the Group Order actually selected, and a trailing Track Properties block. A REQUEST_ERROR carries an error code such as unauthorized or "track does not exist" (usually a namespace/name mismatch).

Related

Decode SUBSCRIBE on the wire

moqtap shows the exact filter, forward state, priority, and Largest Location your implementation sent and received — so you can see whether a stalled subscription is your filter, your auth, or the publisher.