diff --git a/django-stubs/contrib/staticfiles/finders.pyi b/django-stubs/contrib/staticfiles/finders.pyi
index 24bbbcefa3..b1b9848d37 100644
--- a/django-stubs/contrib/staticfiles/finders.pyi
+++ b/django-stubs/contrib/staticfiles/finders.pyi
@@ -2,7 +2,7 @@ from collections.abc import Iterable, Iterator, Sequence
 from typing import Any, Literal, overload
 
 from django.core.checks.messages import CheckMessage
-from django.core.files.storage import FileSystemStorage, Storage
+from django.core.files.storage import FileSystemStorage, Storage, _DefaultStorage
 
 searched_locations: Any
 
@@ -16,7 +16,7 @@ class BaseFinder:
 
 class FileSystemFinder(BaseFinder):
     locations: list[tuple[str, str]]
-    storages: dict[str, Any]
+    storages: dict[str, FileSystemStorage]
     def __init__(self, app_names: Sequence[str] | None = None, *args: Any, **kwargs: Any) -> None: ...
     def find_location(self, root: str, path: str, prefix: str | None = None) -> str | None: ...
     @overload
@@ -48,7 +48,7 @@ class BaseStorageFinder(BaseFinder):
     def list(self, ignore_patterns: Iterable[str] | None) -> Iterable[Any]: ...
 
 class DefaultStorageFinder(BaseStorageFinder):
-    storage: Storage
+    storage: _DefaultStorage
     def __init__(self, *args: Any, **kwargs: Any) -> None: ...
 
 @overload
diff --git a/django-stubs/contrib/staticfiles/management/commands/collectstatic.pyi b/django-stubs/contrib/staticfiles/management/commands/collectstatic.pyi
index ecd4234812..bcd1e46590 100644
--- a/django-stubs/contrib/staticfiles/management/commands/collectstatic.pyi
+++ b/django-stubs/contrib/staticfiles/management/commands/collectstatic.pyi
@@ -1,5 +1,6 @@
 from typing import Any
 
+from django.contrib.staticfiles.storage import _ConfiguredStorage
 from django.core.files.storage import Storage
 from django.core.management.base import BaseCommand
 from django.utils.functional import cached_property
@@ -9,7 +10,7 @@ class Command(BaseCommand):
     symlinked_files: Any
     unmodified_files: Any
     post_processed_files: Any
-    storage: Any
+    storage: _ConfiguredStorage
     def __init__(self, *args: Any, **kwargs: Any) -> None: ...
     @cached_property
     def local(self) -> bool: ...
diff --git a/django-stubs/contrib/staticfiles/storage.pyi b/django-stubs/contrib/staticfiles/storage.pyi
index 027d34d860..5d46361e78 100644
--- a/django-stubs/contrib/staticfiles/storage.pyi
+++ b/django-stubs/contrib/staticfiles/storage.pyi
@@ -52,4 +52,8 @@ class ManifestFilesMixin(HashedFilesMixin):
 class ManifestStaticFilesStorage(ManifestFilesMixin, StaticFilesStorage): ...  # type: ignore[misc]
 class ConfiguredStorage(LazyObject): ...
 
-staticfiles_storage: Storage
+# This is our "placeholder" type the mypy plugin refines to configured
+# 'STORAGES["staticfiles"]["BACKEND"]' wherever it is used as a type.
+_ConfiguredStorage: TypeAlias = ConfiguredStorage
+
+staticfiles_storage: _ConfiguredStorage
diff --git a/django-stubs/core/files/storage/__init__.pyi b/django-stubs/core/files/storage/__init__.pyi
index d141d1ca8c..dd10d7c9b3 100644
--- a/django-stubs/core/files/storage/__init__.pyi
+++ b/django-stubs/core/files/storage/__init__.pyi
@@ -1,3 +1,5 @@
+from typing import TypeAlias
+
 from django.utils.functional import LazyObject
 
 from .base import Storage
@@ -18,6 +20,9 @@ __all__ = (
 
 class DefaultStorage(LazyObject): ...
 
+# This is our "placeholder" type the mypy plugin refines to configured
+# 'STORAGES["default"]["BACKEND"]' wherever it is used as a type.
+_DefaultStorage: TypeAlias = DefaultStorage
+
 storages: StorageHandler
-# default_storage is actually an instance of DefaultStorage, but it proxies through to a Storage
-default_storage: Storage
+default_storage: _DefaultStorage
diff --git a/django-stubs/core/files/storage/handler.pyi b/django-stubs/core/files/storage/handler.pyi
index 02ca26273e..18df340a2b 100644
--- a/django-stubs/core/files/storage/handler.pyi
+++ b/django-stubs/core/files/storage/handler.pyi
@@ -1,15 +1,21 @@
-from typing import Any
+from typing import Any, TypedDict, type_check_only
 
 from django.core.exceptions import ImproperlyConfigured
 from django.utils.functional import cached_property
+from typing_extensions import NotRequired
 
 from .base import Storage
 
+@type_check_only
+class _StorageConfig(TypedDict):
+    BACKEND: str
+    OPTIONS: NotRequired[dict[str, Any]]
+
 class InvalidStorageError(ImproperlyConfigured): ...
 
 class StorageHandler:
-    def __init__(self, backends: dict[str, Storage] | None = None) -> None: ...
+    def __init__(self, backends: dict[str, _StorageConfig] | None = None) -> None: ...
     @cached_property
-    def backends(self) -> dict[str, Storage]: ...
+    def backends(self) -> dict[str, _StorageConfig]: ...
     def __getitem__(self, alias: str) -> Storage: ...
-    def create_storage(self, params: dict[str, Any]) -> Storage: ...
+    def create_storage(self, params: _StorageConfig) -> Storage: ...
diff --git a/mypy_django_plugin/lib/fullnames.py b/mypy_django_plugin/lib/fullnames.py
index 5338e2275b..3811f834ce 100644
--- a/mypy_django_plugin/lib/fullnames.py
+++ b/mypy_django_plugin/lib/fullnames.py
@@ -11,6 +11,7 @@
 MANYTOMANY_FIELD_FULLNAME = "django.db.models.fields.related.ManyToManyField"
 DUMMY_SETTINGS_BASE_CLASS = "django.conf._DjangoConfLazyObject"
 AUTH_USER_MODEL_FULLNAME = "django.conf.settings.AUTH_USER_MODEL"
+STORAGE_HANDLER_CLASS_FULLNAME = "django.core.files.storage.handler.StorageHandler"
 
 QUERYSET_CLASS_FULLNAME = "django.db.models.query.QuerySet"
 BASE_MANAGER_CLASS_FULLNAME = "django.db.models.manager.BaseManager"
diff --git a/mypy_django_plugin/main.py b/mypy_django_plugin/main.py
index 9b109bf1f6..3b7793d522 100644
--- a/mypy_django_plugin/main.py
+++ b/mypy_django_plugin/main.py
@@ -35,6 +35,7 @@
     orm_lookups,
     querysets,
     settings,
+    storage,
 )
 from mypy_django_plugin.transformers.auth import get_user_model
 from mypy_django_plugin.transformers.functional import resolve_str_promise_attribute
@@ -98,8 +99,29 @@ def get_additional_deps(self, file: MypyFile) -> list[tuple[int, str, int]]:
         if file.fullname == "django.conf" and self.django_context.django_settings_module:
             return [self._new_dependency(self.django_context.django_settings_module, PRI_MED)]
 
+        # for settings.STORAGES["staticfiles"]
+        if (
+            file.fullname == "django.contrib.staticfiles.storage"
+            and isinstance(storage_config := self.django_context.settings.STORAGES.get("staticfiles"), dict)
+            and isinstance(storage_backend := storage_config.get("BACKEND"), str)
+            and "." in storage_backend
+        ):
+            return [self._new_dependency(storage_backend.rsplit(".", 1)[0])]
+
+        # for settings.STORAGES
+        elif file.fullname == "django.core.files.storage":
+            return [
+                self._new_dependency(storage_backend.rsplit(".", 1)[0])
+                for storage_config in self.django_context.settings.STORAGES.values()
+                if (
+                    isinstance(storage_config, dict)
+                    and isinstance(storage_backend := storage_config.get("BACKEND"), str)
+                    and "." in storage_backend
+                )
+            ]
+
         # for values / values_list
-        if file.fullname == "django.db.models":
+        elif file.fullname == "django.db.models":
             return [self._new_dependency("typing"), self._new_dependency("django_stubs_ext")]
 
         # for `get_user_model()`
@@ -200,6 +222,9 @@ def get_method_hook(self, fullname: str) -> Callable[[MethodContext], MypyType]
             }
             return hooks.get(class_fullname)
 
+        elif method_name == "__getitem__" and class_fullname == fullnames.STORAGE_HANDLER_CLASS_FULLNAME:
+            return partial(storage.extract_proper_type_for_getitem, django_context=self.django_context)
+
         if method_name in self.manager_and_queryset_method_hooks:
             info = self._get_typeinfo_or_none(class_fullname)
             if info and helpers.has_any_of_bases(
@@ -298,6 +323,10 @@ def get_type_analyze_hook(self, fullname: str) -> Callable[[AnalyzeTypeContext],
             return partial(handle_annotated_type, fullname=fullname)
         elif fullname == "django.contrib.auth.models._User":
             return partial(get_user_model, django_context=self.django_context)
+        elif fullname == "django.contrib.staticfiles.storage._ConfiguredStorage":
+            return partial(storage.get_storage, alias="staticfiles", django_context=self.django_context)
+        elif fullname == "django.core.files.storage._DefaultStorage":
+            return partial(storage.get_storage, alias="default", django_context=self.django_context)
         return None
 
     def get_dynamic_class_hook(self, fullname: str) -> Callable[[DynamicClassDefContext], None] | None:
@@ -311,9 +340,20 @@ def get_dynamic_class_hook(self, fullname: str) -> Callable[[DynamicClassDefCont
 
     def report_config_data(self, ctx: ReportConfigContext) -> dict[str, Any]:
         # Cache would be cleared if any settings do change.
-        extra_data = {}
-        # In all places we use '_User' alias as a type we want to clear cache if
-        # AUTH_USER_MODEL setting changes
+        extra_data: dict[str, Any] = {}
+        # In all places we use '_DefaultStorage' or '_ConfiguredStorage' aliases as a type we want to clear the cache
+        # if STORAGES setting changes
+        if ctx.id.startswith("django.contrib.staticfiles") or ctx.id.startswith("django.core.files.storage"):
+            extra_data["STORAGES"] = [
+                storage_backend
+                for storage_config in self.django_context.settings.STORAGES.values()
+                if (
+                    isinstance(storage_config, dict)
+                    and isinstance(storage_backend := storage_config.get("BACKEND"), str)
+                    and "." in storage_backend
+                )
+            ]
+        # In all places we use '_User' alias as a type we want to clear the cache if AUTH_USER_MODEL setting changes
         if ctx.id.startswith("django.contrib.auth") or ctx.id in {"django.http.request", "django.test.client"}:
             extra_data["AUTH_USER_MODEL"] = self.django_context.settings.AUTH_USER_MODEL
         return self.plugin_config.to_json(extra_data)
diff --git a/mypy_django_plugin/transformers/storage.py b/mypy_django_plugin/transformers/storage.py
new file mode 100644
index 0000000000..8c5a19f078
--- /dev/null
+++ b/mypy_django_plugin/transformers/storage.py
@@ -0,0 +1,74 @@
+from mypy.checker import TypeChecker
+from mypy.plugin import AnalyzeTypeContext, MethodContext
+from mypy.semanal import SemanticAnalyzer
+from mypy.typeanal import TypeAnalyser
+from mypy.types import Instance, PlaceholderType, UninhabitedType, get_proper_type
+from mypy.types import Type as MypyType
+from mypy.typevars import fill_typevars
+
+from mypy_django_plugin.django.context import DjangoContext
+from mypy_django_plugin.lib import helpers
+
+
+def get_storage_backend(alias: str, django_context: DjangoContext) -> str | None:
+    "Defensively look for a settings.STORAGES by its alias."
+
+    try:
+        fullname = django_context.settings.STORAGES[alias]["BACKEND"]
+        if not isinstance(fullname, str) or "." not in fullname:
+            return None
+
+        return fullname
+    except (KeyError, TypeError):
+        return None
+
+
+def get_storage(ctx: AnalyzeTypeContext, alias: str, django_context: DjangoContext) -> MypyType:
+    """
+    Get a storage type by its alias, but do not fail if it cannot be found since this is resolving an internal type-var,
+    and errors would be reported in the type stubs.
+    """
+
+    assert isinstance(ctx.api, TypeAnalyser)
+    assert isinstance(ctx.api.api, SemanticAnalyzer)
+
+    if fullname := get_storage_backend(alias, django_context):
+        if type_info := helpers.lookup_fully_qualified_typeinfo(ctx.api.api, fullname):
+            return fill_typevars(type_info)
+
+        if not ctx.api.api.final_iteration:
+            ctx.api.api.defer()
+            return PlaceholderType(fullname=fullname, args=[], line=ctx.context.line)
+
+    return ctx.type
+
+
+def extract_proper_type_for_getitem(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
+    """
+    Provide type information for `StorageHandler.__getitem__` when providing a literal value.
+    """
+
+    assert isinstance(ctx.api, TypeChecker)
+
+    if ctx.arg_types:
+        alias_type = get_proper_type(ctx.arg_types[0][0])
+
+        if (
+            isinstance(alias_type, Instance)
+            and (alias_literal := alias_type.last_known_value)
+            and isinstance(alias := alias_literal.value, str)
+        ):
+            if alias not in django_context.settings.STORAGES:
+                ctx.api.fail(f'Could not find config for "{alias}" in settings.STORAGES.', ctx.context)
+
+            elif fullname := get_storage_backend(alias, django_context):
+                type_info = helpers.lookup_fully_qualified_typeinfo(ctx.api, fullname)
+                assert type_info
+                return fill_typevars(type_info)
+
+            else:
+                ctx.api.fail(f'"{alias}" in settings.STORAGES is improperly configured.', ctx.context)
+
+            return UninhabitedType()
+
+    return ctx.default_return_type
diff --git a/tests/assert_type/contrib/staticfiles/test_storage.py b/tests/assert_type/contrib/staticfiles/test_storage.py
new file mode 100644
index 0000000000..4c45f2ab77
--- /dev/null
+++ b/tests/assert_type/contrib/staticfiles/test_storage.py
@@ -0,0 +1,8 @@
+from django.contrib.staticfiles.storage import ConfiguredStorage, StaticFilesStorage, staticfiles_storage
+from typing_extensions import assert_type
+
+# The plugin can figure out what these are (but pyright can't):
+assert_type(staticfiles_storage, StaticFilesStorage)  # pyright: ignore[reportAssertTypeFailure]
+
+# what pyright thinks these are:
+assert_type(staticfiles_storage, ConfiguredStorage)  # mypy: ignore[assert-type]
diff --git a/tests/assert_type/core/files/test_storage.py b/tests/assert_type/core/files/test_storage.py
new file mode 100644
index 0000000000..209ffd2fe0
--- /dev/null
+++ b/tests/assert_type/core/files/test_storage.py
@@ -0,0 +1,10 @@
+from django.core.files.storage import DefaultStorage, FileSystemStorage, Storage, default_storage, storages
+from typing_extensions import assert_type
+
+# The plugin can figure out what these are (but pyright can't):
+assert_type(default_storage, FileSystemStorage)  # pyright: ignore[reportAssertTypeFailure]
+assert_type(storages["default"], FileSystemStorage)  # pyright: ignore[reportAssertTypeFailure]
+
+# what pyright thinks these are:
+assert_type(default_storage, DefaultStorage)  # mypy: ignore[assert-type]
+assert_type(storages["default"], Storage)  # mypy: ignore[assert-type]
diff --git a/tests/typecheck/contrib/staticfiles/test_storage.yml b/tests/typecheck/contrib/staticfiles/test_storage.yml
new file mode 100644
index 0000000000..91e56135c2
--- /dev/null
+++ b/tests/typecheck/contrib/staticfiles/test_storage.yml
@@ -0,0 +1,5 @@
+- case: test_staticfiles_storage_defaults
+  main: |
+    from django.contrib.staticfiles.storage import staticfiles_storage
+
+    reveal_type(staticfiles_storage)  # N: Revealed type is "django.contrib.staticfiles.storage.StaticFilesStorage"
diff --git a/tests/typecheck/core/test_storage.yml b/tests/typecheck/core/test_storage.yml
new file mode 100644
index 0000000000..4342812f7a
--- /dev/null
+++ b/tests/typecheck/core/test_storage.yml
@@ -0,0 +1,57 @@
+- case: test_storage_defaults
+  main: |
+    from django.core.files.storage import default_storage, storages
+
+    reveal_type(default_storage)  # N: Revealed type is "django.core.files.storage.filesystem.FileSystemStorage"
+    reveal_type(storages["default"])  # N: Revealed type is "django.core.files.storage.filesystem.FileSystemStorage"
+    reveal_type(storages["staticfiles"])  # N: Revealed type is "django.contrib.staticfiles.storage.StaticFilesStorage"
+
+- case: test_custom_storages
+  main: |
+    from django.core.files.storage import default_storage, storages
+
+    reveal_type(default_storage)  # N: Revealed type is "myapp.storage.MyDefaultStorage"
+    reveal_type(storages["default"])  # N: Revealed type is "myapp.storage.MyDefaultStorage"
+    reveal_type(storages["custom"])  # N: Revealed type is "myapp.storage.MyStorage"
+    reveal_type(storages["staticfiles"])  # N: Revealed type is "django.contrib.staticfiles.storage.StaticFilesStorage"
+
+  custom_settings: |
+    from django.conf.global_settings import STORAGES as DEFAULT_STORAGES
+
+    STORAGES = {
+        **DEFAULT_STORAGES,
+        "default": {"BACKEND": "myapp.storage.MyDefaultStorage"},
+        "custom": {
+            "BACKEND": "myapp.storage.MyStorage",
+            "OPTIONS": {"option_enabled": False, "key": "test"},
+        }
+    }
+
+  files:
+    - path: myapp/storage.py
+      content: |
+        from django.core.files.storage import Storage
+
+        class MyDefaultStorage(Storage):
+            pass
+
+        class MyStorage(Storage):
+            pass
+
+- case: test_improperly_configured_storages
+  main: |
+    from django.core.files.storage import default_storage, storages
+
+    reveal_type(default_storage)  # N: Revealed type is "_DefaultStorage?"
+    reveal_type(storages["default"])  # E: "default" in settings.STORAGES is improperly configured.  [misc] # N: Revealed type is "Never"
+    reveal_type(storages["custom"])  # E: "custom" in settings.STORAGES is improperly configured.  [misc] # N: Revealed type is "Never"
+    reveal_type(storages["custom_two"])  # E: "custom_two" in settings.STORAGES is improperly configured.  [misc] # N: Revealed type is "Never"
+    reveal_type(storages["staticfiles"])  # E: Could not find config for "staticfiles" in settings.STORAGES.  [misc] # N: Revealed type is "Never"
+
+  custom_settings: |
+    STORAGES = {
+        "custom": {"BACKEND": "MyStorage"},
+        "custom_two": ["MyStorage"],
+        "default": True,
+        # "staticfiles" is missing
+    }