diff --git a/config/ArduinoCreateAgent.plist b/config/ArduinoCreateAgent.plist
new file mode 100644
index 000000000..7e85cc3b5
--- /dev/null
+++ b/config/ArduinoCreateAgent.plist
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+	<dict>
+		<key>KeepAlive</key>
+		<false/>
+		<key>Label</key>
+		<string>cc.arduino.arduino-create-agent</string>
+		<key>Program</key>
+		<string>{{.Program}}</string>
+		<key>RunAtLoad</key>
+		<{{.RunAtLoad}}/>
+		<key>AbandonProcessGroup</key>
+		<true/>
+	</dict>
+</plist>
\ No newline at end of file
diff --git a/config/autostart.go b/config/autostart.go
new file mode 100644
index 000000000..c6b2b8d52
--- /dev/null
+++ b/config/autostart.go
@@ -0,0 +1,124 @@
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package config
+
+import (
+	// we need this for the ArduinoCreateAgent.plist in this package
+	_ "embed"
+	"os"
+	"os/exec"
+	"text/template"
+
+	"github.com/arduino/go-paths-helper"
+	log "github.com/sirupsen/logrus"
+)
+
+//go:embed ArduinoCreateAgent.plist
+var launchdAgentDefinition []byte
+
+// getLaunchdAgentPath will return the path of the launchd agent default path
+func getLaunchdAgentPath() *paths.Path {
+	return GetDefaultHomeDir().Join("Library", "LaunchAgents", "ArduinoCreateAgent.plist")
+}
+
+// InstallPlistFile will handle the process of creating the plist file required for the autostart
+// and loading it using launchd
+func InstallPlistFile() {
+	launchdAgentPath := getLaunchdAgentPath()
+	if !launchdAgentPath.Exist() {
+		err := writePlistFile(launchdAgentPath)
+		if err != nil {
+			log.Error(err)
+		} else {
+			err = loadLaunchdAgent() // this will load the agent: basically starting a new instance
+			if err != nil {
+				log.Error(err)
+			} else {
+				log.Info("Quitting, another instance of the agent has been started by launchd")
+				os.Exit(0)
+			}
+		}
+	} else {
+		// we already have an existing launchd plist file, so we don't have to do anything
+		log.Infof("the autostart file %s already exists: nothing to do", launchdAgentPath)
+
+	}
+}
+
+// writePlistFile function will write the required plist file to launchdAgentPath
+// it will return nil in case of success,
+// it will error in any other case
+func writePlistFile(launchdAgentPath *paths.Path) error {
+	src, err := os.Executable()
+
+	if err != nil {
+		return err
+	}
+	data := struct {
+		Program   string
+		RunAtLoad bool
+	}{
+		Program:   src,
+		RunAtLoad: true, // This will start the agent right after login (and also after `launchctl load ...`)
+	}
+
+	t := template.Must(template.New("launchdConfig").Parse(string(launchdAgentDefinition)))
+
+	// we need to create a new launchd plist file
+	plistFile, _ := launchdAgentPath.Create()
+	return t.Execute(plistFile, data)
+}
+
+// loadLaunchdAgent will use launchctl to load the agent, will return an error if something goes wrong
+func loadLaunchdAgent() error {
+	// https://www.launchd.info/
+	oscmd := exec.Command("launchctl", "load", getLaunchdAgentPath().String())
+	err := oscmd.Run()
+	return err
+}
+
+// UninstallPlistFile will handle the process of unloading (unsing launchd) the file required for the autostart
+// and removing the file
+func UninstallPlistFile() {
+	err := unloadLaunchdAgent()
+	if err != nil {
+		log.Error(err)
+	} else {
+		err = removePlistFile()
+		if err != nil {
+			log.Error(err)
+		}
+	}
+}
+
+// unloadLaunchdAgent will use launchctl to load the agent, will return an error if something goes wrong
+func unloadLaunchdAgent() error {
+	// https://www.launchd.info/
+	oscmd := exec.Command("launchctl", "unload", getLaunchdAgentPath().String())
+	err := oscmd.Run()
+	return err
+}
+
+// removePlistFile function will remove the plist file from $HOME/Library/LaunchAgents/ArduinoCreateAgent.plist and return an error
+// it will not do anything if the file is not there
+func removePlistFile() error {
+	launchdAgentPath := getLaunchdAgentPath()
+	if launchdAgentPath.Exist() {
+		log.Infof("removing: %s", launchdAgentPath)
+		return launchdAgentPath.Remove()
+	}
+	log.Infof("the autostart file %s do not exists: nothing to do", launchdAgentPath)
+	return nil
+}
diff --git a/config/config.go b/config/config.go
index 303aadce8..437437e59 100644
--- a/config/config.go
+++ b/config/config.go
@@ -91,6 +91,22 @@ func GetDefaultConfigDir() *paths.Path {
 	return agentConfigDir
 }
 
+// GetDefaultHomeDir returns the full path to the user's home directory.
+func GetDefaultHomeDir() *paths.Path {
+	// UserHomeDir returns the current user's home directory.
+
+	// On Unix, including macOS, it returns the $HOME environment variable.
+	// On Windows, it returns %USERPROFILE%.
+	// On Plan 9, it returns the $home environment variable.
+
+	homeDir, err := os.UserHomeDir()
+	if err != nil {
+		log.Panicf("Can't get user home dir: %s", err)
+	}
+
+	return paths.New(homeDir)
+}
+
 //go:embed config.ini
 var configContent []byte
 
diff --git a/config/config.ini b/config/config.ini
index 960e5f3d3..f63377db5 100644
--- a/config/config.ini
+++ b/config/config.ini
@@ -6,4 +6,5 @@ appName = CreateAgent/Stable
 updateUrl = https://downloads.arduino.cc/
 origins = https://local.arduino.cc:8000
 #httpProxy = http://your.proxy:port # Proxy server for HTTP requests
-crashreport = false # enable crashreport logging
\ No newline at end of file
+crashreport = false # enable crashreport logging
+autostartMacOS = true # the Arduino Create Agent is able to start automatically after login on macOS (launchd agent)
\ No newline at end of file
diff --git a/main.go b/main.go
index 1011ccefe..0cb55a325 100755
--- a/main.go
+++ b/main.go
@@ -66,21 +66,22 @@ var (
 
 // iniflags
 var (
-	address      = iniConf.String("address", "127.0.0.1", "The address where to listen. Defaults to localhost")
-	appName      = iniConf.String("appName", "", "")
-	gcType       = iniConf.String("gc", "std", "Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage)")
-	hostname     = iniConf.String("hostname", "unknown-hostname", "Override the hostname we get from the OS")
-	httpProxy    = iniConf.String("httpProxy", "", "Proxy server for HTTP requests")
-	httpsProxy   = iniConf.String("httpsProxy", "", "Proxy server for HTTPS requests")
-	indexURL     = iniConf.String("indexURL", "https://downloads.arduino.cc/packages/package_staging_index.json", "The address from where to download the index json containing the location of upload tools")
-	iniConf      = flag.NewFlagSet("ini", flag.ContinueOnError)
-	logDump      = iniConf.String("log", "off", "off = (default)")
-	origins      = iniConf.String("origins", "", "Allowed origin list for CORS")
-	regExpFilter = iniConf.String("regex", "usb|acm|com", "Regular expression to filter serial port list")
-	signatureKey = iniConf.String("signatureKey", "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvc0yZr1yUSen7qmE3cxF\nIE12rCksDnqR+Hp7o0nGi9123eCSFcJ7CkIRC8F+8JMhgI3zNqn4cUEn47I3RKD1\nZChPUCMiJCvbLbloxfdJrUi7gcSgUXrlKQStOKF5Iz7xv1M4XOP3JtjXLGo3EnJ1\npFgdWTOyoSrA8/w1rck4c/ISXZSinVAggPxmLwVEAAln6Itj6giIZHKvA2fL2o8z\nCeK057Lu8X6u2CG8tRWSQzVoKIQw/PKK6CNXCAy8vo4EkXudRutnEYHEJlPkVgPn\n2qP06GI+I+9zKE37iqj0k1/wFaCVXHXIvn06YrmjQw6I0dDj/60Wvi500FuRVpn9\ntwIDAQAB\n-----END PUBLIC KEY-----", "Pem-encoded public key to verify signed commandlines")
-	updateURL    = iniConf.String("updateUrl", "", "")
-	verbose      = iniConf.Bool("v", true, "show debug logging")
-	crashreport  = iniConf.Bool("crashreport", false, "enable crashreport logging")
+	address        = iniConf.String("address", "127.0.0.1", "The address where to listen. Defaults to localhost")
+	appName        = iniConf.String("appName", "", "")
+	gcType         = iniConf.String("gc", "std", "Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage)")
+	hostname       = iniConf.String("hostname", "unknown-hostname", "Override the hostname we get from the OS")
+	httpProxy      = iniConf.String("httpProxy", "", "Proxy server for HTTP requests")
+	httpsProxy     = iniConf.String("httpsProxy", "", "Proxy server for HTTPS requests")
+	indexURL       = iniConf.String("indexURL", "https://downloads.arduino.cc/packages/package_staging_index.json", "The address from where to download the index json containing the location of upload tools")
+	iniConf        = flag.NewFlagSet("ini", flag.ContinueOnError)
+	logDump        = iniConf.String("log", "off", "off = (default)")
+	origins        = iniConf.String("origins", "", "Allowed origin list for CORS")
+	regExpFilter   = iniConf.String("regex", "usb|acm|com", "Regular expression to filter serial port list")
+	signatureKey   = iniConf.String("signatureKey", "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvc0yZr1yUSen7qmE3cxF\nIE12rCksDnqR+Hp7o0nGi9123eCSFcJ7CkIRC8F+8JMhgI3zNqn4cUEn47I3RKD1\nZChPUCMiJCvbLbloxfdJrUi7gcSgUXrlKQStOKF5Iz7xv1M4XOP3JtjXLGo3EnJ1\npFgdWTOyoSrA8/w1rck4c/ISXZSinVAggPxmLwVEAAln6Itj6giIZHKvA2fL2o8z\nCeK057Lu8X6u2CG8tRWSQzVoKIQw/PKK6CNXCAy8vo4EkXudRutnEYHEJlPkVgPn\n2qP06GI+I+9zKE37iqj0k1/wFaCVXHXIvn06YrmjQw6I0dDj/60Wvi500FuRVpn9\ntwIDAQAB\n-----END PUBLIC KEY-----", "Pem-encoded public key to verify signed commandlines")
+	updateURL      = iniConf.String("updateUrl", "", "")
+	verbose        = iniConf.Bool("v", true, "show debug logging")
+	crashreport    = iniConf.Bool("crashreport", false, "enable crashreport logging")
+	autostartMacOS = iniConf.Bool("autostartMacOS", true, "the Arduino Create Agent is able to start automatically after login on macOS (launchd agent)")
 )
 
 var homeTemplate = template.Must(template.New("home").Parse(homeTemplateHTML))
@@ -327,6 +328,15 @@ func loop() {
 		}
 	}
 
+	// macos agent launchd autostart
+	if runtime.GOOS == "darwin" {
+		if *autostartMacOS {
+			config.InstallPlistFile()
+		} else {
+			config.UninstallPlistFile()
+		}
+	}
+
 	// launch the hub routine which is the singleton for the websocket server
 	go h.run()
 	// launch our serial port routine