diff --git a/api/graphql/generated.go b/api/graphql/generated.go
index 3212874..9d7744a 100644
--- a/api/graphql/generated.go
+++ b/api/graphql/generated.go
@@ -129,6 +129,8 @@ type ComplexityRoot struct {
 		ShareAlbum                  func(childComplexity int, albumID int, expire *time.Time, password *string) int
 		ShareMedia                  func(childComplexity int, mediaID int, expire *time.Time, password *string) int
 		UpdateUser                  func(childComplexity int, id int, username *string, password *string, admin *bool) int
+		UserAddRootPath             func(childComplexity int, id int, rootPath string) int
+		UserRemoveRootAlbum         func(childComplexity int, userID int, albumID int) int
 	}
 
 	Notification struct {
@@ -246,6 +248,8 @@ type MutationResolver interface {
 	UpdateUser(ctx context.Context, id int, username *string, password *string, admin *bool) (*models.User, error)
 	CreateUser(ctx context.Context, username string, password *string, admin bool) (*models.User, error)
 	DeleteUser(ctx context.Context, id int) (*models.User, error)
+	UserAddRootPath(ctx context.Context, id int, rootPath string) (*models.Album, error)
+	UserRemoveRootAlbum(ctx context.Context, userID int, albumID int) (*models.Album, error)
 	SetPeriodicScanInterval(ctx context.Context, interval int) (int, error)
 	SetScannerConcurrentWorkers(ctx context.Context, workers int) (int, error)
 }
@@ -771,6 +775,30 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.Mutation.UpdateUser(childComplexity, args["id"].(int), args["username"].(*string), args["password"].(*string), args["admin"].(*bool)), true
 
+	case "Mutation.userAddRootPath":
+		if e.complexity.Mutation.UserAddRootPath == nil {
+			break
+		}
+
+		args, err := ec.field_Mutation_userAddRootPath_args(context.TODO(), rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Mutation.UserAddRootPath(childComplexity, args["id"].(int), args["rootPath"].(string)), true
+
+	case "Mutation.userRemoveRootAlbum":
+		if e.complexity.Mutation.UserRemoveRootAlbum == nil {
+			break
+		}
+
+		args, err := ec.field_Mutation_userRemoveRootAlbum_args(context.TODO(), rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Mutation.UserRemoveRootAlbum(childComplexity, args["userId"].(int), args["albumId"].(int)), true
+
 	case "Notification.content":
 		if e.complexity.Notification.Content == nil {
 			break
@@ -1372,6 +1400,10 @@ type Mutation {
   ): User @isAdmin
   deleteUser(id: ID!): User @isAdmin
 
+  "Add a root path from where to look for media for the given user"
+  userAddRootPath(id: ID!, rootPath: String!): Album @isAdmin
+  userRemoveRootAlbum(userId: ID!, albumId: ID!): Album @isAdmin
+
   """
   Set how often, in seconds, the server should automatically scan for new media,
   a value of 0 will disable periodic scans
@@ -1935,6 +1967,54 @@ func (ec *executionContext) field_Mutation_updateUser_args(ctx context.Context,
 	return args, nil
 }
 
+func (ec *executionContext) field_Mutation_userAddRootPath_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
+	var err error
+	args := map[string]interface{}{}
+	var arg0 int
+	if tmp, ok := rawArgs["id"]; ok {
+		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
+		arg0, err = ec.unmarshalNID2int(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["id"] = arg0
+	var arg1 string
+	if tmp, ok := rawArgs["rootPath"]; ok {
+		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("rootPath"))
+		arg1, err = ec.unmarshalNString2string(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["rootPath"] = arg1
+	return args, nil
+}
+
+func (ec *executionContext) field_Mutation_userRemoveRootAlbum_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
+	var err error
+	args := map[string]interface{}{}
+	var arg0 int
+	if tmp, ok := rawArgs["userId"]; ok {
+		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("userId"))
+		arg0, err = ec.unmarshalNID2int(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["userId"] = arg0
+	var arg1 int
+	if tmp, ok := rawArgs["albumId"]; ok {
+		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("albumId"))
+		arg1, err = ec.unmarshalNID2int(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["albumId"] = arg1
+	return args, nil
+}
+
 func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
 	var err error
 	args := map[string]interface{}{}
@@ -4236,6 +4316,124 @@ func (ec *executionContext) _Mutation_deleteUser(ctx context.Context, field grap
 	return ec.marshalOUser2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res)
 }
 
+func (ec *executionContext) _Mutation_userAddRootPath(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:     "Mutation",
+		Field:      field,
+		Args:       nil,
+		IsMethod:   true,
+		IsResolver: true,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	rawArgs := field.ArgumentMap(ec.Variables)
+	args, err := ec.field_Mutation_userAddRootPath_args(ctx, rawArgs)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	fc.Args = args
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		directive0 := func(rctx context.Context) (interface{}, error) {
+			ctx = rctx // use context from middleware stack in children
+			return ec.resolvers.Mutation().UserAddRootPath(rctx, args["id"].(int), args["rootPath"].(string))
+		}
+		directive1 := func(ctx context.Context) (interface{}, error) {
+			if ec.directives.IsAdmin == nil {
+				return nil, errors.New("directive isAdmin is not implemented")
+			}
+			return ec.directives.IsAdmin(ctx, nil, directive0)
+		}
+
+		tmp, err := directive1(rctx)
+		if err != nil {
+			return nil, graphql.ErrorOnPath(ctx, err)
+		}
+		if tmp == nil {
+			return nil, nil
+		}
+		if data, ok := tmp.(*models.Album); ok {
+			return data, nil
+		}
+		return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/photoview/photoview/api/graphql/models.Album`, tmp)
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*models.Album)
+	fc.Result = res
+	return ec.marshalOAlbum2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Mutation_userRemoveRootAlbum(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+	}()
+	fc := &graphql.FieldContext{
+		Object:     "Mutation",
+		Field:      field,
+		Args:       nil,
+		IsMethod:   true,
+		IsResolver: true,
+	}
+
+	ctx = graphql.WithFieldContext(ctx, fc)
+	rawArgs := field.ArgumentMap(ec.Variables)
+	args, err := ec.field_Mutation_userRemoveRootAlbum_args(ctx, rawArgs)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	fc.Args = args
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		directive0 := func(rctx context.Context) (interface{}, error) {
+			ctx = rctx // use context from middleware stack in children
+			return ec.resolvers.Mutation().UserRemoveRootAlbum(rctx, args["userId"].(int), args["albumId"].(int))
+		}
+		directive1 := func(ctx context.Context) (interface{}, error) {
+			if ec.directives.IsAdmin == nil {
+				return nil, errors.New("directive isAdmin is not implemented")
+			}
+			return ec.directives.IsAdmin(ctx, nil, directive0)
+		}
+
+		tmp, err := directive1(rctx)
+		if err != nil {
+			return nil, graphql.ErrorOnPath(ctx, err)
+		}
+		if tmp == nil {
+			return nil, nil
+		}
+		if data, ok := tmp.(*models.Album); ok {
+			return data, nil
+		}
+		return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/photoview/photoview/api/graphql/models.Album`, tmp)
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*models.Album)
+	fc.Result = res
+	return ec.marshalOAlbum2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res)
+}
+
 func (ec *executionContext) _Mutation_setPeriodicScanInterval(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
 	defer func() {
 		if r := recover(); r != nil {
@@ -8008,6 +8206,10 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
 			out.Values[i] = ec._Mutation_createUser(ctx, field)
 		case "deleteUser":
 			out.Values[i] = ec._Mutation_deleteUser(ctx, field)
+		case "userAddRootPath":
+			out.Values[i] = ec._Mutation_userAddRootPath(ctx, field)
+		case "userRemoveRootAlbum":
+			out.Values[i] = ec._Mutation_userRemoveRootAlbum(ctx, field)
 		case "setPeriodicScanInterval":
 			out.Values[i] = ec._Mutation_setPeriodicScanInterval(ctx, field)
 			if out.Values[i] == graphql.Null {
diff --git a/api/graphql/resolvers/user.go b/api/graphql/resolvers/user.go
index 6502988..ceb378e 100644
--- a/api/graphql/resolvers/user.go
+++ b/api/graphql/resolvers/user.go
@@ -217,3 +217,39 @@ func (r *mutationResolver) DeleteUser(ctx context.Context, id int) (*models.User
 
 	return &user, nil
 }
+
+func (r *mutationResolver) UserAddRootPath(ctx context.Context, id int, rootPath string) (*models.Album, error) {
+
+	var user models.User
+	if err := r.Database.First(&user, id).Error; err != nil {
+		return nil, err
+	}
+
+	// TODO: Check if path exists and that user does not already own rootPath, directly or indirectly
+
+	newAlbum, err := scanner.NewRootAlbum(r.Database, rootPath, &user)
+	if err != nil {
+		return nil, err
+	}
+
+	return newAlbum, nil
+}
+
+func (r *mutationResolver) UserRemoveRootAlbum(ctx context.Context, userID int, albumID int) (*models.Album, error) {
+
+	var album models.Album
+	if err := r.Database.First(&album, albumID).Error; err != nil {
+		return nil, err
+	}
+
+	result := r.Database.Exec("DELETE FROM user_albums WHERE album_id = ? AND user_id = ?", albumID, userID)
+	if result.Error != nil {
+		return nil, result.Error
+	}
+
+	if result.RowsAffected == 0 {
+		return nil, errors.New("No relation deleted")
+	}
+
+	return &album, nil
+}
diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql
index f4dcf52..c606cd6 100644
--- a/api/graphql/schema.graphql
+++ b/api/graphql/schema.graphql
@@ -95,6 +95,10 @@ type Mutation {
   ): User @isAdmin
   deleteUser(id: ID!): User @isAdmin
 
+  "Add a root path from where to look for media for the given user"
+  userAddRootPath(id: ID!, rootPath: String!): Album @isAdmin
+  userRemoveRootAlbum(userId: ID!, albumId: ID!): Album @isAdmin
+
   """
   Set how often, in seconds, the server should automatically scan for new media,
   a value of 0 will disable periodic scans
diff --git a/ui/src/Pages/SettingsPage/Users/EditUserRow.js b/ui/src/Pages/SettingsPage/Users/EditUserRow.js
index ac21e93..7865f62 100644
--- a/ui/src/Pages/SettingsPage/Users/EditUserRow.js
+++ b/ui/src/Pages/SettingsPage/Users/EditUserRow.js
@@ -1,84 +1,8 @@
 import React from 'react'
-import PropTypes from 'prop-types'
-import styled from 'styled-components'
-import { Button, Checkbox, Icon, Input, Table } from 'semantic-ui-react'
+import { Button, Checkbox, Input, Table } from 'semantic-ui-react'
+import { EditRootPaths } from './EditUserRowRootPaths'
 import { UserRowProps } from './UserRow'
 
-const RootPathListItem = styled.li`
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-`
-
-const EditRootPath = ({ filePath, removePath }) => (
-  <RootPathListItem>
-    <span>{filePath}</span>
-    <Button negative onClick={() => removePath()}>
-      <Icon name="remove" />
-      Remove
-    </Button>
-  </RootPathListItem>
-)
-
-EditRootPath.propTypes = {
-  filePath: PropTypes.string.isRequired,
-  removePath: PropTypes.func.isRequired,
-}
-
-const NewRootPathInput = styled(Input)`
-  width: 100%;
-  margin-top: 24px;
-`
-
-const EditNewRootPath = ({ state, updateInput }) => (
-  <li>
-    <NewRootPathInput
-      style={{ width: '100%' }}
-      value={state.rootPath}
-      onChange={e => updateInput(e, 'rootPath')}
-      action={{
-        positive: true,
-        icon: 'add',
-        content: 'Add',
-      }}
-    />
-  </li>
-)
-
-EditNewRootPath.propTypes = {
-  state: PropTypes.object.isRequired,
-  updateInput: PropTypes.func.isRequired,
-}
-
-const RootPathList = styled.ul`
-  margin: 0;
-  padding: 0;
-  list-style: none;
-`
-
-const EditRootPaths = ({ user, state, updateInput }) => {
-  const editRows = user.rootAlbums.map(album => (
-    <EditRootPath
-      key={album.id}
-      filePath={album.filePath}
-      removePath={() => {}}
-    />
-  ))
-
-  return (
-    <RootPathList>
-      {editRows}
-      <EditNewRootPath state={state} updateInput={updateInput} />
-    </RootPathList>
-  )
-}
-
-EditRootPaths.propTypes = {
-  updateInput: PropTypes.func.isRequired,
-  user: PropTypes.object.isRequired,
-  state: PropTypes.object.isRequired,
-}
-
 const EditUserRow = ({
   user,
   state,
@@ -104,17 +28,17 @@ const EditUserRow = ({
         />
       </Table.Cell>
       <Table.Cell>
-        <EditRootPaths user={user} state={state} updateInput={updateInput} />
+        <EditRootPaths user={user} />
       </Table.Cell>
       <Table.Cell>
         <Checkbox
           toggle
           checked={state.admin}
           onChange={(_, data) => {
-            setState({
+            setState(state => ({
               ...state,
               admin: data.checked,
-            })
+            }))
           }}
         />
       </Table.Cell>
@@ -123,9 +47,9 @@ const EditUserRow = ({
           <Button
             negative
             onClick={() =>
-              setState({
+              setState(state => ({
                 ...state.oldState,
-              })
+              }))
             }
           >
             Cancel
diff --git a/ui/src/Pages/SettingsPage/Users/EditUserRowRootPaths.js b/ui/src/Pages/SettingsPage/Users/EditUserRowRootPaths.js
new file mode 100644
index 0000000..9fa9705
--- /dev/null
+++ b/ui/src/Pages/SettingsPage/Users/EditUserRowRootPaths.js
@@ -0,0 +1,134 @@
+import PropTypes from 'prop-types'
+import React, { useState } from 'react'
+import { gql, useMutation } from '@apollo/client'
+import { Button, Icon, Input } from 'semantic-ui-react'
+import styled from 'styled-components'
+import { USERS_QUERY } from './UsersTable'
+
+const userAddRootPathMutation = gql`
+  mutation userAddRootPath($id: ID!, $rootPath: String!) {
+    userAddRootPath(id: $id, rootPath: $rootPath) {
+      id
+    }
+  }
+`
+
+const userRemoveAlbumPathMutation = gql`
+  mutation userRemoveAlbumPathMutation($userId: ID!, $albumId: ID!) {
+    userRemoveRootAlbum(userId: $userId, albumId: $albumId) {
+      id
+    }
+  }
+`
+
+const RootPathListItem = styled.li`
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+`
+
+const EditRootPath = ({ album, user }) => {
+  const [removeAlbumPath, { loading }] = useMutation(
+    userRemoveAlbumPathMutation,
+    {
+      refetchQueries: [
+        {
+          query: USERS_QUERY,
+        },
+      ],
+    }
+  )
+
+  return (
+    <RootPathListItem>
+      <span>{album.filePath}</span>
+      <Button
+        negative
+        disabled={loading}
+        onClick={() =>
+          removeAlbumPath({
+            variables: {
+              userId: user.id,
+              albumId: album.id,
+            },
+          })
+        }
+      >
+        <Icon name="remove" />
+        Remove
+      </Button>
+    </RootPathListItem>
+  )
+}
+
+EditRootPath.propTypes = {
+  album: PropTypes.object.isRequired,
+  user: PropTypes.object.isRequired,
+}
+
+const NewRootPathInput = styled(Input)`
+  width: 100%;
+  margin-top: 24px;
+`
+
+const EditNewRootPath = ({ userID }) => {
+  const [value, setValue] = useState('')
+  const [addRootPath, { loading }] = useMutation(userAddRootPathMutation, {
+    refetchQueries: [
+      {
+        query: USERS_QUERY,
+      },
+    ],
+  })
+
+  return (
+    <li>
+      <NewRootPathInput
+        style={{ width: '100%' }}
+        value={value}
+        onChange={e => setValue(e.target.value)}
+        disabled={loading}
+        action={{
+          positive: true,
+          icon: 'add',
+          content: 'Add',
+          onClick: () => {
+            addRootPath({
+              variables: {
+                id: userID,
+                rootPath: value,
+              },
+            })
+          },
+        }}
+      />
+    </li>
+  )
+}
+
+EditNewRootPath.propTypes = {
+  userID: PropTypes.string.isRequired,
+}
+
+const RootPathList = styled.ul`
+  margin: 0;
+  padding: 0;
+  list-style: none;
+`
+
+export const EditRootPaths = ({ user }) => {
+  const editRows = user.rootAlbums.map(album => (
+    <EditRootPath key={album.id} album={album} user={user} />
+  ))
+
+  return (
+    <RootPathList>
+      {editRows}
+      <EditNewRootPath userID={user.id} />
+    </RootPathList>
+  )
+}
+
+EditRootPaths.propTypes = {
+  user: PropTypes.object.isRequired,
+}
diff --git a/ui/src/Pages/SettingsPage/Users/UserRow.js b/ui/src/Pages/SettingsPage/Users/UserRow.js
index 7a19b14..427defe 100644
--- a/ui/src/Pages/SettingsPage/Users/UserRow.js
+++ b/ui/src/Pages/SettingsPage/Users/UserRow.js
@@ -46,6 +46,7 @@ const UserRow = ({ user, refetchUsers }) => {
   const [state, setState] = useState({
     ...user,
     editing: false,
+    newRootPath: '',
   })
 
   const [showConfirmDelete, setConfirmDelete] = useState(false)
diff --git a/ui/src/Pages/SettingsPage/Users/UsersTable.js b/ui/src/Pages/SettingsPage/Users/UsersTable.js
index fa628d9..279446b 100644
--- a/ui/src/Pages/SettingsPage/Users/UsersTable.js
+++ b/ui/src/Pages/SettingsPage/Users/UsersTable.js
@@ -6,7 +6,7 @@ import UserRow from './UserRow'
 import AddUserRow from './AddUserRow'
 import { SectionTitle } from '../SettingsPage'
 
-const USERS_QUERY = gql`
+export const USERS_QUERY = gql`
   query settingsUsersQuery {
     user {
       id
diff --git a/ui/src/Pages/SettingsPage/Users/ViewUserRow.js b/ui/src/Pages/SettingsPage/Users/ViewUserRow.js
index d90432a..17053fe 100644
--- a/ui/src/Pages/SettingsPage/Users/ViewUserRow.js
+++ b/ui/src/Pages/SettingsPage/Users/ViewUserRow.js
@@ -12,7 +12,7 @@ const PathList = styled.ul`
 
 const ViewUserRow = ({
   user,
-  state,
+  // state,
   setState,
   scanUser,
   deleteUser,
@@ -41,7 +41,7 @@ const ViewUserRow = ({
         <Button.Group>
           <Button
             onClick={() => {
-              setState({ ...state, editing: true, oldState: state })
+              setState(state => ({ ...state, editing: true, oldState: state }))
             }}
           >
             <Icon name="edit" />
