diff --git a/scribus/plugins/scriptplugin/prefs_scripter.cpp b/scribus/plugins/scriptplugin/prefs_scripter.cpp
index b4bcf8094a..cf0643ad4f 100644
--- a/scribus/plugins/scriptplugin/prefs_scripter.cpp
+++ b/scribus/plugins/scriptplugin/prefs_scripter.cpp
@@ -31,6 +31,18 @@ Prefs_Scripter::Prefs_Scripter(QWidget* parent)
 	// The startup script box should be disabled  if ext scripts are off
 	startupScriptEdit->setEnabled(extensionScriptsChk->isChecked());
 	startupScriptEdit->setText(scripterCore->startupScript());
+
+	PrefsContext* prefs = PrefsManager::instance()->prefsFile->getPluginContext("scriptplugin");
+	PrefsTable* prefUserScriptsPath = prefs->getTable("userscriptspaths");
+	QStringList userPaths{};
+	for (int i = 0; i < prefUserScriptsPath->getRowCount(); i++)
+	{
+		userPaths.append(prefUserScriptsPath->get(i, 0));
+	}
+	userScriptsPaths = userPaths.join(";");
+	userScriptPathEdit->setText(userScriptsPaths);
+	userScriptPathEdit->setToolTip( "<qt>" + tr("Multiple paths are separated by semicolons. You need to restart Scribus to see new scripts.") + "</qt>");
+
 	// signals and slots connections
 	connect(extensionScriptsChk, SIGNAL(toggled(bool)), startupScriptEdit, SLOT(setEnabled(bool)));
 	// colors
@@ -79,6 +91,19 @@ void Prefs_Scripter::apply()
 		prefs->set("syntaxtext", textColor.name());
 
 		emit prefsChanged();
+
+		if (userScriptPathEdit->text() != userScriptsPaths)
+		{
+			PrefsTable* prefUserScriptsPath = prefs->getTable("userscriptspaths");
+			prefUserScriptsPath->clear();
+			int i = 0;
+			for (auto path: userScriptPathEdit->text().split(";"))
+			{
+				prefUserScriptsPath->set(i, 0, path);
+				i++;
+			}
+			emit prefsUserScriptsPathsChanged();
+		}
 	}
 }
 
diff --git a/scribus/plugins/scriptplugin/prefs_scripter.h b/scribus/plugins/scriptplugin/prefs_scripter.h
index 597236bab8..2c94d20f8b 100644
--- a/scribus/plugins/scriptplugin/prefs_scripter.h
+++ b/scribus/plugins/scriptplugin/prefs_scripter.h
@@ -37,6 +37,8 @@ class Prefs_Scripter : public Prefs_Pane, Ui::Prefs_Scripter
 		QColor signColor;
 		QColor stringColor;
 		QColor numberColor;
+	private:
+		QString userScriptsPaths{};
 
 	protected slots:
 		/*! \brief All requests for color change are handled here.
@@ -47,6 +49,7 @@ class Prefs_Scripter : public Prefs_Pane, Ui::Prefs_Scripter
 
 	signals:
 		void prefsChanged();
+		void prefsUserScriptsPathsChanged();
 };
 
 #endif // PREFS_SCRIPTER_H
diff --git a/scribus/plugins/scriptplugin/prefs_scripterbase.ui b/scribus/plugins/scriptplugin/prefs_scripterbase.ui
index 7c35b7505c..00986aca25 100644
--- a/scribus/plugins/scriptplugin/prefs_scripterbase.ui
+++ b/scribus/plugins/scriptplugin/prefs_scripterbase.ui
@@ -98,6 +98,20 @@
          </item>
         </layout>
        </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_4">
+         <item>
+          <widget class="QLabel" name="label_3">
+           <property name="text">
+            <string>User Scripts Path:</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="userScriptPathEdit"/>
+         </item>
+        </layout>
+       </item>
        <item>
         <spacer name="verticalSpacer_2">
          <property name="orientation">
diff --git a/scribus/plugins/scriptplugin/scriptercore.cpp b/scribus/plugins/scriptplugin/scriptercore.cpp
index c9c8672cca..75a16fe16a 100644
--- a/scribus/plugins/scriptplugin/scriptercore.cpp
+++ b/scribus/plugins/scriptplugin/scriptercore.cpp
@@ -15,6 +15,8 @@ for which a new license (GPL+exception) is in place.
 #include <QTextCodec>
 #include <QByteArray>
 #include <QPixmap>
+#include <QDir>
+#include <QDirIterator>
 #include <cstdlib>
 
 #include "runscriptdialog.h"
@@ -48,6 +50,7 @@ ScripterCore::ScripterCore(QWidget* parent)
 	pcon = new PythonConsole(parent);
 	scrScripterActions.clear();
 	scrRecentScriptActions.clear();
+	scrUserScriptActions.clear();
 	returnString = "init";
 
 	scrScripterActions.insert("scripterExecuteScript", new ScrAction(QObject::tr("&Execute Script..."), QKeySequence(), this));
@@ -90,6 +93,7 @@ void ScripterCore::addToMainWindowMenu(ScribusMainWindow *mw)
 	menuMgr->addMenuItemString("scripterExecuteScript", "Scripter");
 	menuMgr->createMenu("RecentScripts", QObject::tr("&Recent Scripts"), "Scripter", false, true);
 	menuMgr->addMenuItemString("RecentScripts", "Scripter");
+	addUserScriptsMenu();
 	menuMgr->addMenuItemString("scripterExecuteScript", "Scripter");
 	menuMgr->addMenuItemString("SEPARATOR", "Scripter");
 	menuMgr->addMenuItemString("scripterShowConsole", "Scripter");
@@ -101,6 +105,20 @@ void ScripterCore::addToMainWindowMenu(ScribusMainWindow *mw)
 	menuMgr->addMenuItemStringstoMenuBar("Scripter", scrScripterActions);
 	RecentScripts = SavedRecentScripts;
 	rebuildRecentScriptsMenu();
+	rebuildUserScriptsMenu();
+}
+
+void ScripterCore::addUserScriptsMenu()
+{
+	if (m_userScriptsEnabled)
+	{
+		if (!menuMgr->menuExists("UserScripts"))
+		{
+			menuMgr->createMenu("UserScripts", QObject::tr("S&cripts"), "Scripter", false, true);
+			menuMgr->addMenuItemString("UserScripts", "Scripter");
+		}
+
+	}
 }
 
 void ScripterCore::enableMainWindowMenu()
@@ -109,6 +127,10 @@ void ScripterCore::enableMainWindowMenu()
 		return;
 	menuMgr->setMenuEnabled("ScribusScripts", true);
 	menuMgr->setMenuEnabled("RecentScripts", true);
+	if (m_userScriptsEnabled)
+	{
+		menuMgr->setMenuEnabled("UserScripts", true);
+	}
 	scrScripterActions["scripterExecuteScript"]->setEnabled(true);
 }
 
@@ -118,6 +140,10 @@ void ScripterCore::disableMainWindowMenu()
 		return;
 	menuMgr->setMenuEnabled("ScribusScripts", false);
 	menuMgr->setMenuEnabled("RecentScripts", false);
+	if (m_userScriptsEnabled)
+	{
+		menuMgr->setMenuEnabled("UserScripts", false);
+	}
 	scrScripterActions["scripterExecuteScript"]->setEnabled(false);
 }
 
@@ -156,6 +182,33 @@ void ScripterCore::rebuildRecentScriptsMenu()
 	menuMgr->addMenuItemStringstoRememberedMenu("RecentScripts", scrRecentScriptActions);
 }
 
+void ScripterCore::updateUserScriptsMenu()
+{
+	m_userScripts = getUserScripts(m_userScriptsPaths);
+	rebuildUserScriptsMenu();
+}
+
+void ScripterCore::rebuildUserScriptsMenu()
+{
+	menuMgr->clearMenuStrings("UserScripts");
+	scrUserScriptActions.clear();
+	for (auto script: m_userScripts)
+	{
+		QString menuEntry = QFileInfo(script).baseName();
+		scrUserScriptActions.insert(menuEntry, new ScrAction( ScrAction::RecentScript, menuEntry, QKeySequence(), this, script));
+		connect( scrUserScriptActions[menuEntry], SIGNAL(triggeredData(QString)), this, SLOT(runUserScript(QString)) );
+		menuMgr->addMenuItemString(menuEntry, "UserScripts");
+	}
+
+	menuMgr->addMenuItemString("SEPARATOR", "UserScripts");
+
+	scrUserScriptActions.insert("userScriptsUpdate", new ScrAction(QObject::tr("List &Update"), QKeySequence(), this));
+	QObject::connect( scrUserScriptActions["userScriptsUpdate"], SIGNAL(triggered()) , this, SLOT(updateUserScriptsMenu()) );
+	menuMgr->addMenuItemString("userScriptsUpdate", "UserScripts");
+
+	menuMgr->addMenuItemStringstoRememberedMenu("UserScripts", scrUserScriptActions);
+}
+
 void ScripterCore::FinishScriptRun()
 {
 	ScribusMainWindow* ScMW=ScCore->primaryMainWindow();
@@ -228,6 +281,12 @@ void ScripterCore::RecentScript(const QString& fn)
 	FinishScriptRun();
 }
 
+void ScripterCore::runUserScript(const QString& fn)
+{
+	slotRunScriptFile(fn);
+	FinishScriptRun();
+}
+
 void ScripterCore::slotRunScriptFile(const QString& fileName, bool inMainInterpreter)
 {
 	slotRunScriptFile(fileName, QStringList(), inMainInterpreter);
@@ -506,6 +565,64 @@ void ScripterCore::ReadPlugPrefs()
 	m_importAllNames = prefs->getBool("importall",true);
 	m_startupScript = prefs->get("startupscript", QString::null);
 	// and have the console window set up its position
+
+	m_userScriptsPaths = getUserScriptsPaths(prefs);
+	m_userScriptsEnabled = !m_userScriptsPaths.empty();
+	if (m_userScriptsEnabled)
+	{
+		m_userScripts = getUserScripts(m_userScriptsPaths);
+	}
+}
+
+QStringList ScripterCore::getUserScriptsPaths(PrefsContext* prefs)
+{
+	QStringList result{};
+
+	auto prefUserScriptsPath = prefs->getTable("userscriptspaths");
+	result.clear();
+	for (int i = 0; i < prefUserScriptsPath->getRowCount(); i++)
+	{
+		result.append(prefUserScriptsPath->get(i, 0));
+	}
+
+	return result;
+}
+
+QStringList ScripterCore::getUserScripts(QStringList paths)
+{
+	QStringList result{};
+	for (auto path: paths)
+	{
+		QDir dir{path};
+		if (!dir.exists())
+			continue;
+		// TODO: do we need to check for .PY?
+		for (auto scriptPath: dir.entryList(QStringList() << "*.py" << "*.PY", QDir::Files))
+		{
+
+				
+			result << dir.filePath(scriptPath);
+		}
+
+		QDirIterator dirIter(path, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
+		while(dirIter.hasNext())
+		{
+			QDir subPath{dirIter.next()};
+			// TODO: do we need to check for .PY?
+			QStringList scriptPaths{
+				subPath.filePath(subPath.dirName() + ".py"),
+				subPath.filePath("main.py")
+			};
+			for (auto scriptPath: scriptPaths)
+			{
+				if(QFileInfo(scriptPath).exists() && !QDir(scriptPath).exists())
+				{
+					result << scriptPath;
+				}
+			}
+		}
+	}
+	return result;
 }
 
 void ScripterCore::SavePlugPrefs()
@@ -592,6 +709,10 @@ void ScripterCore::languageChange()
 	menuMgr->setText("Scripter", QObject::tr("&Script"));
 	menuMgr->setText("ScribusScripts", QObject::tr("&Scribus Scripts"));
 	menuMgr->setText("RecentScripts", QObject::tr("&Recent Scripts"));
+	if (m_userScriptsEnabled)
+	{
+		menuMgr->setText("UserScripts", QObject::tr("S&cripts"));
+	}
 }
 
 bool ScripterCore::setupMainInterpreter()
@@ -635,6 +756,18 @@ void ScripterCore::updateSyntaxHighlighter()
 	pcon->updateSyntaxHighlighter();
 }
 
+void ScripterCore::updateUserScriptsPaths()
+{
+	PrefsContext* prefs = PrefsManager::instance()->prefsFile->getPluginContext("scriptplugin");
+	m_userScriptsPaths = getUserScriptsPaths(prefs);
+	m_userScripts = getUserScripts(m_userScriptsPaths);
+	m_userScriptsEnabled = !m_userScriptsPaths.empty();
+
+	addUserScriptsMenu();
+
+	rebuildUserScriptsMenu();
+}
+
 const QString & ScripterCore::startupScript() const
 {
 	return m_startupScript;
diff --git a/scribus/plugins/scriptplugin/scriptercore.h b/scribus/plugins/scriptplugin/scriptercore.h
index 8682a96adb..7505ec218b 100644
--- a/scribus/plugins/scriptplugin/scriptercore.h
+++ b/scribus/plugins/scriptplugin/scriptercore.h
@@ -33,11 +33,14 @@ class ScripterCore : public QObject
 	QString returnString;
 	/** @brief String representation of line of code to be passed to the Python interactive interpreter */
 	QString inValue;
+	QStringList getUserScriptsPaths() { return m_userScriptsPaths; }
+	QStringList getUserScriptsPaths(PrefsContext* prefs);
 
 public slots:
 	void runScriptDialog();
 	void StdScript(const QString& filebasename);
 	void RecentScript(const QString& fn);
+	void runUserScript(const QString& script);
 	void slotRunScriptFile(const QString& fileName, bool inMainInterpreter = false);
 	void slotRunScriptFile(const QString& fileName, QStringList arguments, bool inMainInterpreter = false);
 	void slotRunPythonScript(); // needed for running python script from CLI
@@ -62,6 +65,7 @@ public slots:
 	void setStartupScript(const QString& newScript);
 	void setExtensionsEnabled(bool enable);
 	void updateSyntaxHighlighter();
+	void updateUserScriptsPaths();
 
 protected:
 	// Private helper functions
@@ -78,9 +82,13 @@ public slots:
 	PythonConsole *pcon;
 	QStringList SavedRecentScripts;
 	QStringList RecentScripts;
+	QStringList m_userScriptsPaths;
+	bool m_userScriptsEnabled{false};
+	QStringList m_userScripts;
 	MenuManager *menuMgr;
 	QMap<QString, QPointer<ScrAction> > scrScripterActions;
 	QMap<QString, QPointer<ScrAction> > scrRecentScriptActions;
+	QMap<QString, QPointer<ScrAction> > scrUserScriptActions;
 
 	// Preferences
 	/** \brief pref: Enable access to main interpreter and 'extension scripts' */
@@ -89,6 +97,17 @@ public slots:
 	bool m_importAllNames;
 	/** \brief pref: Load this script on startup */
 	QString m_startupScript;
+private slots:
+	void updateUserScriptsMenu();
+private:
+	/** \brief Add the user menu script if it does not exist yet */
+	void addUserScriptsMenu();
+	/**
+	 * \brief List all the scripts in the paths' main directory and all
+	 * the main.py and subdirectory.py in the sub directories.
+	 */
+	QStringList getUserScripts(QStringList paths);
+	void rebuildUserScriptsMenu();
 };
 
 #endif
diff --git a/scribus/plugins/scriptplugin/scriptplugin.cpp b/scribus/plugins/scriptplugin/scriptplugin.cpp
index 87795e8e87..9a0f8c0a40 100644
--- a/scribus/plugins/scriptplugin/scriptplugin.cpp
+++ b/scribus/plugins/scriptplugin/scriptplugin.cpp
@@ -205,6 +205,7 @@ bool ScriptPlugin::newPrefsPanelWidget(QWidget* parent, PrefsPanel*& panel, QStr
 	panel = new ScripterPrefsGui(parent);
 	Q_CHECK_PTR(panel);
 	connect(panel, SIGNAL(prefsChanged()), scripterCore, SLOT(updateSyntaxHighlighter()));
+	connect(panel, SIGNAL(prefsUserScriptsPathsChanged()), scripterCore, SLOT(updateUserScriptsPaths()));
 	caption = tr("Scripter");
 	icon = IconManager::instance()->loadPixmap("python.png");
 	return true;
@@ -216,6 +217,7 @@ bool ScriptPlugin::newPrefsPanelWidget(QWidget* parent, Prefs_Pane*& panel, QStr
 	panel = new Prefs_Scripter(parent);
 	Q_CHECK_PTR(panel);
 	connect(panel, SIGNAL(prefsChanged()), scripterCore, SLOT(updateSyntaxHighlighter()));
+	connect(panel, SIGNAL(prefsUserScriptsPathsChanged()), scripterCore, SLOT(updateUserScriptsPaths()));
 	caption = tr("Scripter");
 	icon = IconManager::instance()->loadPixmap("python_16.png");
 	return true;
diff --git a/scribus/plugins/scriptplugin/scriptplugin.h b/scribus/plugins/scriptplugin/scriptplugin.h
index 6f6bdfce72..1983e39629 100644
--- a/scribus/plugins/scriptplugin/scriptplugin.h
+++ b/scribus/plugins/scriptplugin/scriptplugin.h
@@ -41,6 +41,8 @@ class PLUGIN_API ScriptPlugin : public ScPersistentPlugin
 
 		// Special features (none)
 		QByteArray pythonHome;
+		private slots:
+			void updateUserScriptsPaths();
 };
 
 extern "C" PLUGIN_API int scriptplugin_getPluginAPIVersion();
diff --git a/scribus/scraction.cpp b/scribus/scraction.cpp
index 9ff8ec0cb4..dddc62eb11 100644
--- a/scribus/scraction.cpp
+++ b/scribus/scraction.cpp
@@ -114,6 +114,8 @@ void ScrAction::triggeredToTriggeredData()
 		emit triggeredData(data().toString());
 	if (m_actionType==ScrAction::RecentScript)
 		emit triggeredData(data().toString());
+	if (m_actionType==ScrAction::UserScript)
+		emit triggeredData(data().toString());
 	if (m_actionType==ScrAction::UnicodeChar)
 		emit triggeredUnicodeShortcut(data().toInt());
 	if (m_actionType==ScrAction::Layer)
@@ -143,6 +145,8 @@ void ScrAction::toggledToToggledData(bool ison)
 			emit toggledData(ison, data().toString());
 		if (m_actionType==ScrAction::RecentScript)
 			emit toggledData(ison, text());
+		if (m_actionType==ScrAction::UserScript)
+			emit toggledData(ison, text());
 		if (m_actionType==ScrAction::Layer)
 			emit toggledData(ison, data().toInt());
 		// no toggle for UnicodeChar
diff --git a/scribus/scraction.h b/scribus/scraction.h
index 0d409124a0..b526779ea9 100644
--- a/scribus/scraction.h
+++ b/scribus/scraction.h
@@ -35,7 +35,7 @@ class SCRIBUS_API ScrAction : public QAction
 	Q_OBJECT
 
 public:
-	typedef enum {Normal, DataInt, DataDouble, DataQString, RecentFile, DLL, Window, RecentScript, UnicodeChar, Layer, ActionDLL, RecentPaste, ActionDLLSE } ActionType;
+	typedef enum {Normal, DataInt, DataDouble, DataQString, RecentFile, DLL, Window, RecentScript, UserScript, UnicodeChar, Layer, ActionDLL, RecentPaste, ActionDLLSE } ActionType;
 	
 	/*!
 		\author Craig Bradney
