diff --git a/src/files.jl b/src/files.jl
index a9f9a120dc725c2462f772e85b89e2015e49ddc8..9033cc18e4a948cd4f9d3e6fd23f1300d4da4053 100644
--- a/src/files.jl
+++ b/src/files.jl
@@ -249,6 +249,22 @@ end
 
 getoutput(controller) = gethub(controller)[:output]
 
+function valid_filename(name)
+    # adapted from NyxUI.jl (MIT license, same author)
+    windows_extra = "|:*?<>"
+    for c in "./\\'\"`" * windows_extra
+        if c in name
+            return false
+        end
+    end
+    for nonprintable in 0x0:0x31
+        if nonprintable in name
+            return false
+        end
+    end
+    return true
+end
+
 interpolate(s="yyyymmdd_HHMMSS") = Dates.format(Dates.now(), s)
 
 function savetofile(controller, file; datafile=nothing, merge=false)
@@ -474,6 +490,11 @@ function getoutputfile(controller)
             dir = cwd(controller)
             if isnothing(file)
                 outputfile.name.val = "{yyyymmdd_HHMMSS}.label"
+            elseif !valid_filename(file)
+                @warn "Invalid filename; saving to date_time format instead" file
+                file = "{yyyymmdd_HHMMSS}.label"
+                savetofile(hub, file)
+                reset!(outputfile)
             elseif isfile(joinpath(dir, file))
                 twooptiondialog(hub, outputfile.merge,
                     "File already exists",
diff --git a/src/larvatagger.js b/src/larvatagger.js
index 8437424d0d5ae6ebd80506e8f9a72dae5a256b65..680b6cdc448ef544b3739d25236b252c3a6c6828 100644
--- a/src/larvatagger.js
+++ b/src/larvatagger.js
@@ -118,6 +118,7 @@ const LarvaTagger = (function () {
 		return false;
 	}
 
+	// TODO: validate filepath similarly to valid_filename in files.jl
 	function setOutputFilename(obs) {
 		var defaultfilepath = obs.value;
 		if (defaultfilepath === null) {