If you are upgrading from v0.16.x, replace the binary (or run docker pull). If you are upgrading from v0.15.x and below, please read the upgrading documentation for more information on how to upgrade from previous versions.
Added
Changed
Fixed
WebDAV: PROPFIND responses do not declare the namespace of every requested property on the DAV:multistatus element.
MTA:
SMTP session state obtained before STARTTLS is discarded once the TLS handshake completes, as required by RFC 3207.
Inbound throttle, outbound throttle and queue quota match expressions evaluate their if/then conditions.
DMARC: Messages without an aligned SPF or DKIM pass from a domain that publishes a DMARC policy are reported as fail instead of none in logs.
DMARC: A temporary DNS error on an SPF or DKIM check whose identifier aligns with the author domain produces temperror instead of fail.
DMARC: A policy record without a p tag is only applied, as p=none, when it contains a rua tag, even if it declares sp or np.
iMIP: Notification subjects and bodies omit the event start date, time and timezone for recurring events.
OIDC: Discovery is retried for 30 seconds before the directory is marked as unavailable, so a provider that is still starting up no longer requires a restart.
WebUI: The cached bundle of an Application is discarded when its resourceUrl changes or the record is deleted, so the next unpack fetches the new resource.
DNS: Append . to MX record hostnames.
Recovery mode: Automatic IP banning is disabled during recovery and initial setup.
FoundationDB: The cached read version is invalidated when a queue refresh or registry change broadcast is received.
Sieve: Messages filed with fileinto by a user script are treated as ham and are no longer moved to Junk when classified as spam.
IMAP:
SELECT, EXAMINE and STATUS on a shared mailbox require the r (read) right.
AUTHENTICATE is refused on clear-text connections unless allowPlainTextAuth is enabled, and the greeting and CAPABILITY response advertise LOGINDISABLED instead of the AUTH= mechanisms in that case.
IMAP, POP3, ManageSieve: Partial commands received before STARTTLS or STLS are discarded once the TLS handshake completes.
Network: Inbound TLS handshakes, both implicit and STARTTLS, are aborted after the listener's tlsTimeout, releasing the connection slot.
OAuth: A random key is used when the OIDC provider's encryptionKey or HMAC signatureKey cannot be read, instead of an empty key.
LDAP: The {local} and {domain} filter placeholders are escaped.
JMAP:
CalendarEvent/set stores useDefaultAlerts per user, and CalendarEvent/get returns false when it was never set.
CalendarEvent/get returns null for baseEventId unless the id is a synthetic recurrence instance.
CalendarEvent/get returns null for requested recurrenceRule and recurrenceOverrides properties on synthetic recurrence instances.
CalendarEvent/get and ContactCard/get return only id when the properties argument is an empty list, instead of every property.
If you are upgrading from v0.16.x, replace the binary (or run docker pull). If you are upgrading from v0.15.x and below, please read the upgrading documentation for more information on how to upgrade from previous versions.
Added
Changed
Sieve: Messages generated by user scripts are DKIM signed through the new SieveUserInterpreter.dkimSignDomain setting, which defaults to the account's own domain.
Fixed
JMAP:
CalendarEvent/set requests that ask for scheduling messages are rejected with a forbidden error when the account cannot send them.
Calendar/get and AddressBook/get return every property when the properties argument is omitted or null.
EventSource ping events advertise the interval in seconds rather than milliseconds.
Calendar synthetic ids returned when expanding recurrences identify an occurrence by its recurrence id.
IMAP: Every command in a pipelined STATUS or FETCH batch receives its tagged completion, instead of the first failing command dropping the responses for all commands queued behind it.
WebDAV: Accounts without a storage quota no longer advertise a 4 GiB limit in DAV:quota-available-bytes.
MTA: Inbound DMARC and TLS aggregate reports that a reporter sends more than once are imported again as a duplicate entry.
Spam filter: Domain and URL blocklists are queried only for text written as a link.
iTIP: Detaching an occurrence that the recurrence rule already generates is sent as a METHOD:REQUEST carrying the RECURRENCE-ID instead of a METHOD:ADD.
Sieve: fileinto :specialuse and specialuse_exists accept special-use attributes in the \Trash form.
LDAP: Active Directory servers that answer an unauthenticated bind (a non-empty DN with a zero-length password) with success no longer authenticate accounts without a password.
Network: Listeners bound to the unspecified IPv6 address ([::]) fall back to IPv4 when socket creation fails with EPROTONOSUPPORT.
OpenTelemetry: log exporter does not include the parent span's attributes.
Feature: [#26423] [Plugin] Add PathNavigator and PathConnection for exploring the footpath network as a graph.
Feature: [#26827] [Plugin] Add map resize hook to scripting api.
Feature: [#26877] Clear Scenery can now remove footpath additions, and remove walls separately from small scenery.
Feature: [#26938] [Plugin] Allow getting and setting caret position for textbox while it is in focus.
Improved: [#16269] Unnamed rides in the original Added Attractions & Loopy Landscapes scenarios now use their official names from Rollercoaster Tycoon Classic.
Improved: [#21441] Add sprite font glyph for the Hryvnia (β΄).
Improved: [#22500] LandSetHeightAction has now proper error message title.
Improved: [#23872] Loading a park outside the game now sets the folder of the load/save window, as loading one in-game does.
Improved: [#25362] When dragging footpaths, they will now properly adapt to uneven terrain.
Improved: [#26099] The ride type selection cheat has been moved into the ride operations tab.
Improved: [#26099] The ride visiblity cheat has been moved into the ride colour/appearance tab.
Improved: [#26441, #26958] Change result.position.z of SmallScenery- and WallPlaceAction to match the actual position of the placed tile element.
Improved: [#26862] Add a Liquid Glass app icon for macOS 26 and later.
Improved: [#26931] The save prompt now bounces the macOS dock icon, flashes the Windows taskbar button and marks Linux windows urgent when it blocks quitting an unfocused game.
Improved: [#26936] Rain is now drawn next to the top toolbar, but only when it is centred.
Improved: [#26996] Ride names in scenarios can now be autopatched.
Fix: [#7858] Dropdowns do not close when an action by a plugin or another player in multiplayer closes their parent window.
Fix: [#15891] Lower case hard sign is drawn as upper case.
Fix: [#18197] Track Designs Manager does not autoload scenery.
Fix: [#18415] Preview in the Track Designs Manager is not updated after deleting the first design.
Fix: [#18782] Dropdowns do not close and become non-functional when mouse is released outside of the game window.
Fix: [#21632] [Plugin] Crash when loading custom image larger than 300 by 300 pixels.
Fix: [#22854] Network connections of plugins pile up every time another park is loaded.
Fix: [#24520] The close button does not use the sprite font on languages using a TTF font when the enlarged UI is enabled.
Fix: [#24610] Graph tabs on Finances window get a little bit longer every time you switch between them.
Fix: [#25169] The convert command strips custom objects from the resulting park.
Fix: [#25496] The guest pickup button does not grey out when the guestβs state changes while the window is open.
Fix: [#25558] [Plugin] Image buttons ignore their explicit border setting when a theme draws borders around them.
Fix: [#26056] Freeze when trying to download all objects on save where they are missing.
Fix: [#26802] Opening rides with 0 or -1 water cars per train crash the game.
Fix: [#26811] Crash when a ride points to a non-existing station object.
Fix: [#26816] Water rides ignore zero clearances, preventing adjacent terrain modifications and building them anywhere.
Fix: [#26880] Max negative G-Force stat on flat track erroneously reads as 0.49g.
Fix: [#26903] Making a ride visible or invisible is always denied in multiplayer.
Fix: [#26917] Tile Inspector does not show indestructible track status correctly when βMake all track pieces destructibleβ cheat is enabled.
Fix: [#26921] The bottom panel buttons for finances, park rating, date, etc. are not invalidating properly.
Fix: [#26935] Windows opened on the title screen do not βflashβ around the edges when bringing them back into focus.
Fix: [#26938] [Plugin] Caret in textbox not animating or blinking.
Fix: [#26954] Land paint price is not updated when selecting columns or rows of tiles by holding down Ctrl.
Fix: [#26965] When showing missing objects, some types show up as βUnknown typeβ.
Fix: [#26965] The window with missing objects is displayed under the βLoad gameβ window.
Fix: [#26992] A partial or no car is drawn on the ride vehicle tab if the ride has 0 or fewer cars per train.
Fix: [#26996] Correct spelling of some rides in Heide-Park, Diamond Heights and Alton Towers.
Fix: [#26996] Scenario patch for Okinawa Coast doesnβt work for CD versions of Wacky Worlds (meaning landscape ownership fixes do not get applied).
Fix: [#27004] Windows are moved off-screen when mouse is released outside of the game window.
Fix: [#27023] [Plugin] Transparent plugin sprites do not work correctly in software rendering mode.
use internal/core/VERSION as single source of truth to get version (#6119)
httpp: propagate Flush() through the response writer wrappers (#6126) handlerLogger's responseRecorder and handlerWriteTimeout's writeTimeoutWriter both wrap http.ResponseWriter but implement neither http.Flusher nor Unwrap(). Any handler that flushes mid-response has that flush silently dropped, and its output only reaches the client when the handler returns. That defeats the stated purpose of writeTimeoutWriter, whose own comment says it exists so one can "write long responses, splitted in chunks, without causing timeouts": the deadline is reset per Write(), but nothing ever leaves the buffer until the end. Found while adding a server-sent-events endpoint: events written and flushed at t=0.4s onlyreached the client at t=2.2s, when the handler returned. Adding Flush() plus Unwrap() (so http.ResponseController can reach through) fixes both. The new test drives a real listener throughboth wrappers and reads while the handler is still blocked, so it fails on the current code by hitting the read deadline.
support setting read buffer size on macOS (#6128) (#6131)
drop support for Debian Bullseye (#6137) Bullseye reached EOL on 31st august 2026.
emit "closed" log message when a server is closed (#6152)
fix external command exit code reporting on non-Windows platforms (#6155)
metrics: add 'pos' property to forward_dests (#6183)
metrics: deprecate the 'remoteAddr' property (#6174) Metrics are meant to provide aggregate statistics, not per-peer properties, that can be accessed through the API. The property is still served to keep backward compatibility.
metrics: add 'type' to forward dests, deprecated 'protocol' (#6170) 'type' is aligned with the terminology used in the rest of the server.
metrics, pprof: set default Allow-Origins to empty list (#6165) This prevents cross-site attacks to the API with the default configuration.
always-available: support disabling recording when the source is offline (#6182) Add alwaysAvailableRecorded parameter that controls whether recording happens during offline periods for always-available paths. When set to false, recording only starts when a real source is connected.
allow sources and forward destinations with an empty password (#6193) Restore the behavior introduced in #395 and accidentally reverted by #5779, which factored the RTMP/HLS credential check into validateURL() and applied it to RTSP sources too. A username with an explicitly empty password is valid per RFC 3986 and is required by some legacy cameras whose password cannot be set. It is now accepted for every scheme handled by validateURL(), including forward destinations. A username without password or a password without username is still rejected,with a clearer error message pointing to the right syntax.
fix wrong validation of HTTP allowed origins (#6198) An allowed origin with wildcards was making the server accept origins with a different scheme. This is fixed.
fix HTTP allowed origins dots being interpreter as any character (#6199) A dot in an HTTP allowed origin now requires a corresponding dot in an incoming origin.
serve Origin: Vary header with Access-Control-Allow-Origin (#6200) This prevents CDNs from caching the Access-Control-Allow-Origin header regardless of the origin.
stop returning wildcard in Access-Control-Allow-Origin (#6201) if domain is allowed, return the domain, otherwise nothing.
pmp4, fmp4: stop validating inbound H264/H265 SPS (bluenviron/mediacommon#366) Validating SPS adds latency and is generally redundant.
h265: fix DTS extraction of streams with temporal sub-layers (bluenviron/mediacommon#368) The picture-type switches in the H265 DTS extractor accepted only TRAIL_N/TRAIL_R/RASL_N/RASL_R, so an access unit whose slices are TSA_N/TSA_R/STSA_N/STSA_R (Rec. ITU-T H.265, table 7-1, types 2-5) fell through to "access unit doesn't contain an IDR or non-IDR NALU" and the stream could never be recorded. TSA and STSA pictures use the same slice segment header syntax as TRAIL pictures, so handle them in the same way.
API
add 'type' to forward dests, deprecated 'protocol' (#6170) 'type' is aligned with the terminology used in the rest of the server.
set default Allow-Origins to empty list (#6165) This prevents cross-site attacks to the API with the default configuration.
deprecate 'conf' field of forward destinations (#6175) configuration is already available through the confName property of the path.
refactor endpoint names (#6172) group endpoints by category (i.e. /hls/muxers, /hls/sessions), rename /forward/* into /forward-dests/*, use kebab-case. Backward compatibility is preserved by still serving old endpoints.
Media-Over-QUIC
rename boolean header fields (#6148) rename Properties into HasProperties, FirstObject into IsFirstObject
close server streams when no longer in use (#6151) Explicitly close bidirectional and unidirectional streams that were previously keep open for the entire duration of the session. Note that closing a stream in QUIC/WebTransport means telling the peer that "we are not sending data anymore", while still allowing inbound data.
impose a 100MB limit on reordered bytes per session (#6194) previously, the limit was per-track. This decreases the probability of incurring in an out-of-memory situations.
demux MPEG-TS from static source pull (#6181) When rtspDemuxMpegts is set, a pulled single MP2T track is demuxed into elementary streams, matching RTSP publishers, so HLS can attach.
rtpklv: do not reuse buffer of returned KLV units (bluenviron/gortsplib#1154) Decode() returns a slice that points to the decoder's internal buffer, and the next call to Decode() overwrites that same array in place. A caller that holds the previous unit - for instance while it is being recorded or forwarded on another goroutine - ends up reading the bytes of the next unit, or a torn mix of both when the sizes differ. Clear the buffer instead of truncating it, so each unit gets its own array. This is what the h264, h265, av1, vp8 andmpeg1video decoders already do, at the cost of one allocation per unit.
auth: support qop=auth in digest client (bluenviron/gortsplib#1074) (bluenviron/gortsplib#1155) The client never looked at the qop directive of a digest challenge and always computed the legacy RFC 2069 response, so servers that require qop="auth" - as most RFC 7616 implementations do - rejected every request with a 401. Parse qop in WWW-Authenticate, and when the server offers "auth", send qop, cnonce and nc and compute the response as H(HA1:nonce:nc:cnonce:qop:HA2) (RFC 7616, section 3.4.1). The nonce count increases at every request, as required. Challenges without qop keep the previous behavior. auth-int is not implemented. Also copy the opaque value of the challenge into the Authorization header, which RFC 7616 section 3.3 says the client should return unchanged.
rtpav1: do not set Y/Z bits when no OBU bytes were written (bluenviron/gortsplib#1153) When the remaining space in a packet is too small to hold any byte of thenext OBU, the encoder writes nothing but still marks the current packet with Y=1 and the following one with Z=1. The AV1 RTP payload format (v1.0, section 4.4) requires both bits to be 0 unless an OBU element is actually fragmented across the two packets. Receivers that honor the bits therefore join two complete OBUs into one: this happens with the decoder in this package, with pion/rtp and with ffmpeg. It is reachable whenever an earlier OBU leaves exactly 0 free bytes (or 0-2 when the next OBU is size-prefixed), so it affects streams that are repacketized, e.g. non-RTSP sources read over RTSP in mediamtx.
code.cloudfoundry.org/bytefmt updated from v0.85.0 to v0.88.0
github.com/bluenviron/gohlslib/v2 updated from v2.4.3 to v2.4.4
github.com/bluenviron/gortmplib updated from v1.0.1 to v1.0.2
github.com/bluenviron/gortsplib/v5 updated from v5.6.4 to v5.6.5
github.com/bluenviron/mediacommon/v2 updated from v2.9.3 to v2.9.4
github.com/matthewhartstonge/argon2 updated from v1.5.6 to v1.5.7
github.com/pion/webrtc/v4 updated from v4.2.18 to v4.2.19
github.com/quic-go/quic-go updated from v0.61.0 to v0.62.0
github.com/quic-go/webtransport-go updated from v0.12.0 to v0.13.0
github.com/stretchr/testify updated from v1.12.0 to v1.12.1
github.com/dunglas/httpsfv updated from v1.1.0 to v1.1.1
github.com/pion/stun/v3 updated from v3.1.6 to v3.1.7
github.com/pion/turn/v5 updated from v5.0.12 to v5.0.13
gopkg.in/yaml.v3 removed
go.yaml.in/yaml/v3 v3.0.5 added
github.com/bluenviron/mediamtx-rpicamera updated from v2.8.0 to v2.9.0
hls.js updated from v1.7.0 to v1.7.2
Security
Binaries are compiled from source code by the Release workflow, which is a fully-visible process that prevents any change or external interference in produced artifacts.
Checksums of binaries are also published in a public blockchain by using GitHub Attestations, and they can be verified by running:
ls mediamtx_* | xargs -L1 gh attestation verify --repo bluenviron/mediamtx
You can verify checksums of binaries by downloading checksums.sha256 and running:
UpSnap can now automatically track changing IP addresses. For devices with IP tracking enabled, UpSnap periodically scans the local subnet with nmap, matches discovered hosts by MAC address, and updates the stored IP when a device is found at a new address.
1. Set a cron schedule in the settings page:
We do not recommend anything lower than 1 minute. Depending on the amount of devices you have added to UpSnap, the scan duration will increase. Every 30 minutes is plenty for most users.
2. Enable per device:
Changelog
Features
b5fd864: feat: optional per-device IP address tracking via periodic ARP scan (#1763) (@mwpastore)
One device, however you connect it. The same mouse now keeps one device card and one set of settings across receiver and USB connections. Existing configurations migrate automatically and are backed up before the first save. (#876)
A redesigned device workspace makes larger setups easier to manage. A responsive grid, compact cards, clearer battery status, custom device names, scalable interface presets, and a dedicated action menu make devices faster to scan and configure.
Per-app profiles are now editable in the GUI. Pick an installed application from the new catalog, see which profile is active, and customize its button bindings without editing TOML. (#976)
Scrolling is smoother and more configurable. Built-in smooth scrolling, vertical sensitivity controls, preserved fractional motion, rebindable wheel tilt, and fixes for diverted high-resolution wheels deliver more consistent scrolling across main and thumb wheels. (#902, #925, #992)
Buttons can now distinguish a tap from a hold. TOML bindings can assign independent short- and long-press actions with a 500 ms threshold, while held shortcuts remain active until the button is released. (#961)
What's Changed
chore(gui): bump gpui-component to da4f9369 by @AprilNEA in #655
refactor(hid): centralize device identities by @AprilNEA in #831
docs(agents): scale local gate to affected packages by @AprilNEA in #696
feat(core,gui): make the main wheel's tilt rebindable (MX anywhere 2s,...) by @6lv1-blr in #902
fix(linux): install the app icon at every indexed hicolor size by @4ni1ak in #837
fix(gui): recognize mx ergo wheel tilt metadata by @AprilNEA in #905
ci: automate typo checks and consolidate configs by @AprilNEA in #906
refactor(gui): convert app state to entity events by @AprilNEA in #904
fix(mouse): compose thumb-wheel preset labels from translated action names by @NatsUIJM in #910
fix(linux): restore glibc 2.35 package baseline by @AprilNEA in #911
fix(gui): restore full-phrase thumb-wheel preset labels by @AprilNEA in #913
refactor(gui): componentize free-function builders by @AprilNEA in #914
fix(macos): honor XDG state dir in permission diagnostics by @AprilNEA in #915
fix(hid): improve Unifying receiver discovery reliability by @v14 in #853
feat(gui): add per-app profiles and redesign device workspace by @AprilNEA in #907
fix(macos): use reliable async-hid report writes by @sh3ll3x3c in #889
fix(hid): preserve usage pairs in native handle cache by @sh3ll3x3c in #882
fix: show Windows MSI completion and failure state by @mvanhorn in #819
feat(gui): make custom controls keyboard-operable by @AprilNEA in #916
style(gui): refine profile toolbar layout by @AprilNEA in #921
refactor(gui): clean up render paths and element ids by @AprilNEA in #926
fix(hidpp): read Unifying link encryption from bit 5, not the software-present bit by @AprilNEA in #924
refactor(gui): tighten typed rendering and async ownership by @AprilNEA in #931
refactor(gui): move device reads onto swr by @AprilNEA in #929
feat(gui): add scalable interface presets by @AprilNEA in #934
feat(agent): unify button Down/Up/Cancel lifecycle by @AprilNEA in #933
feat(i18n): add Turkish (tr) interface locale by @4ni1ak in #835
feat(gui): add switchable device gallery views by @AprilNEA in #927
refactor(gui): improve shared component quality by @AprilNEA in #966
feat(agent): hold shortcut output until button release by @AprilNEA in #960
fix(thumbwheel): scale remapped vertical scrolling by @hsearcy in #925
refactor(gui): structure app state by domain by @AprilNEA in #974
feat(gui): add installed application catalog picker by @AprilNEA in #976
feat(gui): add polished battery indicator by @AprilNEA in #983
feat(scroll): preserve precise scroll distances by @AprilNEA in #973
ci: replace target caches with sccache by @AprilNEA in #984
feat(scroll): add finite smooth scroll runtime by @AprilNEA in #975
feat(scroll): smooth diverted thumbwheel input by @AprilNEA in #977
feat(gui): polish the app catalog picker and device cards by @AprilNEA in #986
feat(scroll): add vertical scroll sensitivity by @AprilNEA in #978
feat(scroll): expose smooth scrolling setting by @AprilNEA in #979
refactor(gui): strengthen typed render boundaries by @AprilNEA in #981
fix(core,gui,agent): key device settings by identity, not by transport by @yuzi-co in #876
feat(agent): dispatch independent long-press actions by @AprilNEA in #961
refactor(agent): complete thumbwheel state machine by @AprilNEA in #968
fix(hid): preserve diverted wheel reporting by @AprilNEA in #992
v0.7.5 and v0.7.6 were tagged but never published β their macOS packaging failed before a single artifact was uploaded, so no release exists for either. Everything both of them contained ships here, and the changes below cover everything since v0.7.4.
What's Changed
docs(ci): map every CI job to a local command by @davidbudnick in #732