diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Changelog for ban-instance
+
+## 0.1.0.0 - 2019-11-08
+
+* Initial Release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Data61 (c) 2017
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Data61 nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,75 @@
+# ban-instance - For when a type should never be an instance of a class
+
+![Data61 Logo](https://raw.githubusercontent.com/qfpl/assets/master/data61-transparent-bg.png)
+
+[![Build Status](https://travis-ci.org/qfpl/ban-instance.svg?branch=master)](https://travis-ci.org/qfpl/ban-instance)
+
+## Synopsis
+
+```haskell
+{-# LANGUAGE TemplateHaskell #-}
+
+-- The generated code requires at least these extensions
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE UndecideableInstances #-}
+
+import Lanuage.Haskell.Instance.Ban
+
+data Foo = -- ...
+
+-- Declare that Foo should never have a ToJSON instance
+$(banInstance [t|ToJSON Foo|] "why ToJSON Foo should never be defined")
+```
+
+Code that attempts to use the banned instance will generate a custom
+error message:
+
+```
+   • Attempt to use banned instance (ToJSON Foo)
+      Reason for banning: why ToJSON Foo should never be defined
+      Instance banned at [moduleName] filePath:lineNumber
+```
+
+## Motivation
+
+Banning an instance allows the programmer to actively declare that
+this instance should never be defined, and provide a reason why. In
+terms of what programs the compiler will accept, banning an instance
+is the same as leaving it undefined.
+
+Our main use case is banning `ToJSON`/`FromJSON` instances on "core"
+data structures to ensure serialisation/deserialisation is defined at
+API boundaries. We have systems which send and receive values of
+similar types over multiple different APIs, and which need to vary
+their JSON representations independently to allow upgrades. Defining
+serialisation on core data types means that changes to the
+`ToJSON`/`FromJSON` instance can cause breakage at the API layer of
+some unrelated system, on the other side of the codebase. Better to
+ban `ToJSON`/`FromJSON` on the core data types, and define types for
+presentation that live alongside the rest of the API:
+
+```haskell
+-- In some "core types" module:
+data Foo = -- ...
+$(banInstance [t|ToJSON Foo|] "use a newtype wrapper at the API layer")
+$(banInstance [t|FromJSON Foo|] "use a newtype wrapper at the API layer")
+
+-- In the module for V1 of the API:
+newtype V1 a = V1 a
+
+instance ToJSON (V1 Foo) where -- ...
+instance FromJSON (V1 Foo) where -- ...
+
+-- In the module for V2 of the API:
+data V2 a = V2 a
+
+instance ToJSON (V2 Foo) where -- ...
+instance FromJSON (V2 Foo) where -- ...
+```
+
+## Limitations
+
+* There is currently no support for type classes with associated types
+  or associated data types.
+* Type quotations `[t|...|]` do not support free variables
+  ([GHC#5616](https://gitlab.haskell.org/ghc/ghc/issues/5616)).
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/ban-instance.cabal b/ban-instance.cabal
new file mode 100644
--- /dev/null
+++ b/ban-instance.cabal
@@ -0,0 +1,55 @@
+name:           ban-instance
+version:        0.1.0.0
+synopsis:       For when a type should never be an instance of a class
+description:
+  <<https://raw.githubusercontent.com/qfpl/assets/master/data61-transparent-bg.png>>
+  .
+  Banning an instance allows the programmer to actively declare that
+  an instance should never be defined, and provide a reason why:
+  .
+  @
+  data Foo = -- ...
+
+  -- Declare that Foo should never have a ToJSON instance
+  $(banInstance [t|ToJSON Foo|] "why ToJSON Foo should never be defined")
+  @
+
+category:       Haskell
+homepage:       https://github.com/qfpl/ban-instance#readme
+bug-reports:    https://github.com/qfpl/ban-instance/issues
+author:         Jack Kelly, Alex Mason
+maintainer:     jack.kelly@data61.csiro.au
+copyright:      Copyright (C) 2017 Data61
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+cabal-version:  >= 1.10
+tested-with: GHC == 8.0.2
+              || == 8.2.2
+              || == 8.4.4
+              || == 8.6.5
+              || == 8.8.1
+
+extra-source-files: ChangeLog.md
+                    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/qfpl/ban-instance
+
+library
+  hs-source-dirs: src
+  ghc-options: -Wall
+  build-depends: base >= 4.7 && < 4.14
+               , template-haskell >= 2.11 && < 2.16
+  exposed-modules: Language.Haskell.Instance.Ban
+  default-language: Haskell2010
+
+test-suite ban-instance-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  hs-source-dirs: test
+  ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
+  build-depends: base >= 4.7 && < 4.14
+               , ban-instance
+  default-language: Haskell2010
diff --git a/src/Language/Haskell/Instance/Ban.hs b/src/Language/Haskell/Instance/Ban.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Haskell/Instance/Ban.hs
@@ -0,0 +1,76 @@
+{-# LANGUAGE DataKinds       #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeOperators   #-}
+
+
+{-|
+Module      : Language.Haskell.Instance.Ban
+Description : Declare that a typeclass instance
+Copyright   : (c) 2017, Commonwealth Scientific and Industrial Research Organisation
+License     : BSD3
+Maintainer  : jack.kelly@data61.csiro.au
+Stability   : experimental
+Portability : Non-Portable
+-}
+
+module Language.Haskell.Instance.Ban (banInstance) where
+
+import Data.Maybe                 (mapMaybe)
+import GHC.TypeLits
+import Language.Haskell.TH.Lib
+import Language.Haskell.TH.Ppr
+import Language.Haskell.TH.Syntax
+
+-- TODO: Mark instances as deprecated so haddock sees them.
+-- TODO: Overlappable instances?
+-- | Ban an instance of a typeclass; code which tries to use the
+-- banned instance will fail at compile time. This works by generating
+-- an instance that depends on a custom type error:
+--
+-- @
+-- instance TypeError (..) => ToJSON Foo where
+--   ...
+-- @
+--
+-- Use it like this:
+--
+-- @
+-- \$(banInstance [t|ToJSON Foo|] "why ToJSON Foo should never be defined")
+-- @
+banInstance
+  :: TypeQ
+     -- ^ The instance you want to ban.
+     -- Most easily written with a type-quote: @[t|ToJSON Foo|]@
+  -> String -- ^ The reason that this instance is banned.
+  -> DecsQ
+banInstance constraintQ message = do
+  loc <- qLocation
+  ClassI (ClassD _ _ _ _ classDecs) _ <- className <$> constraintQ >>= reify
+  let context :: CxtQ
+      context = cxt [[t|TypeError ('Text "Attempt to use banned instance (" ':<>: 'ShowType $(constraintQ) ':<>: 'Text ")"
+                                  ':$$: 'Text "Reason for banning: " ':<>: 'Text $(symbol message)
+                                  ':$$: 'Text "Instance banned at " ':<>: 'Text $(symbol $ formatLocation loc)
+                                  ':$$: 'Text ""
+                                  )|]]
+  pure <$> instanceD context constraintQ (convertClassDecs classDecs)
+
+symbol :: String -> TypeQ
+symbol = litT . strTyLit
+
+formatLocation :: Loc -> String
+formatLocation Loc{..} = concat ["[", loc_package, ":", loc_module, "] ", loc_filename, ":",  show $ fst loc_start]
+
+className :: Type -> Name
+className topTy = go topTy where
+  go (AppT ty _) = className ty
+  go (ConT name) = name
+  go _ = error $ "Cannot determine class name for type: " ++ pprint topTy
+
+convertClassDecs :: [Dec] -> [DecQ]
+convertClassDecs = mapMaybe go where
+  -- TODO: Support type/data families?
+  go (SigD name _) = Just $ funD name [clause [] (normalB [|undefined|]) []]
+  go DefaultSigD{} = Nothing
+  go _ = error "Banning instances only supported for classes \
+               \that contain only functions. Patches welcome."
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,27 @@
+{-# OPTIONS_GHC -Wno-unused-matches -Wno-unused-top-binds #-}
+
+{-# LANGUAGE DataKinds              #-}
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE MultiParamTypeClasses  #-}
+{-# LANGUAGE RankNTypes             #-}
+{-# LANGUAGE TemplateHaskell        #-}
+{-# LANGUAGE TypeFamilies           #-}
+{-# LANGUAGE UndecidableInstances   #-}
+
+import Language.Haskell.Instance.Ban
+
+-- Test code generation. We define a class and ban an instance of it,
+-- checking that the generated code throws no warnings. If this file
+-- compiles cleanly, the "test" has passed.
+
+class TestClass a b | a -> b where
+  testFunction :: a -> b
+
+$(banInstance [t|TestClass Char Int|] "because it's really bad")
+
+-- Test that we haven't overlapped other instances by mistake.
+instance TestClass Int Int where
+  testFunction = const 0
+
+main :: IO ()
+main = const (testFunction '3' :: Int) $ pure ()
