diff --git a/.env.sample b/.env.sample
index b93e8c6b..d14491e5 100644
--- a/.env.sample
+++ b/.env.sample
@@ -91,6 +91,11 @@
 # Default: false
 # BK_HIDE_ADMIN=true
 
+# Optional: Hide the 'Problems' entry from the menu. Does not disable the route.
+# Default: false
+# Legacy name: BK_HIDE_MISSING_PARTS
+# BK_HIDE_ALL_PROBLEMS_PARTS=true
+
 # Optional: Hide the 'Instructions' entry from the menu. Does not disable the route.
 # Default: false
 # BK_HIDE_ALL_INSTRUCTIONS=true
@@ -107,10 +112,9 @@
 # Default: false
 # BK_HIDE_ALL_SETS=true
 
-# Optional: Hide the 'Problems' entry from the menu. Does not disable the route.
+# Optional: Hide the 'Storages' entry from the menu. Does not disable the route.
 # Default: false
-# Legacy name: BK_HIDE_MISSING_PARTS
-# BK_HIDE_ALL_PROBLEMS_PARTS=true
+# BK_HIDE_ALL_STORAGES=true
 
 # Optional: Hide the 'Instructions' entry in a Set card
 # Default: false
@@ -255,6 +259,12 @@
 # Default: /bricksocket/
 # BK_SOCKET_PATH=custompath
 
+# Optional: Change the default order of storages. By default ordered by insertion order.
+# Useful column names for this option are:
+# - "bricktracker_metadata_storages"."name" ASC: storage name
+# Default: "bricktracker_metadata_storages"."name" ASC
+# BK_MINIFIGURES_DEFAULT_ORDER="bricktracker_metadata_storages"."name" ASC
+
 # Optional: URL to the themes.csv.gz on Rebrickable
 # Default: https://cdn.rebrickable.com/media/downloads/themes.csv.gz
 # BK_THEMES_FILE_URL=
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ed15af7..b50f3427 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,8 @@
 - Added: `BK_HIDE_TABLE_DAMAGED_PARTS`, hide the Damaged column in all tables
 - Added: `BK_SHOW_GRID_SORT`, show the sort options on the grid by default
 - Added: `BK_SHOW_GRID_FILTERS`, show the filter options on the grid by default
+- Added: `BK_HIDE_ALL_STORAGES`, hide the "Storages" menu entry
+- Added: `BK_MINIFIGURES_DEFAULT_ORDER`, ordering of storages
 
 ### Code
 
@@ -28,7 +30,7 @@
     - Deduplicate
     - Compute number of parts
 
-Parts
+- Parts
     - Damaged parts
 
 - Sets
@@ -38,6 +40,9 @@ Parts
     - Tags
     - Storage
 
+- Storage
+    - Storage content and list
+
 - Socket
     - Add decorator for rebrickable, authenticated and threaded socket actions
 
@@ -86,6 +91,10 @@ Parts
     - Manually collapsible filters (with configuration variable for default state)
     - Manually collapsible sort (with configuration variable for default state)
 
+- Storage
+    - Storage list
+    - Storage content
+
 ## 1.1.1: PDF Instructions Download
 
 ### Instructions
diff --git a/bricktracker/app.py b/bricktracker/app.py
index 4b6f0d4f..af005d96 100644
--- a/bricktracker/app.py
+++ b/bricktracker/app.py
@@ -29,6 +29,7 @@ from bricktracker.views.login import login_page
 from bricktracker.views.minifigure import minifigure_page
 from bricktracker.views.part import part_page
 from bricktracker.views.set import set_page
+from bricktracker.views.storage import storage_page
 from bricktracker.views.wish import wish_page
 
 
@@ -77,6 +78,7 @@ def setup_app(app: Flask) -> None:
     app.register_blueprint(minifigure_page)
     app.register_blueprint(part_page)
     app.register_blueprint(set_page)
+    app.register_blueprint(storage_page)
     app.register_blueprint(wish_page)
 
     # Register admin routes
diff --git a/bricktracker/config.py b/bricktracker/config.py
index cd7ef74a..5b9788fb 100644
--- a/bricktracker/config.py
+++ b/bricktracker/config.py
@@ -29,6 +29,7 @@ CONFIG: Final[list[dict[str, Any]]] = [
     {'n': 'HIDE_ALL_MINIFIGURES', 'c': bool},
     {'n': 'HIDE_ALL_PARTS', 'c': bool},
     {'n': 'HIDE_ALL_SETS', 'c': bool},
+    {'n': 'HIDE_ALL_STORAGES', 'c': bool},
     {'n': 'HIDE_ALL_PROBLEMS_PARTS', 'e': 'BK_HIDE_MISSING_PARTS', 'c': bool},
     {'n': 'HIDE_SET_INSTRUCTIONS', 'c': bool},
     {'n': 'HIDE_TABLE_DAMAGED_PARTS', 'c': bool},
@@ -59,6 +60,7 @@ CONFIG: Final[list[dict[str, Any]]] = [
     {'n': 'SKIP_SPARE_PARTS', 'c': bool},
     {'n': 'SOCKET_NAMESPACE', 'd': 'bricksocket'},
     {'n': 'SOCKET_PATH', 'd': '/bricksocket/'},
+    {'n': 'STORAGE_DEFAULT_ORDER', 'd': '"bricktracker_metadata_storages"."name" ASC'},  # noqa: E501
     {'n': 'THEMES_FILE_URL', 'd': 'https://cdn.rebrickable.com/media/downloads/themes.csv.gz'},  # noqa: E501
     {'n': 'THEMES_PATH', 'd': './themes.csv'},
     {'n': 'TIMEZONE', 'd': 'Etc/UTC'},
diff --git a/bricktracker/metadata_list.py b/bricktracker/metadata_list.py
index 68238e9d..60bfb5f1 100644
--- a/bricktracker/metadata_list.py
+++ b/bricktracker/metadata_list.py
@@ -43,8 +43,7 @@ class BrickMetadataList(BrickRecordList[T]):
 
         # Records override (masking the class variables with instance ones)
         if records is not None:
-            self.records = []
-            self.mapping = {}
+            self.override()
 
             for metadata in records:
                 self.records.append(metadata)
@@ -79,6 +78,13 @@ class BrickMetadataList(BrickRecordList[T]):
     def filter(self) -> list[T]:
         return self.records
 
+    # Add a layer of override data
+    def override(self) -> None:
+        self.fields = BrickRecordFields()
+
+        self.records = []
+        self.mapping = {}
+
     # Return the items as columns for a select
     @classmethod
     def as_columns(cls, /, **kwargs) -> str:
diff --git a/bricktracker/navbar.py b/bricktracker/navbar.py
index 30007dee..20a2b292 100644
--- a/bricktracker/navbar.py
+++ b/bricktracker/navbar.py
@@ -14,6 +14,7 @@ NAVBAR: Final[list[dict[str, Any]]] = [
     {'e': 'part.problem', 't': 'Problems', 'i': 'error-warning-line', 'f': 'HIDE_ALL_PROBLEMS_PARTS'},  # noqa: E501
     {'e': 'minifigure.list', 't': 'Minifigures', 'i': 'group-line', 'f': 'HIDE_ALL_MINIFIGURES'},  # noqa: E501
     {'e': 'instructions.list', 't': 'Instructions', 'i': 'file-line', 'f': 'HIDE_ALL_INSTRUCTIONS'},  # noqa: E501
+    {'e': 'storage.list', 't': 'Storages', 'i': 'archive-2-line', 'f': 'HIDE_ALL_STORAGES'},  # noqa: E501
     {'e': 'wish.list', 't': 'Wishlist', 'i': 'gift-line', 'f': 'HIDE_WISHES'},
     {'e': 'admin.admin', 't': 'Admin', 'i': 'settings-4-line', 'f': 'HIDE_ADMIN'},  # noqa: E501
 ]
diff --git a/bricktracker/set.py b/bricktracker/set.py
index 90b2679b..6368d40c 100644
--- a/bricktracker/set.py
+++ b/bricktracker/set.py
@@ -214,7 +214,11 @@ class BrickSet(RebrickableSet):
 
     # Compute the url for the refresh button
     def url_for_refresh(self, /) -> str:
-        return url_for(
-            'set.refresh',
-            id=self.fields.id,
-        )
+        return url_for('set.refresh', id=self.fields.id)
+
+    # Compute the url for the set storage
+    def url_for_storage(self, /) -> str:
+        if self.fields.storage is not None:
+            return url_for('storage.details', id=self.fields.storage)
+        else:
+            return ''
diff --git a/bricktracker/set_list.py b/bricktracker/set_list.py
index b12f9717..6c3b9283 100644
--- a/bricktracker/set_list.py
+++ b/bricktracker/set_list.py
@@ -5,6 +5,7 @@ from flask import current_app
 from .record_list import BrickRecordList
 from .set_owner_list import BrickSetOwnerList
 from .set_status_list import BrickSetStatusList
+from .set_storage import BrickSetStorage
 from .set_tag_list import BrickSetTagList
 from .set import BrickSet
 
@@ -24,6 +25,7 @@ class BrickSetList(BrickRecordList[BrickSet]):
     select_query: str = 'set/list/all'
     using_minifigure_query: str = 'set/list/using_minifigure'
     using_part_query: str = 'set/list/using_part'
+    using_storage_query: str = 'set/list/using_storage'
 
     def __init__(self, /):
         super().__init__()
@@ -151,3 +153,13 @@ class BrickSetList(BrickRecordList[BrickSet]):
         self.list(override_query=self.using_part_query)
 
         return self
+
+    # Sets using a storage
+    def using_storage(self, storage: BrickSetStorage, /) -> Self:
+        # Save the parameters to the fields
+        self.fields.storage = storage.fields.id
+
+        # Load the sets from the database
+        self.list(override_query=self.using_storage_query)
+
+        return self
diff --git a/bricktracker/set_storage.py b/bricktracker/set_storage.py
index 0a54262f..30c559c9 100644
--- a/bricktracker/set_storage.py
+++ b/bricktracker/set_storage.py
@@ -1,5 +1,7 @@
 from .metadata import BrickMetadata
 
+from flask import url_for
+
 
 # Lego set storage metadata
 class BrickSetStorage(BrickMetadata):
@@ -11,3 +13,10 @@ class BrickSetStorage(BrickMetadata):
     select_query: str = 'set/metadata/storage/select'
     update_field_query: str = 'set/metadata/storage/update/field'
     update_set_state_query: str = 'set/metadata/storage/update/state'
+
+    # Self url
+    def url(self, /) -> str:
+        return url_for(
+            'storage.details',
+            id=self.fields.id,
+        )
diff --git a/bricktracker/set_storage_list.py b/bricktracker/set_storage_list.py
index 72efde70..8453f366 100644
--- a/bricktracker/set_storage_list.py
+++ b/bricktracker/set_storage_list.py
@@ -1,6 +1,8 @@
 import logging
 from typing import Self
 
+from flask import current_app
+
 from .metadata_list import BrickMetadataList
 from .set_storage import BrickSetStorage
 
@@ -13,10 +15,25 @@ class BrickSetStorageList(BrickMetadataList[BrickSetStorage]):
 
     # Queries
     select_query = 'set/metadata/storage/list'
+    all_query = 'set/metadata/storage/all'
 
     # Set state endpoint
     set_state_endpoint: str = 'set.update_storage'
 
+    # Load all storages
+    @classmethod
+    def all(cls, /) -> Self:
+        new = cls.new()
+        new.override()
+
+        for record in new.select(
+            override_query=cls.all_query,
+            order=current_app.config['STORAGE_DEFAULT_ORDER']
+        ):
+            new.records.append(new.model(record=record))
+
+        return new
+
     # Instantiate the list with the proper class
     @classmethod
     def new(cls, /, *, force: bool = False) -> Self:
diff --git a/bricktracker/sql/set/list/using_storage.sql b/bricktracker/sql/set/list/using_storage.sql
new file mode 100644
index 00000000..0dc0f14a
--- /dev/null
+++ b/bricktracker/sql/set/list/using_storage.sql
@@ -0,0 +1,5 @@
+{% extends 'set/base/full.sql' %}
+
+{% block where %}
+WHERE "bricktracker_sets"."storage" IS NOT DISTINCT FROM :storage
+{% endblock %}
diff --git a/bricktracker/sql/set/metadata/storage/all.sql b/bricktracker/sql/set/metadata/storage/all.sql
new file mode 100644
index 00000000..0bd8b8eb
--- /dev/null
+++ b/bricktracker/sql/set/metadata/storage/all.sql
@@ -0,0 +1,14 @@
+{% extends 'set/metadata/storage/base.sql' %}
+
+{% block total_sets %}
+IFNULL(COUNT("bricktracker_sets"."id"), 0) AS "total_sets"
+{% endblock %}
+
+{% block join %}
+LEFT JOIN "bricktracker_sets"
+ON "bricktracker_metadata_storages"."id" IS NOT DISTINCT FROM "bricktracker_sets"."storage"
+{% endblock %}
+
+{% block group %}
+GROUP BY "bricktracker_metadata_storages"."id"
+{% endblock %}
\ No newline at end of file
diff --git a/bricktracker/sql/set/metadata/storage/base.sql b/bricktracker/sql/set/metadata/storage/base.sql
index 2417aa69..bf616ed9 100644
--- a/bricktracker/sql/set/metadata/storage/base.sql
+++ b/bricktracker/sql/set/metadata/storage/base.sql
@@ -1,6 +1,17 @@
 SELECT
     "bricktracker_metadata_storages"."id",
-    "bricktracker_metadata_storages"."name"
+    "bricktracker_metadata_storages"."name",
+    {% block total_sets %}
+    NULL as "total_sets" -- dummy for order: total_sets
+    {% endblock %}
 FROM "bricktracker_metadata_storages"
 
-{% block where %}{% endblock %}
\ No newline at end of file
+{% block join %}{% endblock %}
+
+{% block where %}{% endblock %}
+
+{% block group %}{% endblock %}
+
+{% if order %}
+ORDER BY {{ order }}
+{% endif %}
diff --git a/bricktracker/views/storage.py b/bricktracker/views/storage.py
new file mode 100644
index 00000000..e41e97a4
--- /dev/null
+++ b/bricktracker/views/storage.py
@@ -0,0 +1,36 @@
+from flask import Blueprint, render_template
+
+from .exceptions import exception_handler
+from ..set_owner_list import BrickSetOwnerList
+from ..set_list import BrickSetList
+from ..set_storage import BrickSetStorage
+from ..set_storage_list import BrickSetStorageList
+from ..set_tag_list import BrickSetTagList
+
+storage_page = Blueprint('storage', __name__, url_prefix='/storages')
+
+
+# Index
+@storage_page.route('/', methods=['GET'])
+@exception_handler(__file__)
+def list() -> str:
+    return render_template(
+        'storages.html',
+        table_collection=BrickSetStorageList.all(),
+    )
+
+
+# Storage details
+@storage_page.route('/<id>/details')
+@exception_handler(__file__)
+def details(*, id: str) -> str:
+    storage = BrickSetStorage().select_specific(id)
+
+    return render_template(
+        'storage.html',
+        item=storage,
+        sets=BrickSetList().using_storage(storage),
+        brickset_owners=BrickSetOwnerList.list(),
+        brickset_storages=BrickSetStorageList.list(as_class=True),
+        brickset_tags=BrickSetTagList.list(),
+    )
diff --git a/templates/macro/accordion.html b/templates/macro/accordion.html
index 1fd91f3c..c5c89545 100644
--- a/templates/macro/accordion.html
+++ b/templates/macro/accordion.html
@@ -26,10 +26,10 @@
   </div>
 {% endmacro %}
 
-{% macro cards(card_collection, title, id, parent, target, icon=none) %}
+{% macro cards(card_collection, title, id, parent, target, expanded=false, icon=none) %}
   {% set size=card_collection | length %}
   {% if size %}
-    {{ header(title, id, parent, icon=icon) }}
+    {{ header(title, id, parent, expanded=expanded, icon=icon) }}
       <div class="row">
         {% for item in card_collection %}
           <div class="col-md-6 col-xl-3 d-flex align-items-stretch">
diff --git a/templates/macro/badge.html b/templates/macro/badge.html
index bd683f40..e1f9071a 100644
--- a/templates/macro/badge.html
+++ b/templates/macro/badge.html
@@ -80,7 +80,7 @@
     {% else %}
       {% set text=storage.fields.name %}
     {% endif %}
-    {{ badge(check=storage, solo=solo, last=last, color='light text-warning-emphasis bg-warning-subtle border border-warning-subtle', icon='archive-2-line', text=text, alt='Storage', tooltip=tooltip) }}
+    {{ badge(url=item.url_for_storage(), solo=solo, last=last, color='light text-warning-emphasis bg-warning-subtle border border-warning-subtle', icon='archive-2-line', text=text, alt='Storage', tooltip=tooltip) }}
   {% endif %}
 {% endmacro %}
 
diff --git a/templates/macro/table.html b/templates/macro/table.html
index ebf1ded6..0638380f 100644
--- a/templates/macro/table.html
+++ b/templates/macro/table.html
@@ -1,7 +1,9 @@
-{% macro header(color=false, parts=false, quantity=false, missing_parts=false, damaged_parts=false, sets=false, minifigures=false) %}
+{% macro header(image=true, color=false, parts=false, quantity=false, missing=true, missing_parts=false, damaged=true, damaged_parts=false, sets=false, minifigures=false) %}
   <thead>
     <tr>
-      <th data-table-no-sort="true" class="no-sort" scope="col"><i class="ri-image-line fw-normal"></i> Image</th>
+      {% if image %}
+        <th data-table-no-sort="true" class="no-sort" scope="col"><i class="ri-image-line fw-normal"></i> Image</th>
+      {% endif %}
       <th scope="col"><i class="ri-pencil-line fw-normal"></i> Name</th>
       {% if color %}
         <th scope="col"><i class="ri-palette-line fw-normal"></i> Color</th>
@@ -12,10 +14,10 @@
       {% if quantity %}
         <th data-table-number="true" scope="col"><i class="ri-functions fw-normal"></i> Quantity</th>
       {% endif %}
-      {% if not config['HIDE_TABLE_MISSING_PARTS'] %}
+      {% if missing and not config['HIDE_TABLE_MISSING_PARTS'] %}
         <th data-table-number="true" scope="col"><i class="ri-question-line fw-normal"></i> Missing{% if missing_parts %} parts{% endif %}</th>
       {% endif %}
-      {% if not config['HIDE_TABLE_DAMAGED_PARTS'] %}
+      {% if damaged and not config['HIDE_TABLE_DAMAGED_PARTS'] %}
       <th data-table-number="true" scope="col"><i class="ri-error-warning-line fw-normal"></i> Damaged{% if damaged_parts %} parts{% endif %}</th>
       {% endif %}
       {% if sets %}
diff --git a/templates/storage.html b/templates/storage.html
new file mode 100644
index 00000000..76e95497
--- /dev/null
+++ b/templates/storage.html
@@ -0,0 +1,15 @@
+{% extends 'base.html' %}
+
+{% block title %} - Storage {{ item.fields.name}}{% endblock %}
+
+{% block main %}
+<div class="container">
+  <div class="row">
+    <div class="col-12">
+      {% with solo=true %}
+        {% include 'storage/card.html' %}
+      {% endwith %}
+    </div>
+  </div>
+</div>
+{% endblock %}
diff --git a/templates/storage/card.html b/templates/storage/card.html
new file mode 100644
index 00000000..cf29de3f
--- /dev/null
+++ b/templates/storage/card.html
@@ -0,0 +1,16 @@
+{% import 'macro/accordion.html' as accordion with context %}
+{% import 'macro/badge.html' as badge %}
+{% import 'macro/card.html' as card %}
+
+<div class="card mb-3 flex-fill {% if solo %}card-solo{% endif %}">
+  {{ card.header(item, item.fields.name, solo=solo, icon='archive-2-line') }}
+  <div class="card-body border-bottom-0 {% if not solo %}p-1{% endif %}">
+    {{ badge.total_sets(sets | length, solo=solo, last=last) }}
+  </div>
+  {% if solo %}
+    <div class="accordion accordion-flush border-top" id="storage-details">
+      {{ accordion.cards(sets, 'Sets', 'sets-stored', 'storage-details', 'set/card.html', expanded=true, icon='hashtag') }}
+    </div>
+    <div class="card-footer"></div>
+  {% endif %}
+</div>
diff --git a/templates/storage/table.html b/templates/storage/table.html
new file mode 100644
index 00000000..680c75c8
--- /dev/null
+++ b/templates/storage/table.html
@@ -0,0 +1,16 @@
+{% import 'macro/form.html' as form %}
+{% import 'macro/table.html' as table %}
+
+<div class="table-responsive-sm">
+  <table data-table="true" class="table table-striped align-middle" id="storage">
+    {{ table.header(image=false, missing=false, damaged=false, sets=true) }}
+    <tbody>
+      {% for item in table_collection %}
+      <tr>
+        <td data-sort="{{ item.fields.name }}"><a class="text-reset" href="{{ item.url() }}">{{ item.fields.name }}</a></td>
+        <td>{{ item.fields.total_sets }}</td>
+      </tr>
+      {% endfor %}
+    </tbody>
+  </table>
+</div>
diff --git a/templates/storages.html b/templates/storages.html
new file mode 100644
index 00000000..14ac3548
--- /dev/null
+++ b/templates/storages.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+
+{% block title %} - All storages{% endblock %}
+
+{% block main %}
+<div class="container-fluid px-0">
+  {% with all=true %}
+    {% include 'storage/table.html' %}
+  {% endwith %}
+</div>
+{% endblock %}