diff --git a/src/Attribute/RequiresPhpExtension.php b/src/Attribute/RequiresPhpExtension.php
new file mode 100644
index 0000000000000000000000000000000000000000..8cff848e201867140fa0bd084ce1170fe9a51f1a
--- /dev/null
+++ b/src/Attribute/RequiresPhpExtension.php
@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance\Attribute;
+
+use Attribute;
+use Distantmagic\Resonance\Attribute as BaseAttribute;
+
+#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)]
+final readonly class RequiresPhpExtension extends BaseAttribute
+{
+    /**
+     * @param non-empty-string $name
+     */
+    public function __construct(public string $name) {}
+}
diff --git a/src/Command/GrpcGenerate.php b/src/Command/GrpcGenerate.php
index 6078bd36263114f9eb419d4c3cd7f1c55e44b58f..3d200a94624d110703b85faf48363c28adc2c32a 100644
--- a/src/Command/GrpcGenerate.php
+++ b/src/Command/GrpcGenerate.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Distantmagic\Resonance\Command;
 
 use Distantmagic\Resonance\Attribute\ConsoleCommand;
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\Attribute\WantsFeature;
 use Distantmagic\Resonance\Command;
 use Distantmagic\Resonance\Feature;
@@ -16,6 +17,7 @@ use Symfony\Component\Console\Output\OutputInterface;
     name: 'grpc:generate',
     description: 'Generate GRPC stubs'
 )]
+#[RequiresPhpExtension('grpc')]
 #[WantsFeature(Feature::Grpc)]
 final class GrpcGenerate extends Command
 {
diff --git a/src/Command/PostfixBounce.php b/src/Command/PostfixBounce.php
index 6db07429a0c4650fa7fd306d1390e1b1c9113998..19c79966bf04bf99f824758816353b83e8fd35da 100644
--- a/src/Command/PostfixBounce.php
+++ b/src/Command/PostfixBounce.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Distantmagic\Resonance\Command;
 
 use Distantmagic\Resonance\Attribute\ConsoleCommand;
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\Attribute\WantsFeature;
 use Distantmagic\Resonance\Command;
 use Distantmagic\Resonance\CoroutineCommand;
@@ -22,6 +23,8 @@ use Symfony\Component\Console\Output\OutputInterface;
     name: 'postfix:bounce',
     description: 'Handles email bounces (requires mailparse)'
 )]
+#[RequiresPhpExtension('http')]
+#[RequiresPhpExtension('mailparse')]
 #[WantsFeature(Feature::Postfix)]
 final class PostfixBounce extends CoroutineCommand
 {
@@ -36,10 +39,6 @@ final class PostfixBounce extends CoroutineCommand
 
     protected function executeInCoroutine(InputInterface $input, OutputInterface $output): int
     {
-        if (!extension_loaded('mailparse') || !extension_loaded('http')) {
-            throw new RuntimeException('You need to install "http" and "mailparse" extensions');
-        }
-
         $content = stream_get_contents(STDIN);
 
         if (false === $content || empty($content)) {
diff --git a/src/Command/Watch.php b/src/Command/Watch.php
index 19bf802c82df8dec5dfd6613dce7a255e07d4772..ab17e8c91a55ad2163da387111cb426e2cdc497d 100644
--- a/src/Command/Watch.php
+++ b/src/Command/Watch.php
@@ -6,10 +6,10 @@ namespace Distantmagic\Resonance\Command;
 
 use Distantmagic\Resonance\ApplicationConfiguration;
 use Distantmagic\Resonance\Attribute\ConsoleCommand;
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\Command;
 use Distantmagic\Resonance\InotifyIterator;
 use Psr\Log\LoggerInterface;
-use RuntimeException;
 use Swoole\Process;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputInterface;
@@ -17,8 +17,9 @@ use Symfony\Component\Console\Output\OutputInterface;
 
 #[ConsoleCommand(
     name: 'watch:command',
-    description: 'Watch project files for changes (requires inotify)'
+    description: 'Watch project files for changes'
 )]
+#[RequiresPhpExtension('inotify')]
 final class Watch extends Command
 {
     private ?Process $process = null;
@@ -37,10 +38,6 @@ final class Watch extends Command
 
     protected function execute(InputInterface $input, OutputInterface $output): int
     {
-        if (!extension_loaded('inotify')) {
-            throw new RuntimeException('You need to install "inotify" extension');
-        }
-
         /**
          * @var string $childCommandName
          */
diff --git a/src/DependencyInjectionContainer.php b/src/DependencyInjectionContainer.php
index 61b470710576045088a7aeca1686e87163682515..eaaa224fe616bd70ca7d631fe91377f776a42e03 100644
--- a/src/DependencyInjectionContainer.php
+++ b/src/DependencyInjectionContainer.php
@@ -5,8 +5,10 @@ declare(strict_types=1);
 namespace Distantmagic\Resonance;
 
 use Closure;
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\DependencyInjectionContainerException\AmbiguousProvider;
 use Distantmagic\Resonance\DependencyInjectionContainerException\DisabledFeatureProvider;
+use Distantmagic\Resonance\DependencyInjectionContainerException\MissingPhpExtension;
 use Distantmagic\Resonance\DependencyInjectionContainerException\MissingProvider;
 use Ds\Map;
 use Ds\Set;
@@ -224,6 +226,17 @@ readonly class DependencyInjectionContainer
      */
     private function makeClassFromReflection(ReflectionClass $reflectionClass, DependencyStack $stack): object
     {
+        $reflectionClassAttributeManager = new ReflectionClassAttributeManager($reflectionClass);
+        $requiredPhpExtensions = $reflectionClassAttributeManager->findAttributes(RequiresPhpExtension::class);
+
+        if (!$requiredPhpExtensions->isEmpty()) {
+            foreach ($requiredPhpExtensions as $requiredPhpExtension) {
+                if (!extension_loaded($requiredPhpExtension->name)) {
+                    throw new MissingPhpExtension($reflectionClass->name, $requiredPhpExtension->name);
+                }
+            }
+        }
+
         $constructorReflection = $reflectionClass->getConstructor();
 
         if ($constructorReflection) {
diff --git a/src/DependencyInjectionContainerException/MissingPhpExtension.php b/src/DependencyInjectionContainerException/MissingPhpExtension.php
new file mode 100644
index 0000000000000000000000000000000000000000..35a34078a759656a03efee09bdd751e973d7ada8
--- /dev/null
+++ b/src/DependencyInjectionContainerException/MissingPhpExtension.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance\DependencyInjectionContainerException;
+
+use Distantmagic\Resonance\DependencyInjectionContainerException;
+use Throwable;
+
+class MissingPhpExtension extends DependencyInjectionContainerException
+{
+    /**
+     * @param class-string     $className
+     * @param non-empty-string $extensionName
+     */
+    public function __construct(
+        string $className,
+        string $extensionName,
+        ?Throwable $previous = null,
+    ) {
+        parent::__construct(
+            sprintf(
+                'To use "%s" you need to install "%s" PHP extension.',
+                $className,
+                $extensionName,
+            ),
+            $previous
+        );
+    }
+}
diff --git a/src/DoctrineMySQLDriver.php b/src/DoctrineMySQLDriver.php
index a74d99db98fe6fb18af6242b70dbbfd4f1d5b651..ac2636f306de96f3e685403fd9e8a4cf67ddc1ab 100644
--- a/src/DoctrineMySQLDriver.php
+++ b/src/DoctrineMySQLDriver.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Distantmagic\Resonance;
 
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\Attribute\Singleton;
 use Doctrine\DBAL\Driver\AbstractMySQLDriver;
 
@@ -13,6 +14,7 @@ use Doctrine\DBAL\Driver\AbstractMySQLDriver;
  *
  * @psalm-suppress DeprecatedInterface
  */
+#[RequiresPhpExtension('pdo')]
 #[Singleton]
 class DoctrineMySQLDriver extends AbstractMySQLDriver
 {
diff --git a/src/DoctrinePostgreSQLDriver.php b/src/DoctrinePostgreSQLDriver.php
index b0147adfe0eb13d3baa8fef8c382c73f8bcee633..8c4063902603cf20760911627defd6f29c0d099c 100644
--- a/src/DoctrinePostgreSQLDriver.php
+++ b/src/DoctrinePostgreSQLDriver.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Distantmagic\Resonance;
 
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\Attribute\Singleton;
 use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
 
@@ -13,6 +14,7 @@ use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
  *
  * @psalm-suppress DeprecatedInterface
  */
+#[RequiresPhpExtension('pdo')]
 #[Singleton]
 class DoctrinePostgreSQLDriver extends AbstractPostgreSQLDriver
 {
diff --git a/src/DoctrineSQLiteDriver.php b/src/DoctrineSQLiteDriver.php
index fa6f0f105f968d34a1c14292ba7e277ba89769e3..014e6ec2210aeb2393ff726b548ab14d9f256c64 100644
--- a/src/DoctrineSQLiteDriver.php
+++ b/src/DoctrineSQLiteDriver.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Distantmagic\Resonance;
 
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\Attribute\Singleton;
 use Doctrine\DBAL\Driver\AbstractSQLiteDriver;
 
@@ -13,6 +14,7 @@ use Doctrine\DBAL\Driver\AbstractSQLiteDriver;
  *
  * @psalm-suppress DeprecatedInterface
  */
+#[RequiresPhpExtension('pdo')]
 #[Singleton]
 class DoctrineSQLiteDriver extends AbstractSQLiteDriver
 {
diff --git a/src/IntlFormatter.php b/src/IntlFormatter.php
index 7fbca43c692a9712f5d567e290869083a2e960e2..baa662914e0b630fbb1ed65d0e34a76d584cf6e8 100644
--- a/src/IntlFormatter.php
+++ b/src/IntlFormatter.php
@@ -6,11 +6,13 @@ namespace Distantmagic\Resonance;
 
 use DateTimeImmutable;
 use DateTimeInterface;
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\Attribute\Singleton;
 use IntlDateFormatter;
 use Swoole\Http\Request;
 
 #[Singleton]
+#[RequiresPhpExtension('intl')]
 final readonly class IntlFormatter
 {
     private IntlDateFormatterRepository $formatters;
diff --git a/src/LlamaCppClient.php b/src/LlamaCppClient.php
index 48b298e95550a7bb04ea4d1f753f9cb0a8d66287..09b7a3ef3db8a7ccac6832d2e7bd71646e8722f0 100644
--- a/src/LlamaCppClient.php
+++ b/src/LlamaCppClient.php
@@ -5,12 +5,14 @@ declare(strict_types=1);
 namespace Distantmagic\Resonance;
 
 use CurlHandle;
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\Attribute\Singleton;
 use Generator;
 use JsonSerializable;
 use RuntimeException;
 use Swoole\Coroutine\Channel;
 
+#[RequiresPhpExtension('curl')]
 #[Singleton]
 readonly class LlamaCppClient
 {
diff --git a/src/SQLiteVSSConnectionBuilder.php b/src/SQLiteVSSConnectionBuilder.php
index be27327ac27885ecc42f8ee7e062fd7b056756ce..0523cb5da0bc09e41233f547133dd24966f9f8c8 100644
--- a/src/SQLiteVSSConnectionBuilder.php
+++ b/src/SQLiteVSSConnectionBuilder.php
@@ -4,9 +4,11 @@ declare(strict_types=1);
 
 namespace Distantmagic\Resonance;
 
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\Attribute\Singleton;
 use SQLite3;
 
+#[RequiresPhpExtension('sqlite3')]
 #[Singleton]
 readonly class SQLiteVSSConnectionBuilder
 {
diff --git a/src/SingletonProvider/DatabaseConnectionPoolRepositoryProvider.php b/src/SingletonProvider/DatabaseConnectionPoolRepositoryProvider.php
index a79340aad2566e49ecf0c381e44eb04c408635fb..dcfa796603336c0ad6915648155f59df6a8f9515 100644
--- a/src/SingletonProvider/DatabaseConnectionPoolRepositoryProvider.php
+++ b/src/SingletonProvider/DatabaseConnectionPoolRepositoryProvider.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Distantmagic\Resonance\SingletonProvider;
 
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\Attribute\Singleton;
 use Distantmagic\Resonance\DatabaseConfiguration;
 use Distantmagic\Resonance\DatabaseConnectionPoolRepository;
@@ -17,6 +18,7 @@ use Swoole\Database\PDOConfig;
 /**
  * @template-extends SingletonProvider<DatabaseConnectionPoolRepository>
  */
+#[RequiresPhpExtension('pdo')]
 #[Singleton(provides: DatabaseConnectionPoolRepository::class)]
 final readonly class DatabaseConnectionPoolRepositoryProvider extends SingletonProvider
 {
diff --git a/src/SingletonProvider/DoctrineEntityManagerRepositoryProvider.php b/src/SingletonProvider/DoctrineEntityManagerRepositoryProvider.php
index 587ab4f6aae342e7a29b1fcb8293a62ac419e5b4..41a8fb9303efc16a27c314f24707eabc7d63f040 100644
--- a/src/SingletonProvider/DoctrineEntityManagerRepositoryProvider.php
+++ b/src/SingletonProvider/DoctrineEntityManagerRepositoryProvider.php
@@ -6,6 +6,7 @@ namespace Distantmagic\Resonance\SingletonProvider;
 
 use Distantmagic\Resonance\ApplicationConfiguration;
 use Distantmagic\Resonance\Attribute\GrantsFeature;
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\Attribute\Singleton;
 use Distantmagic\Resonance\DoctrineConnectionRepository;
 use Distantmagic\Resonance\DoctrineEntityManagerRepository;
@@ -21,6 +22,7 @@ use Symfony\Component\Filesystem\Filesystem;
 /**
  * @template-extends SingletonProvider<DoctrineEntityManagerRepository>
  */
+#[RequiresPhpExtension('pdo')]
 #[GrantsFeature(Feature::Doctrine)]
 #[Singleton(provides: DoctrineEntityManagerRepository::class)]
 final readonly class DoctrineEntityManagerRepositoryProvider extends SingletonProvider
diff --git a/src/SingletonProvider/PDOPoolConnectionBuilderCollectionProvider.php b/src/SingletonProvider/PDOPoolConnectionBuilderCollectionProvider.php
index 740218972742e8eeb45291c64d0f40f7158b4d40..1820af1fbd630b559b37053911667ed92872274d 100644
--- a/src/SingletonProvider/PDOPoolConnectionBuilderCollectionProvider.php
+++ b/src/SingletonProvider/PDOPoolConnectionBuilderCollectionProvider.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Distantmagic\Resonance\SingletonProvider;
 
 use Distantmagic\Resonance\Attribute\BuildsPDOPoolConnection;
+use Distantmagic\Resonance\Attribute\RequiresPhpExtension;
 use Distantmagic\Resonance\Attribute\RequiresSingletonCollection;
 use Distantmagic\Resonance\Attribute\Singleton;
 use Distantmagic\Resonance\HttpResponderCollection;
@@ -19,6 +20,7 @@ use Distantmagic\Resonance\SingletonProvider;
 /**
  * @template-extends SingletonProvider<HttpResponderCollection>
  */
+#[RequiresPhpExtension('pdo')]
 #[RequiresSingletonCollection(SingletonCollection::PDOPoolConnectionBuilder)]
 #[Singleton(provides: PDOPoolConnectionBuilderCollection::class)]
 final readonly class PDOPoolConnectionBuilderCollectionProvider extends SingletonProvider