diff --git a/apps/files_external/lib/AppInfo/Application.php b/apps/files_external/lib/AppInfo/Application.php
index 8eebc550d094f7e80c4f60827837b5eaee3dc600..01de6f1e40bfcda99a7dce47b33ee8ca8930a3da 100644
--- a/apps/files_external/lib/AppInfo/Application.php
+++ b/apps/files_external/lib/AppInfo/Application.php
@@ -29,6 +29,7 @@
 
 namespace OCA\Files_External\AppInfo;
 
+use OCA\Files_External\Config\UserPlaceholderHandler;
 use OCA\Files_External\Lib\Auth\PublicKey\RSAPrivateKey;
 use OCA\Files_External\Lib\Auth\SMB\KerberosAuth;
 use \OCP\AppFramework\App;
@@ -67,7 +68,12 @@ use OCP\Files\Config\IUserMountCache;
  */
 class Application extends App implements IBackendProvider, IAuthMechanismProvider {
 
-	public function __construct(array $urlParams = array()) {
+	/**
+	 * Application constructor.
+	 *
+	 * @throws \OCP\AppFramework\QueryException
+	 */
+	public function __construct(array $urlParams = []) {
 		parent::__construct('files_external', $urlParams);
 
 		$container = $this->getContainer();
@@ -76,15 +82,20 @@ class Application extends App implements IBackendProvider, IAuthMechanismProvide
 			return $c->getServer()->query('UserMountCache');
 		});
 
+		/** @var BackendService $backendService */
 		$backendService = $container->query(BackendService::class);
 		$backendService->registerBackendProvider($this);
 		$backendService->registerAuthMechanismProvider($this);
+		$backendService->registerConfigHandler('user', function() use ($container) {
+			return $container->query(UserPlaceholderHandler::class);
+		});
 
 		// force-load auth mechanisms since some will register hooks
 		// TODO: obsolete these and use the TokenProvider to get the user's password from the session
 		$this->getAuthMechanisms();
 
-		// app developers: do NOT depend on this! it will disappear with oC 9.0!
+		// don't remove this, as app loading order might be a side effect and
+		// querying the service from the server not reliable
 		\OC::$server->getEventDispatcher()->dispatch(
 			'OCA\\Files_External::loadAdditionalBackends'
 		);
diff --git a/apps/files_external/lib/Config/ConfigAdapter.php b/apps/files_external/lib/Config/ConfigAdapter.php
index 34e96df0441db682047578fe6486576d145568e4..8d9c7ea8d1bb51bfe99274c02a6fa9b86e794894 100644
--- a/apps/files_external/lib/Config/ConfigAdapter.php
+++ b/apps/files_external/lib/Config/ConfigAdapter.php
@@ -29,7 +29,6 @@ namespace OCA\Files_External\Config;
 use OC\Files\Storage\Wrapper\Availability;
 use OCA\Files_External\Migration\StorageMigrator;
 use OCP\Files\Storage;
-use OC\Files\Mount\MountPoint;
 use OCP\Files\Storage\IStorageFactory;
 use OCA\Files_External\Lib\PersonalMount;
 use OCP\Files\Config\IMountProvider;
@@ -73,12 +72,11 @@ class ConfigAdapter implements IMountProvider {
 	 *
 	 * @param StorageConfig $storage
 	 * @param IUser $user
+	 * @throws \OCP\AppFramework\QueryException
 	 */
 	private function prepareStorageConfig(StorageConfig &$storage, IUser $user) {
 		foreach ($storage->getBackendOptions() as $option => $value) {
-			$storage->setBackendOption($option, \OC_Mount_Config::setUserVars(
-				$user->getUID(), $value
-			));
+			$storage->setBackendOption($option, \OC_Mount_Config::substitutePlaceholdersInConfig($value));
 		}
 
 		$objectStore = $storage->getBackendOption('objectstore');
diff --git a/apps/files_external/lib/Config/IConfigHandler.php b/apps/files_external/lib/Config/IConfigHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..3e2ec32ba230fc62d9637077be99bd02be5ebf4e
--- /dev/null
+++ b/apps/files_external/lib/Config/IConfigHandler.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files_External\Config;
+
+/**
+ * Interface IConfigHandler
+ *
+ * @package OCA\Files_External\Config
+ * @since 16.0.0
+ */
+interface IConfigHandler {
+	/**
+	 * @param mixed $optionValue
+	 * @return mixed the same type as $optionValue
+	 * @since 16.0.0
+	 */
+	public function handle($optionValue);
+}
diff --git a/apps/files_external/lib/Config/SimpleSubstitutionTrait.php b/apps/files_external/lib/Config/SimpleSubstitutionTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..05bd529f1c146930f205cf1214f4194e1b8be0f6
--- /dev/null
+++ b/apps/files_external/lib/Config/SimpleSubstitutionTrait.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files_External\Config;
+
+/**
+ * Trait SimpleSubstitutionTrait
+ *
+ * @package OCA\Files_External\Config
+ * @since 16.0.0
+ */
+trait SimpleSubstitutionTrait {
+	/**
+	 * @var string the placeholder without $ prefix
+	 * @since 16.0.0
+	 */
+	private $placeholder;
+
+	/** @var string */
+	protected $sanitizedPlaceholder;
+
+	/**
+	 * @param mixed $optionValue
+	 * @param string $replacement
+	 * @return mixed
+	 * @since 16.0.0
+	 */
+	private function processInput($optionValue, string $replacement) {
+		$this->checkPlaceholder();
+		if (is_array($optionValue)) {
+			foreach ($optionValue as &$value) {
+				$value = $this->substituteIfString($value, $replacement);
+			}
+		} else {
+			$optionValue = $this->substituteIfString($optionValue, $replacement);
+		}
+		return $optionValue;
+	}
+
+	/**
+	 * @throws \RuntimeException
+	 */
+	protected function checkPlaceholder(): void {
+		$this->sanitizedPlaceholder = trim(strtolower($this->placeholder));
+		if(!(bool)\preg_match('/^[a-z0-9]*$/', $this->sanitizedPlaceholder)) {
+			throw new \RuntimeException(sprintf(
+				'Invalid placeholder %s, only [a-z0-9] are allowed', $this->sanitizedPlaceholder
+			));
+		}
+		if($this->sanitizedPlaceholder === '') {
+			throw new \RuntimeException('Invalid empty placeholder');
+		}
+	}
+
+	/**
+	 * @param mixed $value
+	 * @param string $replacement
+	 * @return mixed
+	 */
+	protected function substituteIfString($value, string $replacement) {
+		if(is_string($value)) {
+			return str_ireplace('$' . $this->sanitizedPlaceholder, $replacement, $value);
+		}
+		return $value;
+	}
+}
diff --git a/apps/files_external/lib/Config/UserPlaceholderHandler.php b/apps/files_external/lib/Config/UserPlaceholderHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..721d3bbe02ae0694ce90487652ddf2fc2cb107fe
--- /dev/null
+++ b/apps/files_external/lib/Config/UserPlaceholderHandler.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files_External\Config;
+
+use OCP\IUserSession;
+
+class UserPlaceholderHandler implements IConfigHandler {
+	use SimpleSubstitutionTrait;
+
+	/** @var IUserSession */
+	private $session;
+
+	public function __construct(IUserSession $session) {
+		$this->session = $session;
+		$this->placeholder = 'user';
+	}
+
+	/**
+	 * @param mixed $optionValue
+	 * @return mixed the same type as $optionValue
+	 * @since 16.0.0
+	 */
+	public function handle($optionValue) {
+		$user = $this->session->getUser();
+		if($user === null) {
+			return $optionValue;
+		}
+		$uid = $user->getUID();
+
+		return $this->processInput($optionValue, $uid);
+	}
+}
diff --git a/apps/files_external/lib/Lib/Storage/FTP.php b/apps/files_external/lib/Lib/Storage/FTP.php
index dc4ab9cb0e19ef885f481c87334345ff6ac3a779..db2ae9cf298bf9e3569a0eb73139442ed03f0f54 100644
--- a/apps/files_external/lib/Lib/Storage/FTP.php
+++ b/apps/files_external/lib/Lib/Storage/FTP.php
@@ -44,8 +44,6 @@ class FTP extends StreamWrapper{
 	private $secure;
 	private $root;
 
-	private static $tempFiles=array();
-
 	public function __construct($params) {
 		if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
 			$this->host=$params['host'];
diff --git a/apps/files_external/lib/Service/BackendService.php b/apps/files_external/lib/Service/BackendService.php
index bd6c525f319a0e10a5fa917b53b0b9bdb9c45a30..bd4d2bffa7b9cb8b1ac390a85295751b13d7dfaa 100644
--- a/apps/files_external/lib/Service/BackendService.php
+++ b/apps/files_external/lib/Service/BackendService.php
@@ -4,6 +4,7 @@
  *
  * @author Morris Jobke <hey@morrisjobke.de>
  * @author Robin McCorkell <robin@mccorkell.me.uk>
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  *
  * @license AGPL-3.0
  *
@@ -23,6 +24,7 @@
 
 namespace OCA\Files_External\Service;
 
+use OCA\Files_External\Config\IConfigHandler;
 use \OCP\IConfig;
 
 use \OCA\Files_External\Lib\Backend\Backend;
@@ -67,6 +69,11 @@ class BackendService {
 	/** @var IAuthMechanismProvider[] */
 	private $authMechanismProviders = [];
 
+	/** @var callable[] */
+	private $configHandlerLoaders = [];
+
+	private $configHandlers = [];
+
 	/**
 	 * @param IConfig $config
 	 */
@@ -280,4 +287,66 @@ class BackendService {
 	protected function isAllowedAuthMechanism(AuthMechanism $authMechanism) {
 		return true; // not implemented
 	}
+
+	/**
+	 * registers a configuration handler
+	 *
+	 * The function of the provided $placeholder is mostly to act a sorting
+	 * criteria, so longer placeholders are replaced first. This avoids
+	 * "$user" overwriting parts of "$userMail" and "$userLang", for example.
+	 * The provided value should not contain the $ prefix, only a-z0-9 are
+	 * allowed. Upper case letters are lower cased, the replacement is case-
+	 * insensitive.
+	 *
+	 * The configHandlerLoader should just instantiate the handler on demand.
+	 * For now all handlers are instantiated when a mount is loaded, independent
+	 * of whether the placeholder is present or not. This may change in future.
+	 *
+	 * @since 16.0.0
+	 */
+	public function registerConfigHandler(string $placeholder, callable $configHandlerLoader) {
+		$placeholder = trim(strtolower($placeholder));
+		if(!(bool)\preg_match('/^[a-z0-9]*$/', $placeholder)) {
+			throw new \RuntimeException(sprintf(
+				'Invalid placeholder %s, only [a-z0-9] are allowed', $placeholder
+			));
+		}
+		if($placeholder === '') {
+			throw new \RuntimeException('Invalid empty placeholder');
+		}
+		if(isset($this->configHandlerLoaders[$placeholder]) || isset($this->configHandlers[$placeholder])) {
+			throw new \RuntimeException(sprintf('A handler is already registered for %s', $placeholder));
+		}
+		$this->configHandlerLoaders[$placeholder] = $configHandlerLoader;
+	}
+
+	protected function loadConfigHandlers():void {
+		$newLoaded = false;
+		foreach ($this->configHandlerLoaders as $placeholder => $loader) {
+			$handler = $loader();
+			if(!$handler instanceof IConfigHandler) {
+				throw new \RuntimeException(sprintf(
+					'Handler for %s is not an instance of IConfigHandler', $placeholder
+				));
+			}
+			$this->configHandlers[$placeholder] = $handler;
+			$newLoaded = true;
+		}
+		$this->configHandlerLoaders = [];
+		if($newLoaded) {
+			// ensure those with longest placeholders come first,
+			// to avoid substring matches
+			uksort($this->configHandlers, function ($phA, $phB) {
+				return strlen($phB) <=> strlen($phA);
+			});
+		}
+	}
+
+	/**
+	 * @since 16.0.0
+	 */
+	public function getConfigHandlers() {
+		$this->loadConfigHandlers();
+		return $this->configHandlers;
+	}
 }
diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php
index 7fb118c2de5ed6246a9083e17be36d993a2960e5..65e8ae387bdfd42a8da6967e48cf5b4abccad0cd 100644
--- a/apps/files_external/lib/config.php
+++ b/apps/files_external/lib/config.php
@@ -3,6 +3,7 @@
  * @copyright Copyright (c) 2016, ownCloud, Inc.
  *
  * @author Andreas Fischer <bantu@owncloud.com>
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  * @author Bart Visscher <bartv@thisnet.nl>
  * @author Björn Schießle <bjoern@schiessle.org>
  * @author Frank Karlitschek <frank@karlitschek.de>
@@ -35,6 +36,8 @@
  *
  */
 
+use OCA\Files_External\Config\IConfigHandler;
+use OCA\Files_External\Config\UserPlaceholderHandler;
 use phpseclib\Crypt\AES;
 use \OCA\Files_External\AppInfo\Application;
 use \OCA\Files_External\Lib\Backend\LegacyBackend;
@@ -104,7 +107,7 @@ class OC_Mount_Config {
 			$mountPoint = '/'.$uid.'/files'.$storage->getMountPoint();
 			$mountEntry = self::prepareMountPointEntry($storage, false);
 			foreach ($mountEntry['options'] as &$option) {
-				$option = self::setUserVars($uid, $option);
+				$option = self::substitutePlaceholdersInConfig($option);
 			}
 			$mountPoints[$mountPoint] = $mountEntry;
 		}
@@ -113,7 +116,7 @@ class OC_Mount_Config {
 			$mountPoint = '/'.$uid.'/files'.$storage->getMountPoint();
 			$mountEntry = self::prepareMountPointEntry($storage, true);
 			foreach ($mountEntry['options'] as &$option) {
-				$option = self::setUserVars($uid, $option);
+				$option = self::substitutePlaceholdersInConfig($uid, $option);
 			}
 			$mountPoints[$mountPoint] = $mountEntry;
 		}
@@ -199,18 +202,26 @@ class OC_Mount_Config {
 	 * @param string $user user value
 	 * @param string|array $input
 	 * @return string
+	 * @deprecated use self::substitutePlaceholdersInConfig($input)
 	 */
 	public static function setUserVars($user, $input) {
-		if (is_array($input)) {
-			foreach ($input as &$value) {
-				if (is_string($value)) {
-					$value = str_replace('$user', $user, $value);
-				}
-			}
-		} else {
-			if (is_string($input)) {
-				$input = str_replace('$user', $user, $input);
-			}
+		$handler = self::$app->getContainer()->query(UserPlaceholderHandler::class);
+		return $handler->handle($input);
+	}
+
+	/**
+	 * @param mixed $input
+	 * @return mixed
+	 * @throws \OCP\AppFramework\QueryException
+	 * @since 16.0.0
+	 */
+	public static function substitutePlaceholdersInConfig($input) {
+		/** @var BackendService $backendService */
+		$backendService = self::$app->getContainer()->query(BackendService::class);
+		/** @var IConfigHandler[] $handlers */
+		$handlers = $backendService->getConfigHandlers();
+		foreach ($handlers as $handler) {
+			$input = $handler->handle($input);
 		}
 		return $input;
 	}
@@ -229,7 +240,21 @@ class OC_Mount_Config {
 			return StorageNotAvailableException::STATUS_SUCCESS;
 		}
 		foreach ($options as &$option) {
-			$option = self::setUserVars(OCP\User::getUser(), $option);
+			$option = self::substitutePlaceholdersInConfig($option);
+			if(!self::arePlaceholdersSubstituted($option)) {
+				\OC::$server->getLogger()->error(
+					'A placeholder was not substituted: {option} for mount type {class}',
+					[
+						'app' => 'files_external',
+						'option' => $option,
+						'class' => $class,
+					]
+				);
+				throw new StorageNotAvailableException(
+					'Mount configuration incomplete',
+					StorageNotAvailableException::STATUS_INCOMPLETE_CONF
+				);
+			}
 		}
 		if (class_exists($class)) {
 			try {
@@ -254,6 +279,22 @@ class OC_Mount_Config {
 		return StorageNotAvailableException::STATUS_ERROR;
 	}
 
+	public static function arePlaceholdersSubstituted($option):bool {
+		$result = true;
+		if(is_array($option)) {
+			foreach ($option as $optionItem) {
+				if(is_array($optionItem)) {
+					$result = $result && self::arePlaceholdersSubstituted($option);
+				}
+			}
+		} else if (is_string($option)) {
+			if (strpos($option, '$') !== false) {
+				$result = false;
+			}
+		}
+		return $result;
+	}
+
 	/**
 	 * Read the mount points in the config file into an array
 	 *
diff --git a/apps/files_external/tests/Config/UserPlaceholderHandlerTest.php b/apps/files_external/tests/Config/UserPlaceholderHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b6103c20b4613a563f2b7689f51084e5d57c3ac3
--- /dev/null
+++ b/apps/files_external/tests/Config/UserPlaceholderHandlerTest.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\files_external\tests\Config;
+
+use OCA\Files_External\Config\UserPlaceholderHandler;
+use OCP\IUser;
+use OCP\IUserSession;
+
+class UserPlaceholderHandlerTest extends \Test\TestCase {
+	/** @var IUser|\PHPUnit_Framework_MockObject_MockObject */
+	protected $user;
+
+	/** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
+	protected $session;
+
+	/** @var UserPlaceholderHandler */
+	protected $handler;
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->user = $this->createMock(IUser::class);
+		$this->user->expects($this->any())
+			->method('getUid')
+			->willReturn('alice');
+		$this->session = $this->createMock(IUserSession::class);
+
+		$this->handler = new UserPlaceholderHandler($this->session);
+	}
+
+	protected function setUser() {
+		$this->session->expects($this->any())
+			->method('getUser')
+			->willReturn($this->user);
+	}
+
+	public function optionProvider() {
+		return [
+			['/foo/bar/$user/foobar', '/foo/bar/alice/foobar'],
+			[['/foo/bar/$user/foobar'], ['/foo/bar/alice/foobar']],
+			[['/FOO/BAR/$USER/FOOBAR'], ['/FOO/BAR/alice/FOOBAR']],
+		];
+	}
+
+	/**
+	 * @dataProvider optionProvider
+	 */
+	public function testHandle($option, $expected) {
+		$this->setUser();
+		$this->assertSame($expected, $this->handler->handle($option));
+	}
+
+	/**
+	 * @dataProvider optionProvider
+	 */
+	public function testHandleNoUser($option) {
+		$this->assertSame($option, $this->handler->handle($option));
+	}
+
+}
diff --git a/apps/files_external/tests/Service/BackendServiceTest.php b/apps/files_external/tests/Service/BackendServiceTest.php
index e8a3181658ccd19512845e13e0121c20480f3d58..71990553e8dd5041b1ddb3bb8f61e019177bcfa7 100644
--- a/apps/files_external/tests/Service/BackendServiceTest.php
+++ b/apps/files_external/tests/Service/BackendServiceTest.php
@@ -23,31 +23,27 @@
  */
 namespace OCA\Files_External\Tests\Service;
 
+use OCA\Files_External\Config\IConfigHandler;
 use OCA\Files_External\Lib\Auth\AuthMechanism;
 use OCA\Files_External\Lib\Backend\Backend;
 use OCA\Files_External\Lib\Config\IAuthMechanismProvider;
 use OCA\Files_External\Lib\Config\IBackendProvider;
-use \OCA\Files_External\Service\BackendService;
+use OCA\Files_External\Service\BackendService;
 use OCP\IConfig;
-use OCP\IL10N;
 
 class BackendServiceTest extends \Test\TestCase {
 
-	/** @var \OCP\IConfig */
+	/** @var \OCP\IConfig|\PHPUnit_Framework_MockObject_MockObject */
 	protected $config;
 
-	/** @var \OCP\IL10N */
-	protected $l10n;
-
 	protected function setUp() {
 		$this->config = $this->createMock(IConfig::class);
-		$this->l10n = $this->createMock(IL10N::class);
 	}
 
 	/**
 	 * @param string $class
 	 *
-	 * @return \OCA\Files_External\Lib\Backend\Backend
+	 * @return \OCA\Files_External\Lib\Backend\Backend|\PHPUnit_Framework_MockObject_MockObject
 	 */
 	protected function getBackendMock($class) {
 		$backend = $this->getMockBuilder(Backend::class)
@@ -61,7 +57,7 @@ class BackendServiceTest extends \Test\TestCase {
 	/**
 	 * @param string $class
 	 *
-	 * @return \OCA\Files_External\Lib\Auth\AuthMechanism
+	 * @return \OCA\Files_External\Lib\Auth\AuthMechanism|\PHPUnit_Framework_MockObject_MockObject
 	 */
 	protected function getAuthMechanismMock($class) {
 		$backend = $this->getMockBuilder(AuthMechanism::class)
@@ -73,10 +69,11 @@ class BackendServiceTest extends \Test\TestCase {
 	}
 
 	public function testRegisterBackend() {
-		$service = new BackendService($this->config, $this->l10n);
+		$service = new BackendService($this->config);
 
 		$backend = $this->getBackendMock('\Foo\Bar');
 
+		/** @var \OCA\Files_External\Lib\Backend\Backend|\PHPUnit_Framework_MockObject_MockObject $backendAlias */
 		$backendAlias = $this->getMockBuilder(Backend::class)
 			->disableOriginalConstructor()
 			->getMock();
@@ -100,11 +97,12 @@ class BackendServiceTest extends \Test\TestCase {
 	}
 
 	public function testBackendProvider() {
-		$service = new BackendService($this->config, $this->l10n);
+		$service = new BackendService($this->config);
 
 		$backend1 = $this->getBackendMock('\Foo\Bar');
 		$backend2 = $this->getBackendMock('\Bar\Foo');
 
+		/** @var IBackendProvider|\PHPUnit_Framework_MockObject_MockObject $providerMock */
 		$providerMock = $this->createMock(IBackendProvider::class);
 		$providerMock->expects($this->once())
 			->method('getBackends')
@@ -118,11 +116,12 @@ class BackendServiceTest extends \Test\TestCase {
 	}
 
 	public function testAuthMechanismProvider() {
-		$service = new BackendService($this->config, $this->l10n);
+		$service = new BackendService($this->config);
 
 		$backend1 = $this->getAuthMechanismMock('\Foo\Bar');
 		$backend2 = $this->getAuthMechanismMock('\Bar\Foo');
 
+		/** @var IAuthMechanismProvider|\PHPUnit_Framework_MockObject_MockObject $providerMock */
 		$providerMock = $this->createMock(IAuthMechanismProvider::class);
 		$providerMock->expects($this->once())
 			->method('getAuthMechanisms')
@@ -136,18 +135,20 @@ class BackendServiceTest extends \Test\TestCase {
 	}
 
 	public function testMultipleBackendProviders() {
-		$service = new BackendService($this->config, $this->l10n);
+		$service = new BackendService($this->config);
 
 		$backend1a = $this->getBackendMock('\Foo\Bar');
 		$backend1b = $this->getBackendMock('\Bar\Foo');
 
 		$backend2 = $this->getBackendMock('\Dead\Beef');
 
+		/** @var IBackendProvider|\PHPUnit_Framework_MockObject_MockObject $provider1Mock */
 		$provider1Mock = $this->createMock(IBackendProvider::class);
 		$provider1Mock->expects($this->once())
 			->method('getBackends')
 			->willReturn([$backend1a, $backend1b]);
 		$service->registerBackendProvider($provider1Mock);
+		/** @var IBackendProvider|\PHPUnit_Framework_MockObject_MockObject $provider2Mock */
 		$provider2Mock = $this->createMock(IBackendProvider::class);
 		$provider2Mock->expects($this->once())
 			->method('getBackends')
@@ -169,7 +170,7 @@ class BackendServiceTest extends \Test\TestCase {
 				['files_external', 'user_mounting_backends', '', 'identifier:\User\Mount\Allowed,identifier_alias']
 			]));
 
-		$service = new BackendService($this->config, $this->l10n);
+		$service = new BackendService($this->config);
 
 		$backendAllowed = $this->getBackendMock('\User\Mount\Allowed');
 		$backendAllowed->expects($this->never())
@@ -193,7 +194,7 @@ class BackendServiceTest extends \Test\TestCase {
 	}
 
 	public function testGetAvailableBackends() {
-		$service = new BackendService($this->config, $this->l10n);
+		$service = new BackendService($this->config);
 
 		$backendAvailable = $this->getBackendMock('\Backend\Available');
 		$backendAvailable->expects($this->once())
@@ -216,5 +217,50 @@ class BackendServiceTest extends \Test\TestCase {
 		$this->assertArrayNotHasKey('identifier:\Backend\NotAvailable', $availableBackends);
 	}
 
+	public function invalidConfigPlaceholderProvider() {
+		return [
+			[['@user']],
+			[['$user']],
+			[['hællo']],
+			[['spa ce']],
+			[['yo\o']],
+			[['<script>…</script>']],
+			[['xxyoloxx', 'invÆlid']],
+			[['tautology', 'tautology']],
+			[['tautology2', 'TAUTOLOGY2']],
+		];
+	}
+
+	/**
+	 * @dataProvider invalidConfigPlaceholderProvider
+	 * @expectedException \RuntimeException
+	 */
+	public function testRegisterConfigHandlerInvalid(array $placeholders) {
+		$service = new BackendService($this->config);
+		$mock = $this->createMock(IConfigHandler::class);
+		$cb = function () use ($mock) { return $mock; };
+		foreach ($placeholders as $placeholder) {
+			$service->registerConfigHandler($placeholder, $cb);
+		}
+	}
+
+	public function testConfigHandlers() {
+		$service = new BackendService($this->config);
+		$mock = $this->createMock(IConfigHandler::class);
+		$mock->expects($this->exactly(3))
+			->method('handle');
+		$cb = function () use ($mock) { return $mock; };
+		$service->registerConfigHandler('one', $cb);
+		$service->registerConfigHandler('2', $cb);
+		$service->registerConfigHandler('Three', $cb);
+
+		/** @var IConfigHandler[] $handlers */
+		$handlers = $service->getConfigHandlers();
+
+		foreach ($handlers as $handler) {
+			$handler->handle('Something');
+		}
+	}
+
 }
 
diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php
index f371ef9efb362f1cd6a7e2a3a16bf57e01b059eb..5afd928301a969998a1ce37e1574ce1736f28421 100644
--- a/apps/user_ldap/appinfo/app.php
+++ b/apps/user_ldap/appinfo/app.php
@@ -34,6 +34,8 @@
 	return new OCA\User_LDAP\GroupPluginManager();
 });
 
+$app = new \OCA\User_LDAP\AppInfo\Application();
+
 $helper = new \OCA\User_LDAP\Helper(\OC::$server->getConfig());
 $configPrefixes = $helper->getServerConfigurationPrefixes(true);
 if(count($configPrefixes) > 0) {
@@ -67,6 +69,8 @@ if(count($configPrefixes) > 0) {
 	OC::$server->getEventDispatcher()->dispatch('OCA\\User_LDAP\\User\\User::postLDAPBackendAdded');
 
 	\OC::$server->getGroupManager()->addBackend($groupBackend);
+
+	$app->registerBackendDependents();
 }
 
 \OCP\Util::connectHook(
diff --git a/apps/user_ldap/composer/composer/autoload_classmap.php b/apps/user_ldap/composer/composer/autoload_classmap.php
index e25b7ee3126d0f0c299bb10f241fd36421ec4c53..fadbc701ec0366b0c03dc4ca0dc281df03091b89 100644
--- a/apps/user_ldap/composer/composer/autoload_classmap.php
+++ b/apps/user_ldap/composer/composer/autoload_classmap.php
@@ -23,12 +23,14 @@ return array(
     'OCA\\User_LDAP\\ConnectionFactory' => $baseDir . '/../lib/ConnectionFactory.php',
     'OCA\\User_LDAP\\Controller\\ConfigAPIController' => $baseDir . '/../lib/Controller/ConfigAPIController.php',
     'OCA\\User_LDAP\\Controller\\RenewPasswordController' => $baseDir . '/../lib/Controller/RenewPasswordController.php',
+    'OCA\\User_LDAP\\Exceptions\\AttributeNotSet' => $baseDir . '/../lib/Exceptions/AttributeNotSet.php',
     'OCA\\User_LDAP\\Exceptions\\ConstraintViolationException' => $baseDir . '/../lib/Exceptions/ConstraintViolationException.php',
     'OCA\\User_LDAP\\Exceptions\\NotOnLDAP' => $baseDir . '/../lib/Exceptions/NotOnLDAP.php',
     'OCA\\User_LDAP\\FilesystemHelper' => $baseDir . '/../lib/FilesystemHelper.php',
     'OCA\\User_LDAP\\GroupPluginManager' => $baseDir . '/../lib/GroupPluginManager.php',
     'OCA\\User_LDAP\\Group_LDAP' => $baseDir . '/../lib/Group_LDAP.php',
     'OCA\\User_LDAP\\Group_Proxy' => $baseDir . '/../lib/Group_Proxy.php',
+    'OCA\\User_LDAP\\Handler\\ExtStorageConfigHandler' => $baseDir . '/../lib/Handler/ExtStorageConfigHandler.php',
     'OCA\\User_LDAP\\Helper' => $baseDir . '/../lib/Helper.php',
     'OCA\\User_LDAP\\IGroupLDAP' => $baseDir . '/../lib/IGroupLDAP.php',
     'OCA\\User_LDAP\\ILDAPGroupPlugin' => $baseDir . '/../lib/ILDAPGroupPlugin.php',
diff --git a/apps/user_ldap/composer/composer/autoload_static.php b/apps/user_ldap/composer/composer/autoload_static.php
index 23819055be47db09ab593b8161c50ced5a731a28..d40df6e4836258839667e248fa57e80585270caa 100644
--- a/apps/user_ldap/composer/composer/autoload_static.php
+++ b/apps/user_ldap/composer/composer/autoload_static.php
@@ -38,12 +38,14 @@ class ComposerStaticInitUser_LDAP
         'OCA\\User_LDAP\\ConnectionFactory' => __DIR__ . '/..' . '/../lib/ConnectionFactory.php',
         'OCA\\User_LDAP\\Controller\\ConfigAPIController' => __DIR__ . '/..' . '/../lib/Controller/ConfigAPIController.php',
         'OCA\\User_LDAP\\Controller\\RenewPasswordController' => __DIR__ . '/..' . '/../lib/Controller/RenewPasswordController.php',
+        'OCA\\User_LDAP\\Exceptions\\AttributeNotSet' => __DIR__ . '/..' . '/../lib/Exceptions/AttributeNotSet.php',
         'OCA\\User_LDAP\\Exceptions\\ConstraintViolationException' => __DIR__ . '/..' . '/../lib/Exceptions/ConstraintViolationException.php',
         'OCA\\User_LDAP\\Exceptions\\NotOnLDAP' => __DIR__ . '/..' . '/../lib/Exceptions/NotOnLDAP.php',
         'OCA\\User_LDAP\\FilesystemHelper' => __DIR__ . '/..' . '/../lib/FilesystemHelper.php',
         'OCA\\User_LDAP\\GroupPluginManager' => __DIR__ . '/..' . '/../lib/GroupPluginManager.php',
         'OCA\\User_LDAP\\Group_LDAP' => __DIR__ . '/..' . '/../lib/Group_LDAP.php',
         'OCA\\User_LDAP\\Group_Proxy' => __DIR__ . '/..' . '/../lib/Group_Proxy.php',
+        'OCA\\User_LDAP\\Handler\\ExtStorageConfigHandler' => __DIR__ . '/..' . '/../lib/Handler/ExtStorageConfigHandler.php',
         'OCA\\User_LDAP\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php',
         'OCA\\User_LDAP\\IGroupLDAP' => __DIR__ . '/..' . '/../lib/IGroupLDAP.php',
         'OCA\\User_LDAP\\ILDAPGroupPlugin' => __DIR__ . '/..' . '/../lib/ILDAPGroupPlugin.php',
diff --git a/apps/user_ldap/js/wizard/wizardTabAdvanced.js b/apps/user_ldap/js/wizard/wizardTabAdvanced.js
index 9302104b1f62dbe750cc28a98476c815eb8dd279..1545147f64c8bdb0104bf7b53f53a6a1ba466595 100644
--- a/apps/user_ldap/js/wizard/wizardTabAdvanced.js
+++ b/apps/user_ldap/js/wizard/wizardTabAdvanced.js
@@ -120,7 +120,11 @@ OCA = OCA || {};
 				home_folder_naming_rule: {
 					$element: $('#home_folder_naming_rule'),
 					setMethod: 'setHomeFolderAttribute'
-				}
+				},
+				ldap_ext_storage_home_attribute: {
+					$element: $('#ldap_ext_storage_home_attribute'),
+					setMethod: 'setExternalStorageHomeAttribute'
+				},
 			};
 			this.setManagedItems(items);
 		},
@@ -326,6 +330,15 @@ OCA = OCA || {};
 			this.setElementValue(this.managedItems.ldap_email_attr.$element, attribute);
 		},
 
+		/**
+		 * sets the external storage home attribute
+		 *
+		 * @param {string} attribute
+		 */
+		setExternalStorageHomeAttribute: function(attribute) {
+			this.setElementValue(this.managedItems.ldap_ext_storage_home_attribute.$element, attribute);
+		},
+
 		/**
 		 * sets the quota attribute
 		 *
diff --git a/apps/user_ldap/lib/AppInfo/Application.php b/apps/user_ldap/lib/AppInfo/Application.php
index 59d7cdb49245e45934fea9461778071375f7b94c..7e0c35344882653217f8c98279558e490f3e16e2 100644
--- a/apps/user_ldap/lib/AppInfo/Application.php
+++ b/apps/user_ldap/lib/AppInfo/Application.php
@@ -23,7 +23,9 @@
 
 namespace OCA\User_LDAP\AppInfo;
 
+use OCA\Files_External\Service\BackendService;
 use OCA\User_LDAP\Controller\RenewPasswordController;
+use OCA\User_LDAP\Handler\ExtStorageConfigHandler;
 use OCA\User_LDAP\ILDAPWrapper;
 use OCA\User_LDAP\LDAP;
 use OCP\AppFramework\App;
@@ -57,4 +59,18 @@ class Application extends App {
 			return new LDAP();
 		});
 	}
+
+	public function registerBackendDependents() {
+		$container = $this->getContainer();
+
+		$container->getServer()->getEventDispatcher()->addListener(
+			'OCA\\Files_External::loadAdditionalBackends',
+			function() use ($container) {
+				$storagesBackendService = $container->query(BackendService::class);
+				$storagesBackendService->registerConfigHandler('home', function () use ($container) {
+					return $container->query(ExtStorageConfigHandler::class);
+				});
+			}
+		);
+	}
 }
diff --git a/apps/user_ldap/lib/Configuration.php b/apps/user_ldap/lib/Configuration.php
index c912d30b49b5932297f8710a6af360fbeb16c157..ee77702a090fd4c0bd17168fc232b89d9ffb42ce 100644
--- a/apps/user_ldap/lib/Configuration.php
+++ b/apps/user_ldap/lib/Configuration.php
@@ -106,6 +106,7 @@ class Configuration {
 		'turnOnPasswordChange' => false,
 		'ldapDynamicGroupMemberURL' => null,
 		'ldapDefaultPPolicyDN' => null,
+		'ldapExtStorageHomeAttribute' => null,
 	);
 
 	/**
@@ -477,6 +478,7 @@ class Configuration {
 			'ldap_dynamic_group_member_url'     => '',
 			'ldap_default_ppolicy_dn'           => '',
 			'ldap_user_avatar_rule'             => 'default',
+			'ldap_ext_storage_home_attribute'   => '',
 		);
 	}
 
@@ -537,6 +539,7 @@ class Configuration {
 			'ldap_experienced_admin'            => 'ldapExperiencedAdmin',
 			'ldap_dynamic_group_member_url'     => 'ldapDynamicGroupMemberURL',
 			'ldap_default_ppolicy_dn'           => 'ldapDefaultPPolicyDN',
+			'ldap_ext_storage_home_attribute'   => 'ldapExtStorageHomeAttribute',
 			'ldapIgnoreNamingRules'             => 'ldapIgnoreNamingRules',	// sysconfig
 		);
 		return $array;
diff --git a/apps/user_ldap/lib/Connection.php b/apps/user_ldap/lib/Connection.php
index 7becf311a2254b57ba29652fbba91db4bf1f251c..ba393dffc122a48b64196d77efddc71479e0ae61 100644
--- a/apps/user_ldap/lib/Connection.php
+++ b/apps/user_ldap/lib/Connection.php
@@ -60,6 +60,8 @@ use OCP\ILogger;
  * @property string ldapQuotaAttribute
  * @property string ldapQuotaDefault
  * @property string ldapEmailAttribute
+ * @property string ldapExtStorageHomeAttribute
+ * @property string homeFolderNamingRule
  */
 class Connection extends LDAPUtility {
 	private $ldapConnectionRes = null;
diff --git a/apps/user_ldap/lib/Exceptions/AttributeNotSet.php b/apps/user_ldap/lib/Exceptions/AttributeNotSet.php
new file mode 100644
index 0000000000000000000000000000000000000000..540b65c18209586f6f1a9d1af75a98be998f3286
--- /dev/null
+++ b/apps/user_ldap/lib/Exceptions/AttributeNotSet.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP\Exceptions;
+
+class AttributeNotSet extends \RuntimeException {}
diff --git a/apps/user_ldap/lib/Handler/ExtStorageConfigHandler.php b/apps/user_ldap/lib/Handler/ExtStorageConfigHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..98a3cc712630ade83585dd92b8645b169391c218
--- /dev/null
+++ b/apps/user_ldap/lib/Handler/ExtStorageConfigHandler.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP\Handler;
+
+use OCA\Files_External\Config\IConfigHandler;
+use OCA\Files_External\Config\SimpleSubstitutionTrait;
+use OCA\User_LDAP\User_Proxy;
+use OCP\IUserSession;
+
+class ExtStorageConfigHandler implements IConfigHandler {
+	use SimpleSubstitutionTrait;
+
+	/** @var IUserSession */
+	private $session;
+
+	public function __construct(IUserSession $session) {
+		$this->placeholder = 'home';
+		$this->session = $session;
+	}
+
+	/**
+	 * @param mixed $optionValue
+	 * @return mixed the same type as $optionValue
+	 * @since 16.0.0
+	 * @throws \Exception
+	 */
+	public function handle($optionValue) {
+		$user = $this->session->getUser();
+		if($user === null) {
+			return $optionValue;
+		}
+
+		$backend = $user->getBackend();
+		if(!$backend instanceof User_Proxy) {
+			return $optionValue;
+		}
+
+		$access = $backend->getLDAPAccess($user->getUID());
+		if(!$access) {
+			return $optionValue;
+		}
+
+		$attribute = $access->connection->ldapExtStorageHomeAttribute;
+		if(empty($attribute)) {
+			return $optionValue;
+		}
+
+		$ldapUser = $access->userManager->get($user->getUID());
+		$extHome = $ldapUser->getExtStorageHome();
+
+		return $this->processInput($optionValue, $extHome);
+	}
+}
diff --git a/apps/user_ldap/lib/User/Manager.php b/apps/user_ldap/lib/User/Manager.php
index 6185c0da45c736c1b474b128f265759fbce286f7..046b42551b67cdeaa136b207ef657b6866400a50 100644
--- a/apps/user_ldap/lib/User/Manager.php
+++ b/apps/user_ldap/lib/User/Manager.php
@@ -176,6 +176,7 @@ class Manager {
 			$this->access->getConnection()->ldapEmailAttribute,
 			$this->access->getConnection()->ldapUserDisplayName,
 			$this->access->getConnection()->ldapUserDisplayName2,
+			$this->access->getConnection()->ldapExtStorageHomeAttribute,
 		];
 
 		$homeRule = $this->access->getConnection()->homeFolderNamingRule;
diff --git a/apps/user_ldap/lib/User/User.php b/apps/user_ldap/lib/User/User.php
index 0d8f993746f916c5d9e7978abfec517066323dc5..d68d8b35d1407206f8f1b8f920ce107d301a3df6 100644
--- a/apps/user_ldap/lib/User/User.php
+++ b/apps/user_ldap/lib/User/User.php
@@ -32,6 +32,7 @@ namespace OCA\User_LDAP\User;
 
 use OCA\User_LDAP\Access;
 use OCA\User_LDAP\Connection;
+use OCA\User_LDAP\Exceptions\AttributeNotSet;
 use OCA\User_LDAP\FilesystemHelper;
 use OCA\User_LDAP\LogWrapper;
 use OCP\IAvatarManager;
@@ -244,6 +245,13 @@ class User {
 		}
 		$this->connection->writeToCache($cacheKey, $groups);
 
+		//external storage var
+		$attr = strtolower($this->connection->ldapExtStorageHomeAttribute);
+		if(isset($ldapEntry[$attr])) {
+			$this->updateExtStorageHome($ldapEntry[$attr][0]);
+		}
+		unset($attr);
+
 		//Avatar
 		/** @var Connection $connection */
 		$connection = $this->access->getConnection();
@@ -616,6 +624,47 @@ class User {
 		return false;
 	}
 
+	/**
+	 * @throws AttributeNotSet
+	 * @throws \OC\ServerNotAvailableException
+	 * @throws \OCP\PreConditionNotMetException
+	 */
+	public function getExtStorageHome():string {
+		$value = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', '');
+		if ($value !== '') {
+			return $value;
+		}
+
+		$value = $this->updateExtStorageHome();
+		if ($value !== '') {
+			return $value;
+		}
+
+		throw new AttributeNotSet(sprintf(
+			'external home storage attribute yield no value for %s', $this->getUsername()
+		));
+	}
+
+	/**
+	 * @throws \OCP\PreConditionNotMetException
+	 * @throws \OC\ServerNotAvailableException
+	 */
+	public function updateExtStorageHome(string $valueFromLDAP = null):string {
+		if($valueFromLDAP === null) {
+			$extHomeValues = $this->access->readAttribute($this->getDN(), $this->connection->ldapExtStorageHomeAttribute);
+		} else {
+			$extHomeValues = [$valueFromLDAP];
+		}
+		if ($extHomeValues && isset($extHomeValues[0])) {
+			$extHome = $extHomeValues[0];
+			$this->config->setUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', $extHome);
+			return $extHome;
+		} else {
+			$this->config->deleteUserValue($this->getUsername(), 'user_ldap', 'extStorageHome');
+			return '';
+		}
+	}
+
 	/**
 	 * called by a post_login hook to handle password expiry
 	 *
diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php
index 6d69d0aeb1596a351ea0ee64ac71de391b2329d2..7c41363c635180fad1b79620931fe15bf3e9de41 100644
--- a/apps/user_ldap/templates/settings.php
+++ b/apps/user_ldap/templates/settings.php
@@ -108,6 +108,7 @@ style('user_ldap', 'settings');
 				<p><label for="ldap_quota_def"><?php p($l->t('Quota Default'));?></label><input type="text" id="ldap_quota_def" name="ldap_quota_def" data-default="<?php p($_['ldap_quota_def_default']); ?>" title="<?php p($l->t('Override default quota for LDAP users who do not have a quota set in the Quota Field.'));?>" /></p>
 				<p><label for="ldap_email_attr"><?php p($l->t('Email Field'));?></label><input type="text" id="ldap_email_attr" name="ldap_email_attr" data-default="<?php p($_['ldap_email_attr_default']); ?>" title="<?php p($l->t('Set the user\'s email from their LDAP attribute. Leave it empty for default behaviour.'));?>" /></p>
 				<p><label for="home_folder_naming_rule"><?php p($l->t('User Home Folder Naming Rule'));?></label><input type="text" id="home_folder_naming_rule" name="home_folder_naming_rule" title="<?php p($l->t('Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute.'));?>" data-default="<?php p($_['home_folder_naming_rule_default']); ?>" /></p>
+				<p><label for="ldap_ext_storage_home_attribute"> <?php p($l->t('"$home" Placeholder Field')); ?></label><input type="text" id="ldap_ext_storage_home_attribute" name="ldap_ext_storage_home_attribute" title="<?php p($l->t('$home in an external storage configuration will replaced with the value of the specified attribute')); ?>" data-default="<?php p($_['ldap_ext_storage_home_attribute_default']); ?>"></p>
 			</div>
 		</div>
 		<?php print_unescaped($_['settingControls']); ?>
diff --git a/apps/user_ldap/tests/ConfigurationTest.php b/apps/user_ldap/tests/ConfigurationTest.php
index ab1312860fad6267aa9de9c849c03e3fc78e9275..6e45f1638674789ce0afb0e08aa1a51b1c20ab34 100644
--- a/apps/user_ldap/tests/ConfigurationTest.php
+++ b/apps/user_ldap/tests/ConfigurationTest.php
@@ -97,6 +97,8 @@ class ConfigurationTest extends \Test\TestCase {
 			'set avatar rule, default' => ['ldapUserAvatarRule', 'default', 'default'],
 			'set avatar rule, none' => ['ldapUserAvatarRule', 'none', 'none'],
 			'set avatar rule, data attribute' => ['ldapUserAvatarRule', 'data:jpegPhoto', 'data:jpegPhoto'],
+
+			'set external storage home attribute' => ['ldapExtStorageHomeAttribute', 'homePath', 'homePath'],
 		);
 	}
 
diff --git a/apps/user_ldap/tests/User/UserTest.php b/apps/user_ldap/tests/User/UserTest.php
index 6ff9defe47bcf54a248bb49253de0baa6a4de9a8..f99100789d8fd5111029d455aa75536009676271 100644
--- a/apps/user_ldap/tests/User/UserTest.php
+++ b/apps/user_ldap/tests/User/UserTest.php
@@ -789,6 +789,50 @@ class UserTest extends \Test\TestCase {
 		$this->user->update();
 	}
 
+	public function extStorageHomeDataProvider() {
+		return [
+			[ 'myFolder', null ],
+			[ '', null, false ],
+			[ 'myFolder', 'myFolder' ],
+		];
+	}
+
+	/**
+	 * @dataProvider extStorageHomeDataProvider
+	 */
+	public function testUpdateExtStorageHome(string $expected, string $valueFromLDAP = null, bool $isSet = true) {
+		if($valueFromLDAP === null) {
+			$this->connection->expects($this->once())
+				->method('__get')
+				->willReturnMap([
+					['ldapExtStorageHomeAttribute', 'homeDirectory'],
+				]);
+
+			$return = [];
+			if($isSet) {
+				$return[] = $expected;
+			}
+			$this->access->expects($this->once())
+				->method('readAttribute')
+				->with($this->dn, 'homeDirectory')
+				->willReturn($return);
+		}
+
+		if($expected !== '') {
+			$this->config->expects($this->once())
+				->method('setUserValue')
+				->with($this->uid, 'user_ldap', 'extStorageHome', $expected);
+		} else {
+			$this->config->expects($this->once())
+				->method('deleteUserValue')
+				->with($this->uid, 'user_ldap', 'extStorageHome');
+		}
+
+		$actual = $this->user->updateExtStorageHome($valueFromLDAP);
+		$this->assertSame($expected, $actual);
+
+	}
+
 	public function testUpdateNoRefresh() {
 		$this->config->expects($this->at(0))
 			->method('getUserValue')
@@ -867,15 +911,16 @@ class UserTest extends \Test\TestCase {
 	}
 
 	public function testProcessAttributes() {
-		$requiredMethods = array(
+		$requiredMethods = [
 			'markRefreshTime',
 			'updateQuota',
 			'updateEmail',
 			'composeAndStoreDisplayName',
 			'storeLDAPUserName',
 			'getHomePath',
-			'updateAvatar'
-		);
+			'updateAvatar',
+			'updateExtStorageHome',
+		];
 
 		/** @var User|\PHPUnit_Framework_MockObject_MockObject $userMock */
 		$userMock = $this->getMockBuilder(User::class)
@@ -914,6 +959,7 @@ class UserTest extends \Test\TestCase {
 			strtolower($this->connection->ldapQuotaAttribute) => ['4096'],
 			strtolower($this->connection->ldapEmailAttribute) => ['alice@wonderland.org'],
 			strtolower($this->connection->ldapUserDisplayName) => ['Aaaaalice'],
+			strtolower($this->connection->ldapExtStorageHomeAttribute) => ['homeDirectory'],
 			'uid' => [$this->uid],
 			'homedirectory' => ['Alice\'s Folder'],
 			'memberof' => ['cn=groupOne', 'cn=groupTwo'],