diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2024 goosedb
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,118 @@
+# Import style GHC plugin
+
+The plugin helps to unify imports style via compile warnings/errors
+
+Tested with:
+  * 9.2.7
+  * 9.4.8
+  * 9.6.4
+  * 9.8.2
+
+## Usage
+
+### Yaml config
+#### Enable plugin in your cabal file:
+```cabal
+  build-depends: ..., import-style-plugin, ...
+
+  ghc-options: -fplugin ImportStylePlugin.Yaml -fplugin-opt ImportStylePlugin.Yaml:<path-to-config>
+```
+#### Write config:
+
+```yaml
+qualificationStyle: Post | Pre | null
+
+bannedModules: # can be null
+  Module.Name:
+    severity: Error | Warning
+    why: string with ban reason
+
+importRules: # can be null
+  DataModuleName:
+    severity: Error | Warning
+    rules:
+      - qualification: Required | Forbidden | null
+        aliases:
+          # Either `exactly`, so alias can't be omitted 
+          exactly:
+            - Module.Alias
+            - Alias
+          # or just list, so alias can be omitted
+          - Module.Alias
+          - Alias
+          # or null
+        importedNames:
+          # Either whitelist, so import should be 
+          # accompanied with explicit import list 
+          whitelist:
+            - pattern (:|>) 
+            - type (+)
+            - foo
+          # or blacklist, so import should be 
+          # accompanied with explicit hiding list with all these names
+          # or explicit import list shouldn't contain any of these names 
+          blacklist:
+            - pattern (:|>) 
+            - type (+)
+            - foo
+          # or null
+```
+#### Here is example:
+```yaml
+qualificationStyle: Post
+
+bannedModules:
+  # Forbids Data.Map at all
+  Data.Map:
+    severity: Error
+    why: Use 'Data.Map.Strict'
+
+importRules:
+  # Either
+  # import Data.Text (Text)
+  # or
+  # import Data.Text qualified as Text
+  Data.Text:
+    severity: Warning
+    rules:
+      - qualification: Required
+        aliases:
+          exactly: 
+            - Text
+
+      - qualification: Forbidden
+        importedNames:
+          whitelist: 
+            - Text
+```
+### Derived plugin
+It can be useful if you have a lot of projects with one style. So you haven't to copy paste config but just use _style_ as library
+
+  1. Create library package
+  2. Add to deps `import-style-plugin`
+  3. Create module `YourPlugin.hs` (or something like that, it's unimportant)
+  4. Put there 
+      ```haskell
+      module YourPlugin where
+      
+      import ImportStylePlugin.Derived qualified as Derived
+      import ImportStylePlugin.Config as Cfg
+
+      -- variable name `plugin` IS important
+      -- Configure your style with Haskell types from `ImportStylePlugin.Config`
+      plugin = Derived.plugin
+        ImportsStyle
+          { qualificationStyle = ...
+          , bannedModules = ...
+          , importRules = ... 
+          }
+      ```
+  5. Plug in
+      ```cabal
+        build-depends: ..., your-import-style-plugin, ...
+
+        ghc-options: -fplugin YourPlugin
+      ```
+## Tips
+### Custom warning
+Since ghc-9.8 the plugin introduces custom warning `x-import-style`. So if you set `-Werror` but still want to style warnings be warnings use ghc option `-Wwarn=x-import-style`. You also can disable at all warnings with `-Wno-x-import-style`
diff --git a/import-style-plugin.cabal b/import-style-plugin.cabal
new file mode 100644
--- /dev/null
+++ b/import-style-plugin.cabal
@@ -0,0 +1,31 @@
+cabal-version:      3.0
+name:               import-style-plugin
+version:            0.1.0.0
+license:            MIT
+license-file:       LICENSE
+author:             goosedb
+maintainer:         goosedb@yandex.ru
+build-type:         Simple
+extra-doc-files:    CHANGELOG.md, README.md
+homepage:           https://github.com/goosedb/import-style-plugin
+
+category: Development
+synopsis: Helps maintain consistency of imports
+
+common warnings
+    ghc-options: -Wall
+
+library
+    import:           warnings
+    exposed-modules:  ImportStylePlugin
+                    , ImportStylePlugin.Yaml
+                    , ImportStylePlugin.Derived
+                    , ImportStylePlugin.Config
+                    , ImportStylePlugin.Compat
+    build-depends:    base >=4.11 && <4.20
+                    , ghc >= 9.2 && < 9.10
+                    , containers
+                    , yaml
+                    , aeson
+    hs-source-dirs:   src
+    default-language: Haskell2010
diff --git a/src/ImportStylePlugin.hs b/src/ImportStylePlugin.hs
new file mode 100644
--- /dev/null
+++ b/src/ImportStylePlugin.hs
@@ -0,0 +1,105 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ViewPatterns #-}
+module ImportStylePlugin where
+
+import Control.Monad (forM_, unless)
+import Data.Functor (($>), (<&>))
+import Data.List (intercalate, intersperse)
+import qualified Data.Map.Strict as Map
+import Data.Maybe (catMaybes)
+import qualified Data.Set as Set
+import Data.String (fromString)
+import qualified GHC.Hs as Ghc
+import qualified GHC.Plugins as Ghc
+import qualified GHC.Tc.Types as Ghc
+import ImportStylePlugin.Compat (getExplicitlyHiddenNames, getExplicitlyImportedNames, report)
+import ImportStylePlugin.Config as Config
+
+importPlugin ::
+  ImportsStyle ->
+  Ghc.TcGblEnv ->
+  Ghc.TcM Ghc.TcGblEnv
+importPlugin ImportsStyle{..} e@Ghc.TcGblEnv{tcg_rn_imports} = corrections $> e
+ where
+  mkWord = \case
+    Error -> "must"
+    Warning -> "should"
+
+  corrections = forM_ tcg_rn_imports \(Ghc.L Ghc.SrcSpanAnn{..} importDecl@Ghc.ImportDecl{..}) ->
+    let (Ghc.L _ (Ghc.moduleNameString -> moduleName)) = ideclName
+     in do
+          case qualificationStyle of
+            _ | ideclQualified == Ghc.NotQualified -> pure ()
+            Just Pre | ideclQualified /= Ghc.QualifiedPre -> report Error "Use prefix qualification" (Just locA)
+            Just Post | ideclQualified /= Ghc.QualifiedPost -> report Error "Use postfix qualification" (Just locA)
+            _ -> pure ()
+
+          case Map.lookup (Config.ModuleName moduleName) bannedModules of
+            Just Ban{..} -> report severity (fromString why) (Just locA)
+            Nothing -> maybe
+              do pure ()
+              do
+                \ImportRules{..} -> unless (any (isImportValid importDecl) rules) do
+                  let header = fromString $ "Import " <> mkWord severity <> " satisfy the following rules:"
+                  let allStringRules = Ghc.vcat $ (header :) $ intersperse "or" $ map (Ghc.vcat . stringifyRule severity) rules
+                  report severity allStringRules (Just locA)
+              do Map.lookup (Config.ModuleName moduleName) importRules
+
+  stringifyRule severity ImportRule{..} =
+    map (fromString . ("  * " <>)) $
+      catMaybes
+        [ qualification <&> \case
+            Required -> "Module " <> word <> " be qualified"
+            Forbidden -> "Module " <> word <> " not be qualified"
+        , let common (Set.toList -> allowed) =
+                "Alias " <> word <> " be " <> case allowed of
+                  [name] -> name
+                  _ -> "one of " <> commaSep allowed
+           in aliases <&> \case
+                Exactly allowed -> if Set.null allowed then "Import " <> word <> " not have alias" else common allowed
+                OrOmitted allowed -> common allowed <> " or alias can be omitted"
+        , importedNames <&> \case
+            BlackList names -> "Module " <> word <> " not import the following names: " <> printNamesList names
+            WhiteList names | Set.null names -> "Module" <> word <> " not import names explicitly"
+            WhiteList names -> "Module " <> word <> " import only the following names: " <> printNamesList names
+        ]
+   where
+    printNamesList = commaSep . Set.toList
+    commaSep = intercalate ", "
+
+    word = mkWord severity
+
+  isImportValid :: Ghc.ImportDecl Ghc.GhcRn -> ImportRule -> Bool
+  isImportValid decl@Ghc.ImportDecl{..} ImportRule{..} =
+    isQualificationValid
+      && areImportedNamesValid
+      && isAliasValid
+   where
+    isQualificationValid = case qualification of
+      Just Required -> ideclQualified /= Ghc.NotQualified
+      Just Forbidden -> ideclQualified == Ghc.NotQualified
+      Nothing -> True
+
+    isAliasValid = case aliases of
+      Just (Exactly allowedAliases) -> oneOfAllowed False allowedAliases
+      Just (OrOmitted allowedAliases) -> oneOfAllowed True allowedAliases
+      Nothing -> True
+     where
+      oneOfAllowed def allowed = maybe
+        do def
+        do \(Ghc.L _ name) -> Ghc.moduleNameString name `Set.member` allowed
+        do ideclAs
+
+    areImportedNamesValid = case importedNames of
+      Just (WhiteList allowedNames) ->
+        maybe False (all (`Set.member` allowedNames)) (getExplicitlyImportedNames decl)
+      Just (BlackList forbiddenNames) ->
+        maybe True (Set.null . (forbiddenNames `Set.difference`) . Set.fromList) (getExplicitlyHiddenNames decl)
+          && maybe False (not . any (`Set.member` forbiddenNames)) (getExplicitlyImportedNames decl)
+      Nothing -> True
diff --git a/src/ImportStylePlugin/Compat.hs b/src/ImportStylePlugin/Compat.hs
new file mode 100644
--- /dev/null
+++ b/src/ImportStylePlugin/Compat.hs
@@ -0,0 +1,152 @@
+{- FOURMOLU_DISABLE -}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+module ImportStylePlugin.Compat where
+
+#if __GLASGOW_HASKELL__ >= 908 && __GLASGOW_HASKELL__ < 910
+import qualified GHC as Ghc
+import qualified GHC.Utils.Outputable as Ghc
+import qualified GHC.Types.Error as Ghc
+import qualified GHC.Tc.Utils.Monad as Ghc
+import qualified GHC.Tc.Errors.Types as Ghc
+import qualified GHC.Unit.Module.Warnings as Ghc
+#endif
+
+#if __GLASGOW_HASKELL__ >= 906 && __GLASGOW_HASKELL__ < 908
+import qualified GHC as Ghc
+import qualified GHC.Types.Error as Ghc
+import qualified GHC.Tc.Utils.Monad as Ghc
+import qualified GHC.Tc.Errors.Types as Ghc
+import qualified GHC.Utils.Outputable as Ghc
+#endif
+
+#if __GLASGOW_HASKELL__ >= 904 && __GLASGOW_HASKELL__ < 906
+import qualified GHC as Ghc
+import qualified GHC.Tc.Types as Ghc
+import qualified GHC.Utils.Error as Ghc
+import qualified GHC.Tc.Utils.Monad as Ghc
+import qualified GHC.Tc.Errors.Types as Ghc
+import qualified GHC.Types.Error as Ghc
+import qualified GHC.Driver.Ppr as Ghc
+#endif
+
+#if __GLASGOW_HASKELL__ >= 902 && __GLASGOW_HASKELL__ < 904
+import qualified GHC as Ghc
+import qualified GHC.Utils.Outputable as Ghc
+import qualified GHC.Tc.Utils.Monad as Ghc
+import qualified GHC.Driver.Flags as Ghc
+#endif
+
+import ImportStylePlugin.Config as Cfg
+
+getRawNames :: [Ghc.GenLocated l (Ghc.IE Ghc.GhcRn)] -> [String]
+getRawNames names =
+  [ Ghc.showPprUnsafe  n
+  | Ghc.L
+      _
+      n <-
+      names
+  ] 
+
+getExplicitlyImportedNames :: Ghc.ImportDecl Ghc.GhcRn -> Maybe [String]
+getExplicitlyImportedNames Ghc.ImportDecl {..} =
+  case 
+#if __GLASGOW_HASKELL__ >= 906 
+    ideclImportList 
+#else 
+    ideclHiding
+#endif
+  of
+    Just 
+      (
+#if __GLASGOW_HASKELL__ >= 906 
+        Ghc.Exactly
+#else 
+        False
+#endif
+      , Ghc.L _ names
+      ) -> Just (getRawNames names)
+    _ -> Nothing
+
+getExplicitlyHiddenNames :: Ghc.ImportDecl Ghc.GhcRn -> Maybe [String]
+getExplicitlyHiddenNames Ghc.ImportDecl {..} =
+  case 
+#if __GLASGOW_HASKELL__ >= 906 
+    ideclImportList 
+#else 
+    ideclHiding
+#endif
+  of
+    Just 
+      ( 
+#if __GLASGOW_HASKELL__ >= 906 
+        Ghc.EverythingBut
+#else 
+        True
+#endif
+      , Ghc.L _ names) -> Just (getRawNames names)
+    _ -> Nothing
+
+
+
+#if __GLASGOW_HASKELL__ >= 908 && __GLASGOW_HASKELL__ < 910
+report :: Severity -> Ghc.SDoc -> Maybe Ghc.SrcSpan -> Ghc.TcRn ()
+report severity msg loc =
+  maybe Ghc.addDiagnostic Ghc.addDiagnosticAt loc
+    . Ghc.TcRnUnknownMessage
+    . Ghc.mkSimpleUnknownDiagnostic
+    $ Ghc.DiagnosticMessage
+      { Ghc.diagMessage = Ghc.mkSimpleDecorated msg
+      , Ghc.diagReason = case severity of
+          Error -> Ghc.ErrorWithoutFlag
+          Warning -> Ghc.WarningWithCategory (Ghc.WarningCategory "x-import-style")
+      , Ghc.diagHints = []
+      }
+#endif
+
+#if __GLASGOW_HASKELL__ >= 906 && __GLASGOW_HASKELL__ < 908
+report :: Severity -> Ghc.SDoc -> Maybe Ghc.SrcSpan -> Ghc.TcRn ()
+report severity msg loc =
+  maybe Ghc.addDiagnostic Ghc.addDiagnosticAt loc
+    . Ghc.TcRnUnknownMessage
+    . Ghc.UnknownDiagnostic
+    $ Ghc.DiagnosticMessage
+      { Ghc.diagMessage = Ghc.mkSimpleDecorated msg
+      , Ghc.diagReason = case severity of
+          Error -> Ghc.ErrorWithoutFlag
+          Warning -> Ghc.WarningWithoutFlag 
+      , Ghc.diagHints = []
+      }
+#endif
+
+    
+#if __GLASGOW_HASKELL__ >= 904 && __GLASGOW_HASKELL__ < 906
+report :: Severity -> Ghc.SDoc -> Maybe Ghc.SrcSpan -> Ghc.TcRn ()
+report severity msg loc = 
+  maybe Ghc.addDiagnostic Ghc.addDiagnosticAt loc
+    . Ghc.TcRnUnknownMessage
+    $ Ghc.DiagnosticMessage
+      { Ghc.diagMessage = Ghc.mkSimpleDecorated msg
+      , Ghc.diagReason = case severity of
+          Error -> Ghc.ErrorWithoutFlag
+          Warning -> Ghc.WarningWithoutFlag
+      , Ghc.diagHints = []
+      }
+#endif
+
+
+#if __GLASGOW_HASKELL__ >= 902 && __GLASGOW_HASKELL__ < 904
+report :: Severity -> Ghc.SDoc -> Maybe Ghc.SrcSpan -> Ghc.TcRn ()
+report severity msg loc = 
+  maybe 
+    do case severity of
+        Error -> Ghc.addErr msg
+        Warning -> Ghc.addWarn Ghc.NoReason msg
+    do \l -> case severity of
+        Error -> Ghc.addErrAt l msg
+        Warning -> Ghc.addWarnAt Ghc.NoReason l msg
+    do loc 
+#endif
diff --git a/src/ImportStylePlugin/Config.hs b/src/ImportStylePlugin/Config.hs
new file mode 100644
--- /dev/null
+++ b/src/ImportStylePlugin/Config.hs
@@ -0,0 +1,77 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module ImportStylePlugin.Config where
+
+import Control.Applicative ((<|>))
+import Data.Aeson (FromJSON (..), FromJSONKey, Value (..), withObject, (.:))
+import qualified Data.Map.Strict as Map
+import Data.Set (Set)
+import Data.String (IsString)
+import GHC.Generics (Generic)
+
+-- | Report either error or warning during compilation
+data Severity = Error | Warning
+  deriving (Generic, FromJSON, Show)
+
+data Qualification = Required | Forbidden
+  deriving (Generic, FromJSON, Show)
+
+data ModuleAliases = Exactly (Set String) | OrOmitted (Set String)
+  deriving (Show)
+
+data NamesList = BlackList (Set String) | WhiteList (Set String)
+  deriving (Show)
+
+data ImportRule = ImportRule
+  { qualification :: Maybe Qualification
+  -- ^ @'Nothing'@ means "don\'t care"
+  , aliases :: Maybe ModuleAliases
+  -- ^ @'Nothing'@ means "don\'t care"
+  , importedNames :: Maybe NamesList
+  -- ^ @'Nothing'@ means "don\'t care"
+  }
+  deriving (Generic, FromJSON, Show)
+
+data QualificationStyle = Post | Pre
+  deriving (Generic, FromJSON, Show)
+
+data ImportRules = ImportRules
+  { rules :: [ImportRule]
+  , severity :: Severity
+  }
+  deriving (Generic, FromJSON, Show)
+
+data Ban = Ban
+  { severity :: Severity
+  , why :: String
+  -- ^ ban reason
+  }
+  deriving (Generic, FromJSON, Show)
+
+data ImportsStyle
+  = ImportsStyle
+  { qualificationStyle :: Maybe QualificationStyle
+  -- ^ @'Nothing'@ means "don\'t care"
+  , bannedModules :: Map.Map ModuleName Ban
+  , importRules :: Map.Map ModuleName ImportRules
+  }
+  deriving (Generic, FromJSON, Show)
+
+newtype ModuleName = ModuleName String
+  deriving (Eq, Ord)
+  deriving newtype (FromJSONKey, IsString, Show)
+
+instance FromJSON NamesList where
+  parseJSON = withObject "NamesList" \o ->
+    BlackList <$> o .: "blacklist" <|> WhiteList <$> o .: "whitelist"
+
+instance FromJSON ModuleAliases where
+  parseJSON (Object o) = Exactly <$> o .: "exactly"
+  parseJSON o = OrOmitted <$> parseJSON o
diff --git a/src/ImportStylePlugin/Derived.hs b/src/ImportStylePlugin/Derived.hs
new file mode 100644
--- /dev/null
+++ b/src/ImportStylePlugin/Derived.hs
@@ -0,0 +1,12 @@
+module ImportStylePlugin.Derived where
+
+import qualified GHC.Plugins as Ghc
+import ImportStylePlugin
+import ImportStylePlugin.Config
+
+plugin :: ImportsStyle -> Ghc.Plugin
+plugin style =
+  Ghc.defaultPlugin
+    { Ghc.typeCheckResultAction = \_ _ c -> importPlugin style c
+    , Ghc.pluginRecompile = Ghc.purePlugin
+    }
diff --git a/src/ImportStylePlugin/Yaml.hs b/src/ImportStylePlugin/Yaml.hs
new file mode 100644
--- /dev/null
+++ b/src/ImportStylePlugin/Yaml.hs
@@ -0,0 +1,24 @@
+{-# LANGUAGE LambdaCase #-}
+
+module ImportStylePlugin.Yaml where
+
+import Control.Monad.IO.Class (MonadIO (..))
+import Data.Functor (($>))
+import Data.String (IsString (..))
+import Data.Yaml (decodeFileEither, prettyPrintParseException)
+import qualified GHC.Plugins as Ghc
+import ImportStylePlugin (importPlugin)
+import ImportStylePlugin.Compat (report)
+import ImportStylePlugin.Config (Severity (..))
+
+plugin :: Ghc.Plugin
+plugin =
+  Ghc.defaultPlugin
+    { Ghc.typeCheckResultAction = \opts _ a -> do
+        case opts of
+          [filepath] -> do
+            liftIO (decodeFileEither filepath) >>= \case
+              Right style -> importPlugin style a
+              Left err -> report Warning (fromString $ prettyPrintParseException err) Nothing $> a
+          _ -> pure a
+    }
