diff --git a/config.sample.js b/config.sample.js
index 7d6a513..d860436 100644
--- a/config.sample.js
+++ b/config.sample.js
@@ -655,6 +655,15 @@ module.exports = {
     }
   },
 
+  /*
+    Dashboard config.
+  */
+  dashboard: {
+    uploadsPerPage: 24,
+    albumsPerPage: 10,
+    usersPerPage: 10
+  },
+
   /*
     Cloudflare support.
   */
diff --git a/controllers/albumsController.js b/controllers/albumsController.js
index 39b2733..2434830 100644
--- a/controllers/albumsController.js
+++ b/controllers/albumsController.js
@@ -20,8 +20,14 @@ const self = {
   onHold: new Set() // temporarily held random album identifiers
 }
 
+/** Preferences */
+
 const homeDomain = utils.conf.homeDomain || utils.conf.domain
 
+const albumsPerPage = config.dashboard
+  ? Math.max(Math.min(config.dashboard.albumsPerPage || 0, 100), 1)
+  : 25
+
 const zipMaxTotalSize = parseInt(config.cloudflare.zipMaxTotalSize)
 const zipMaxTotalSizeBytes = zipMaxTotalSize * 1e6
 const zipOptions = config.uploads.jsZipOptions
@@ -112,41 +118,48 @@ self.list = async (req, res) => {
     }
   }
 
+  // Base result object
+  const result = { success: true, albums: [], albumsPerPage, count: 0, homeDomain }
+
   // Query albums count for pagination
-  const count = await utils.db.table('albums')
+  result.count = await utils.db.table('albums')
     .where(filter)
     .count('id as count')
     .then(rows => rows[0].count)
-  if (!count) {
-    return res.json({ success: true, albums: [], count })
+  if (!result.count) {
+    return res.json(result)
   }
 
   const fields = ['id', 'name']
 
-  let albums
   if (simple) {
-    albums = await utils.db.table('albums')
+    result.albums = await utils.db.table('albums')
       .where(filter)
       .select(fields)
 
-    return res.json({ success: true, albums, count })
-  } else {
-    let offset = req.path_parameters && Number(req.path_parameters.page)
-    if (isNaN(offset)) offset = 0
-    else if (offset < 0) offset = Math.max(0, Math.ceil(count / 25) + offset)
-
-    fields.push('identifier', 'enabled', 'timestamp', 'editedAt', 'zipGeneratedAt', 'download', 'public', 'description')
-    if (all) fields.push('userid')
-
-    albums = await utils.db.table('albums')
-      .where(filter)
-      .limit(25)
-      .offset(25 * offset)
-      .select(fields)
+    return res.json(result)
   }
 
+  let offset = req.path_parameters && Number(req.path_parameters.page)
+  if (isNaN(offset)) {
+    offset = 0
+  } else if (offset < 0) {
+    offset = Math.max(0, Math.ceil(result.count / albumsPerPage) + offset)
+  }
+
+  fields.push('identifier', 'enabled', 'timestamp', 'editedAt', 'zipGeneratedAt', 'download', 'public', 'description')
+  if (all) {
+    fields.push('userid')
+  }
+
+  result.albums = await utils.db.table('albums')
+    .where(filter)
+    .limit(albumsPerPage)
+    .offset(albumsPerPage * offset)
+    .select(fields)
+
   const albumids = {}
-  for (const album of albums) {
+  for (const album of result.albums) {
     album.download = album.download !== 0
     album.public = album.public !== 0
     album.uploads = 0
@@ -171,7 +184,7 @@ self.list = async (req, res) => {
     }
   }
 
-  await Promise.all(albums.map(album => getAlbumZipSize(album)))
+  await Promise.all(result.albums.map(album => getAlbumZipSize(album)))
 
   const uploads = await utils.db.table('files')
     .whereIn('albumid', Object.keys(albumids))
@@ -186,19 +199,17 @@ self.list = async (req, res) => {
 
   // If we are not listing all albums, send response
   if (!all) {
-    return res.json({ success: true, albums, count, homeDomain })
+    return res.json(result)
   }
 
   // Otherwise proceed to querying usernames
-  const userids = albums
+  const userids = result.albums
     .map(album => album.userid)
-    .filter((v, i, a) => {
-      return v !== null && v !== undefined && v !== '' && a.indexOf(v) === i
-    })
+    .filter(utils.filterUniquifySqlArray)
 
   // If there are no albums attached to a registered user, send response
   if (!userids.length) {
-    return res.json({ success: true, albums, count, homeDomain })
+    return res.json(result)
   }
 
   // Query usernames of user IDs from currently selected files
@@ -206,12 +217,13 @@ self.list = async (req, res) => {
     .whereIn('id', userids)
     .select('id', 'username')
 
-  const users = {}
+  result.users = {}
+
   for (const user of usersTable) {
-    users[user.id] = user.username
+    result.users[user.id] = user.username
   }
 
-  return res.json({ success: true, albums, count, users, homeDomain })
+  return res.json(result)
 }
 
 self.create = async (req, res) => {
diff --git a/controllers/authController.js b/controllers/authController.js
index cece374..72c15ac 100644
--- a/controllers/authController.js
+++ b/controllers/authController.js
@@ -26,9 +26,15 @@ const self = {
   }
 }
 
+/** Preferences */
+
 // https://github.com/kelektiv/node.bcrypt.js/tree/v5.0.1#a-note-on-rounds
 const saltRounds = 10
 
+const usersPerPage = config.dashboard
+  ? Math.max(Math.min(config.dashboard.usersPerPage || 0, 100), 1)
+  : 25
+
 self.verify = async (req, res) => {
   utils.assertRequestType(req, 'application/json')
 
@@ -360,24 +366,30 @@ self.listUsers = async (req, res) => {
   const isadmin = perms.is(user, 'admin')
   if (!isadmin) throw new ClientError('', { statusCode: 403 })
 
-  const count = await utils.db.table('users')
+  // Base result object
+  const result = { success: true, users: [], usersPerPage, count: 0 }
+
+  result.count = await utils.db.table('users')
     .count('id as count')
     .then(rows => rows[0].count)
-  if (!count) {
-    return res.json({ success: true, users: [], count })
+  if (!result.count) {
+    return res.json(result)
   }
 
   let offset = req.path_parameters && Number(req.path_parameters.page)
-  if (isNaN(offset)) offset = 0
-  else if (offset < 0) offset = Math.max(0, Math.ceil(count / 25) + offset)
+  if (isNaN(offset)) {
+    offset = 0
+  } else if (offset < 0) {
+    offset = Math.max(0, Math.ceil(result.count / usersPerPage) + offset)
+  }
 
-  const users = await utils.db.table('users')
-    .limit(25)
-    .offset(25 * offset)
+  result.users = await utils.db.table('users')
+    .limit(usersPerPage)
+    .offset(usersPerPage * offset)
     .select('id', 'username', 'enabled', 'timestamp', 'permission', 'registration')
 
   const pointers = {}
-  for (const user of users) {
+  for (const user of result.users) {
     user.groups = perms.mapPermissions(user)
     delete user.permission
     user.uploads = 0
@@ -394,7 +406,7 @@ self.listUsers = async (req, res) => {
     pointers[upload.userid].usage += parseInt(upload.size)
   }
 
-  return res.json({ success: true, users, count })
+  return res.json(result)
 }
 
 module.exports = self
diff --git a/controllers/uploadController.js b/controllers/uploadController.js
index ad24ebf..5eea7de 100644
--- a/controllers/uploadController.js
+++ b/controllers/uploadController.js
@@ -62,6 +62,10 @@ const enableHashing = config.uploads.hash === undefined
 const queryDatabaseForIdentifierMatch = config.uploads.queryDatabaseForIdentifierMatch ||
   config.uploads.queryDbForFileCollisions // old config name for identical behavior
 
+const uploadsPerPage = config.dashboard
+  ? Math.max(Math.min(config.dashboard.uploadsPerPage || 0, 100), 1)
+  : 25
+
 /** Chunks helper class & function **/
 
 class ChunksData {
@@ -1686,18 +1690,24 @@ self.list = async (req, res) => {
     })
   }
 
+  // Base result object
+  const result = { success: true, files: [], uploadsPerPage, count: 0, basedomain }
+
   // Query uploads count for pagination
-  const count = await utils.db.table('files')
+  result.count = await utils.db.table('files')
     .where(filter)
     .count('id as count')
     .then(rows => rows[0].count)
-  if (!count) {
-    return res.json({ success: true, files: [], count })
+  if (!result.count) {
+    return res.json(result)
   }
 
   let offset = req.path_parameters && Number(req.path_parameters.page)
-  if (isNaN(offset)) offset = 0
-  else if (offset < 0) offset = Math.max(0, Math.ceil(count / 25) + offset)
+  if (isNaN(offset)) {
+    offset = 0
+  } else if (offset < 0) {
+    offset = Math.max(0, Math.ceil(result.count / uploadsPerPage) + offset)
+  }
 
   const columns = ['id', 'name', 'original', 'userid', 'size', 'timestamp']
   if (utils.retentions.enabled) columns.push('expirydate')
@@ -1724,33 +1734,33 @@ self.list = async (req, res) => {
     orderByRaw = '`id` desc'
   }
 
-  const files = await utils.db.table('files')
+  result.files = await utils.db.table('files')
     .where(filter)
     .orderByRaw(orderByRaw)
-    .limit(25)
-    .offset(25 * offset)
+    .limit(uploadsPerPage)
+    .offset(uploadsPerPage * offset)
     .select(columns)
 
-  if (!files.length) {
-    return res.json({ success: true, files, count, basedomain })
+  if (!result.files.length) {
+    return res.json(result)
   }
 
-  for (const file of files) {
+  for (const file of result.files) {
     file.extname = utils.extname(file.name)
     if (utils.mayGenerateThumb(file.extname)) {
       file.thumb = `thumbs/${file.name.slice(0, -file.extname.length)}.png`
     }
   }
 
+  result.albums = {}
+
   // If we queried albumid, query album names
-  let albums = {}
   if (columns.includes('albumid')) {
-    const albumids = files
+    const albumids = result.files
       .map(file => file.albumid)
-      .filter((v, i, a) => {
-        return v !== null && v !== undefined && v !== '' && a.indexOf(v) === i
-      })
-    albums = await utils.db.table('albums')
+      .filter(utils.filterUniquifySqlArray)
+
+    result.albums = await utils.db.table('albums')
       .whereIn('id', albumids)
       .where('enabled', 1)
       .select('id', 'name')
@@ -1766,21 +1776,18 @@ self.list = async (req, res) => {
 
   // If we are not listing all uploads, send response
   if (!all) {
-    return res.json({ success: true, files, count, albums, basedomain })
+    return res.json(result)
   }
-
   // Otherwise proceed to querying usernames
   let usersTable = filterObj.uploaders
   if (!usersTable.length) {
-    const userids = files
+    const userids = result.files
       .map(file => file.userid)
-      .filter((v, i, a) => {
-        return v !== null && v !== undefined && v !== '' && a.indexOf(v) === i
-      })
+      .filter(utils.filterUniquifySqlArray)
 
     // If there are no uploads attached to a registered user, send response
     if (!userids.length) {
-      return res.json({ success: true, files, count, albums, basedomain })
+      return res.json(result)
     }
 
     // Query usernames of user IDs from currently selected files
@@ -1789,12 +1796,13 @@ self.list = async (req, res) => {
       .select('id', 'username')
   }
 
-  const users = {}
+  result.users = {}
+
   for (const user of usersTable) {
-    users[user.id] = user.username
+    result.users[user.id] = user.username
   }
 
-  return res.json({ success: true, files, count, users, albums, basedomain })
+  return res.json(result)
 }
 
 /** Get file info */
diff --git a/controllers/utilsController.js b/controllers/utilsController.js
index 6cecb2f..17d89de 100644
--- a/controllers/utilsController.js
+++ b/controllers/utilsController.js
@@ -386,6 +386,13 @@ self.mask = string => {
   }
 }
 
+self.filterUniquifySqlArray = (value, index, array) => {
+  return value !== null &&
+    value !== undefined &&
+    value !== '' &&
+    array.indexOf(value) === index
+}
+
 self.assertRequestType = (req, type) => {
   if (!req.is(type)) {
     throw new ClientError(`Request Content-Type must be ${type}.`)
diff --git a/src/js/dashboard.js b/src/js/dashboard.js
index 679dccf..76de06e 100644
--- a/src/js/dashboard.js
+++ b/src/js/dashboard.js
@@ -545,7 +545,8 @@ page.getUploads = (params = {}) => {
       }
     }
 
-    const pages = Math.ceil(response.data.count / 25)
+    const uploadsPerPage = response.data.uploadsPerPage || 25
+    const pages = Math.ceil(response.data.count / uploadsPerPage)
     const files = response.data.files
     if (params.pageNum && (files.length === 0)) {
       page.updateTrigger(params.trigger)
@@ -564,8 +565,10 @@ page.getUploads = (params = {}) => {
     const users = response.data.users
     const basedomain = response.data.basedomain
 
-    if (params.pageNum < 0) params.pageNum = Math.max(0, pages + params.pageNum)
-    const pagination = page.paginate(response.data.count, 25, params.pageNum)
+    if (params.pageNum < 0) {
+      params.pageNum = Math.max(0, pages + params.pageNum)
+    }
+    const pagination = page.paginate(response.data.count, uploadsPerPage, params.pageNum)
 
     const filter = `
       <div class="column">
@@ -671,7 +674,7 @@ page.getUploads = (params = {}) => {
       .replace(/(data-action="page-ellipsis")/g, `$1 data-jumpid="${bottomJumpId}"`)
 
     // Whether there are any unselected items
-    let unselected = false
+    let unselected = true
 
     const showOriginalNames = page.views[page.currentView].originalNames
     const hasExpiryDateColumn = files.some(file => typeof file.expirydate !== 'undefined')
@@ -720,7 +723,9 @@ page.getUploads = (params = {}) => {
 
       // Update selected status
       files[i].selected = page.selected[page.currentView].includes(files[i].id)
-      if (!files[i].selected) unselected = true
+      if (files[i].selected) {
+        unselected = false
+      }
 
       // Appendix (display album or user)
       if (params.all) {
@@ -888,7 +893,7 @@ page.getUploads = (params = {}) => {
     }
 
     const selectAll = document.querySelector('#selectAll')
-    if (selectAll && !unselected && files.length) {
+    if (selectAll && !unselected) {
       selectAll.checked = true
       selectAll.title = 'Unselect all'
     }
@@ -1615,7 +1620,8 @@ page.getAlbums = (params = {}) => {
       }
     }
 
-    const pages = Math.ceil(response.data.count / 25)
+    const albumsPerPage = response.data.albumsPerPage || 25
+    const pages = Math.ceil(response.data.count / albumsPerPage)
     const albums = response.data.albums
     if (params.pageNum && (albums.length === 0)) {
       page.updateTrigger(params.trigger)
@@ -1633,8 +1639,10 @@ page.getAlbums = (params = {}) => {
     const users = response.data.users
     const homeDomain = response.data.homeDomain || window.location.origin
 
-    if (params.pageNum < 0) params.pageNum = Math.max(0, pages + params.pageNum)
-    const pagination = page.paginate(response.data.count, 25, params.pageNum)
+    if (params.pageNum < 0) {
+      params.pageNum = Math.max(0, pages + params.pageNum)
+    }
+    const pagination = page.paginate(response.data.count, albumsPerPage, params.pageNum)
 
     const filter = `
       <div class="column">
@@ -1722,7 +1730,7 @@ page.getAlbums = (params = {}) => {
       .replace(/(data-action="page-ellipsis")/g, `$1 data-jumpid="${bottomJumpId}"`)
 
     // Whether there are any unselected items
-    let unselected = false
+    let unselected = true
 
     const createNewAlbum = `
       <h2 class="subtitle">Create new album</h2>
@@ -1793,7 +1801,9 @@ page.getAlbums = (params = {}) => {
       const albumUrl = homeDomain + albumUrlText
 
       const selected = page.selected[page.currentView].includes(album.id)
-      if (!selected) unselected = true
+      if (selected) {
+        unselected = false
+      }
 
       // Prettify
       album.hasZip = album.zipSize !== null
@@ -2386,7 +2396,8 @@ page.getUsers = (params = {}) => {
       }
     }
 
-    const pages = Math.ceil(response.data.count / 25)
+    const usersPerPage = response.data.usersPerPage || 25
+    const pages = Math.ceil(response.data.count / usersPerPage)
     const users = response.data.users
     if (params.pageNum && (users.length === 0)) {
       page.updateTrigger(params.trigger)
@@ -2401,8 +2412,10 @@ page.getUsers = (params = {}) => {
     page.currentView = 'users'
     page.cache = {}
 
-    if (params.pageNum < 0) params.pageNum = Math.max(0, pages + params.pageNum)
-    const pagination = page.paginate(response.data.count, 25, params.pageNum)
+    if (params.pageNum < 0) {
+      params.pageNum = Math.max(0, pages + params.pageNum)
+    }
+    const pagination = page.paginate(response.data.count, usersPerPage, params.pageNum)
 
     const filter = `
       <div class="column">
@@ -2494,7 +2507,7 @@ page.getUsers = (params = {}) => {
       .replace(/(data-action="page-ellipsis")/g, `$1 data-jumpid="${bottomJumpId}"`)
 
     // Whether there are any unselected items
-    let unselected = false
+    let unselected = true
 
     page.dom.innerHTML = `
       ${pagination}
@@ -2528,7 +2541,9 @@ page.getUsers = (params = {}) => {
     for (let i = 0; i < users.length; i++) {
       const user = users[i]
       const selected = page.selected[page.currentView].includes(user.id)
-      if (!selected) unselected = true
+      if (selected) {
+        unselected = false
+      }
 
       let displayGroup = null
       const groups = Object.keys(user.groups)