MoQ is under active development. APIs and protocols may change between releases.

Skip to content

Upgrade ​

This page walks from the 2026-09-17 releases (moq-relay 0.14.18, moq-cli 0.11.2, moq-net 0.2.22, @moq/net 0.3.5, moq-ffi 0.3.19) to the 2026-09-23 release train (moq-relay 0.15.1, moq-cli 0.12.1, moq-net 0.3.0, @moq/net 0.4.0, moq-ffi 0.4.1). Each crate's CHANGELOG.md has the full list; this page is the subset that breaks a working setup.

A released flag, environment variable, or config key that was renamed is refused at startup with its replacement named, rather than ignored. Fix what the error lists and rerun.

Wire ​

Older protocol versions still negotiate, so relays and clients can be upgraded in any order, apart from pattern-only token grants and two wire changes:

  • The lite 06 ALPN is moq-lite-06, not moq-lite-06-wip. An explicit moq-lite-06-wip in a version list is refused (#3941).
  • The hang catalog's root timeline entry is archive, and wall time moved to a root clock: { wall, timescale } (#3612, #3675). A new moq export hls finds no timeline in an old publisher's catalog, so upgrade publishers before the HLS gateway.

Relay and CLI ​

The moq-relay and moq flags split into --listen-* (accepting), --connect-* (dialing), and a shared --quic-* section. Environment variables follow the flag (MOQ_SERVER_BIND is MOQ_LISTEN).

BeforeAfter
--server-bind--listen
--server-*, --tls-cert, --tls-key, --tls-generate--listen-*, --listen-tls-cert, --listen-tls-key, --listen-tls-generate
--client-connect--connect
--client-*--connect-*
--client-failover-delay--connect-race
--client-reconnect=false--connect-once (inverted)
--tls-disable-verify, --client-tls-disable-verify--connect-tls-insecure
--server-quic-*, --client-quic-*--quic-*, applied to both directions
TOML [server], [client][listen], [connect]
TOML [server.quic], [client.quic][quic]
TOML listen, connect, failover_delay, reconnect, disable_verifybind, url, race, once (inverted), insecure
--cluster-lingerremoved; a broadcast closes when its last publisher is lost
--cluster-connect host:porta full URL, https://host/?jwt=TOKEN
--cluster-mesh, TOML meshremoved; list every peer with --cluster-connect or --cluster-connect-api
moq --origin, --name, --latency-max--hop, --broadcast, --max-age
moq publish, moq subscribemoq import, moq export
moq token, the moq-token binarymoq auth

Other changes to a deployment:

  • Auth is one contract (#3688). The relay asks an auth server per session (--auth-url) or applies a static anonymous grant (--auth-public); exactly one is required. --auth-key, --auth-key-dir, --auth-api, --auth-api-mode, --auth-public-api, --auth-domain, --auth-mtls-tier, and --auth-tls-* are gone: run moq auth serve --key-dir ... next to the relay and point --auth-url at it. The flag-by-flag mapping is in Migrating from the relay flags.
  • Grants are patterns, not prefixes. anon is now exactly the broadcast anon; write anon/** for the subtree. This applies to --auth-public, TOML public, and the [auth.public] table, which is now public_subscribe / public_publish. A public or mTLS pattern with no wildcard refuses to start, naming the subtree to write, rather than pick one reading silently. The patterns are rooted at /, as in 0.14, so anon/** admits a client dialed at /anon and refuses one dialed outside anon/. Earlier 0.15 releases rooted them at the dialed path instead.
  • Token grants are patterns. JWT publish and subscribe claims are patterns, so a token granting alice covers only alice; sign alice/** instead. Existing put/get tokens and key scopes keep working as subtrees, and subtree-only grants are still signed in that form, so a moq-token deployment can upgrade issuers and verifiers in either order. Verifiers on the pattern-only moq-auth 0.1.0/0.1.1 or @moq/auth 0.1.x/0.2.0 refuse that form, so upgrade them before their issuers. Grants only a pattern can express need an upgraded verifier.
  • Removed auth settings refuse to start. 0.14's [auth] key, key_dir, auth_api, domains, mtls_tier, and [auth.tls], and their flags and MOQ_AUTH_* variables, stop the relay with the replacement named rather than being ignored.
  • Token claims. iss, sub, and jti are ignored and nbf is enforced. Any other claim refuses the token with its name, aud and cluster included, where 0.14 ignored all but aud: an issuer adding app claims such as user_id must drop them.
  • Every credential is evaluated or refused. A relay on --auth-public refuses a session presenting a token, as 0.14 did, including peers sending cluster.token, and refuses to start with a client CA. moq auth serve refuses a session presenting both a JWT and a certificate, so a peer presents one or the other.
  • moq auth serve never re-checks or expires by default, as 0.14 never did. --revalidate needs --expires, and --limit-* needs --revalidate.
  • mTLS admits nothing on its own. A verified client certificate is reported to the auth server, which grants it. moq auth serve --mtls-publish '**' --mtls-subscribe '**' restores the old full access for every certificate the relay's client CA verifies, so keep that CA to cluster peers.
  • moq --listen needs auth. A CLI listener refuses to start without --auth-url or --auth-public instead of accepting everyone.
  • Gossip discovery is removed. A relay dials only the peers it lists or finds on the LAN, never a URL learned from an announcement, and no longer announces .internal/origins. The /nodes endpoint lists only peers this relay dialed. Until every relay that ran --cluster-mesh is upgraded, keep client grants off .internal/: an older relay still dials any URL announced there with cluster.token, and an upgraded peer still forwards it.
  • Other 0.14 auth differences kept. Peers identify by certificate or LAN path. An auth server's root alias may have any depth. A path in --cluster-connect is not refused, although it shifts the mesh frame. moq auth serve --key takes a file, not an https or JWKS URL. moq auth sign --root is the token root and JS verify --root the dialed path. /.cluster* roots are reserved. --auth-public a,b splits on commas.
  • noq is the only QUIC stack (#3811). The quinn and quiche cargo features and the backend setting are gone.
  • Stats counters are *_started / *_ended (sessions_started, announces_ended, ...). This release still writes the old announced / *_closed names beside them, so move consumers now.

GStreamer ​

  • moqsink properties estimated-send-bitrate / estimated-recv-bitrate are estimated-send-rate / estimated-recv-rate, with no alias; a gst-launch line naming the old ones fails at runtime.

Rust ​

  • moq-native is moq-tokio (#2896). moq-native 0.20.0 is a stub that fails to compile with the rename. ClientConfig::default().init()? is moq_tokio::connect::Config::default().init(quic)?, and with_publisher(&origin).with_subscriber(origin) is with_origin(origin). Names sit under their modules (connection::Goaway, connection::Monitor, transport::Session; #3745).
  • moq-token is moq-auth (#3684). Claims holds Patterns, and Claims::authorize returns pattern residuals.
  • Origins (#3400, #3804). Origin::random().produce() is moq_tokio::origin::spawn(). create_broadcast(path, route) is origin.publish(path, route), or create_broadcast(path) then broadcast.announce(route). with_root plus scope(prefixes) is one scope(root, &patterns) returning Result. origin::Info is origin::Config.
  • Announcements are prefix routes (#3225, #3770). announce::Update is { prefix, route, kind, captures }: skip !update.kind.is_active() and resolve the broadcast with consumer.request_broadcast(&update.prefix). Serve a subtree on demand with origin.dynamic(prefix, route).
  • Tracks. with_latency_max / latency_max is with_max_age / max_age. write_datagram(Datagram) is insert_datagram(sequence, timestamp, payload) (#3666); append_datagram is unchanged. track::SubscriberControl is track::Control, track::GroupRequest is group::Request, and ConnectionStats is session::Stats with estimated_send_rate / estimated_recv_rate.
  • Oversized groups abort with GroupTooLarge instead of shedding their head (#3585).
  • Catalog edits are fallible (#3644, #3813). moq-mux and moq-json lock() is modify()?, and a guard that fails to publish on drop aborts the track. timeline::Config::wall is the broadcast Clock.
  • moq-json config (#3718). compression: bool is a Compression enum; track-owning options are producer::Config / consumer::Config.
  • moq-mux imports are typed. import::Init splits into AudioInit, VideoInit, and ContainerInit, with typed formats instead of strings, and Track::new is Track::audio / Track::video.
  • moq-relay embedding (#3638). Relay fields are private: clone the handles you need, mount routes, then call Relay::run. Cluster::with_cache moved to cluster::Options.

JavaScript ​

The JavaScript packages have no changelog; this list follows the breaking PRs, so a minor rename may be missing.

  • @moq/token is @moq/auth. sign / verify are Key.sign / Key.verify, and claims are pattern unions (see Token grants are patterns).
  • One Connection (#3614, #3636). Connection.Reload is new Moq.Connection({ url }), which pools one connection per relay. closed settles only on close(); the error that stopped retrying is error.
  • Origins hold broadcasts (#2705, #3225). connection.publish(path, broadcast) is origin.createBroadcast(path) then broadcast.announce(), and connection.announced(prefix) is origin.announced(scope) yielding { prefix, kind, route }. ignoreSelf is gone: reflected announces are always dropped.
  • Names mirror Rust (#3710). latencyMax is maxAge (a Time.Milli), writeDatagram is insertDatagram, and RemoteError is Error.Stream / Error.Session.
  • @moq/watch (#3396, #3817). latency, latency-min, latency-max, and jitter are delay and buffer, which need a unit (delay="100ms", buffer="30s"). reload is announced. Watch.Broadcast({ connection }) is Watch.Player({ origin: connection.origin, ... }).
  • @moq/publish takes origin: connection.origin instead of a connection signal.
  • @moq/json and @moq/binary take one options object (#3640): new Json.Snapshot.Consumer({ track }) instead of (track, config).
  • @moq/hang reads the catalog archive entry instead of timeline.

Bindings ​

Python (moq-rs 0.5.0), Swift (0.5.0), Kotlin (dev.moq:moq 0.5.0), and Go wrap moq-ffi 0.4. These are the moq-ffi names; each wrapper follows them in its own casing:

  • Go module path is moq.dev/moq (was github.com/moq-dev/moq-go/moq).
  • Publish split by kind. publish_media* is publish_audio, publish_video, or publish_container, each taking its own init. The raw encoder paths that used to be publish_audio / publish_video are encode_audio / encode_video.
  • Durations are microseconds (max_age_us, MoqBackoff.initial_us), and rate estimates are estimated_send_rate / estimated_recv_rate.
  • Setters are fallible (#3642). Client and server configuration setters return an error (Busy during connect or listen) instead of dropping the value. set_tls_disable_verify(bool) is set_tls_verify(bool).
  • Announcements. MoqAnnounced is MoqAnnounceConsumer and MoqAnnouncement is MoqAnnounceUpdate; MoqBroadcastRequest::abort is reject, and MoqOriginOptions is MoqOriginConfig.
  • MoqAudioCodec is an opus() object (#3671).
  • Errors. MoqError::Protocol carries a MoqProtocolError (scope, wire code, kind) instead of a flattened message.
  • Track and group finish() keeps the handle open so a later abort() can still run.

C (libmoq 0.6):

  • The 41 moq_client_* setters are one zero-initializable moq_client_config, whose durations are _us.
  • moq_publish_media is moq_publish_audio, moq_publish_video, or moq_publish_container; the raw encoders are moq_encode_audio* / moq_encode_video*. Formats are enums instead of strings.
  • moq_announced is moq_announce_update, moq_origin_consume_announced is moq_origin_announced_broadcast, and moq_broadcast_request_abort is moq_broadcast_request_reject.

Licensed under MIT or Apache-2.0