diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9223991e6580ad50e904a58d104f9a9de424049c..c3cd68547aec54883e4e8c6b66b625c0b73a5b7a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 ### Added
 
 * New config option to set user agent when requesting URL previews.
+* Added support for `image/jxl` thumbnailing.
 
 ### Fixed
 
diff --git a/thumbnailing/i/jpegxl.go b/thumbnailing/i/jpegxl.go
new file mode 100644
index 0000000000000000000000000000000000000000..7c46f0a3e624711a990016c21977cc9933a2eea8
--- /dev/null
+++ b/thumbnailing/i/jpegxl.go
@@ -0,0 +1,69 @@
+package i
+
+import (
+	"errors"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path"
+
+	"github.com/turt2live/matrix-media-repo/common/rcontext"
+	"github.com/turt2live/matrix-media-repo/thumbnailing/m"
+	"github.com/turt2live/matrix-media-repo/util"
+	"github.com/turt2live/matrix-media-repo/util/cleanup"
+)
+
+type jpegxlGenerator struct {
+}
+
+func (d jpegxlGenerator) supportedContentTypes() []string {
+	return []string{"image/jxl"}
+}
+
+func (d jpegxlGenerator) supportsAnimation() bool {
+	return false
+}
+
+func (d jpegxlGenerator) matches(img []byte, contentType string) bool {
+	return contentType == "image/jxl"
+}
+
+func (d jpegxlGenerator) GetOriginDimensions(b []byte, contentType string, ctx rcontext.RequestContext) (bool, int, int, error) {
+	return false, 0, 0, nil
+}
+
+func (d jpegxlGenerator) GenerateThumbnail(b []byte, contentType string, width int, height int, method string, animated bool, ctx rcontext.RequestContext) (*m.Thumbnail, error) {
+	key, err := util.GenerateRandomString(16)
+	if err != nil {
+		return nil, errors.New("jpegxl: error generating temp key: " + err.Error())
+	}
+
+	tempFile1 := path.Join(os.TempDir(), "media_repo."+key+".1.jpegxl")
+	tempFile2 := path.Join(os.TempDir(), "media_repo."+key+".2.png")
+
+	defer os.Remove(tempFile1)
+	defer os.Remove(tempFile2)
+
+	f, err := os.OpenFile(tempFile1, os.O_RDWR|os.O_CREATE, 0640)
+	if err != nil {
+		return nil, errors.New("jpegxl: error writing temp jpegxl file: " + err.Error())
+	}
+	_, _ = f.Write(b)
+	cleanup.DumpAndCloseStream(f)
+
+	err = exec.Command("convert", tempFile1, tempFile2).Run()
+	if err != nil {
+		return nil, errors.New("jpegxl: error converting jpegxl file: " + err.Error())
+	}
+
+	b, err = ioutil.ReadFile(tempFile2)
+	if err != nil {
+		return nil, errors.New("jpegxl: error reading temp png file: " + err.Error())
+	}
+
+	return pngGenerator{}.GenerateThumbnail(b, "image/png", width, height, method, false, ctx)
+}
+
+func init() {
+	generators = append(generators, jpegxlGenerator{})
+}