diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,13 @@
+# hpqtypes-extras-1.10.1.0 (2020-01-09)
+* Add support for ON CONFLICT clause
+
 # hpqtypes-extras-1.10.0.0 (2019-11-05)
 * Implement `UuidT` Column Type ([#28](https://github.com/scrive/hpqtypes-extras/pull/28)).
 * Fix sqlValidateCheck and sqlValidateFK
 
 # hpqtypes-extras-1.9.0.1 (2019-06-04)
-* Create composite types automatically only if database is empty.
+* Create composite types automatically only if database is empty
+  ([#24](https://github.com/scrive/hpqtypes-extras/pull/24)).
 
 # hpqtypes-extras-1.9.0.0 (2019-05-22)
 * Extend `checkDatabaseAllowUnknownTables` to allow unknown composite
diff --git a/hpqtypes-extras.cabal b/hpqtypes-extras.cabal
--- a/hpqtypes-extras.cabal
+++ b/hpqtypes-extras.cabal
@@ -1,6 +1,6 @@
 cabal-version:       1.18
 name:                hpqtypes-extras
-version:             1.10.0.0
+version:             1.10.1.0
 synopsis:            Extra utilities for hpqtypes library
 description:         The following extras for hpqtypes library:
                      .
diff --git a/src/Database/PostgreSQL/PQTypes/SQL/Builder.hs b/src/Database/PostgreSQL/PQTypes/SQL/Builder.hs
--- a/src/Database/PostgreSQL/PQTypes/SQL/Builder.hs
+++ b/src/Database/PostgreSQL/PQTypes/SQL/Builder.hs
@@ -162,6 +162,9 @@
   , sqlLeftJoinOn
   , sqlRightJoinOn
   , sqlFullJoinOn
+  , sqlOnConflictDoNothing
+  , sqlOnConflictOnColumns
+  , sqlOnConflictOnColumnsDoNothing
   , sqlSet
   , sqlSetInc
   , sqlSetList
@@ -339,10 +342,11 @@
   }
 
 data SqlInsert = SqlInsert
-  { sqlInsertWhat   :: SQL
-  , sqlInsertSet    :: [(SQL, Multiplicity SQL)]
-  , sqlInsertResult :: [SQL]
-  , sqlInsertWith   :: [(SQL, SQL)]
+  { sqlInsertWhat       :: SQL
+  , sqlInsertOnConflict :: Maybe (SQL, Maybe SQL)
+  , sqlInsertSet        :: [(SQL, Multiplicity SQL)]
+  , sqlInsertResult     :: [SQL]
+  , sqlInsertWith       :: [(SQL, SQL)]
   }
 
 data SqlInsertSelect = SqlInsertSelect
@@ -444,15 +448,21 @@
     "INSERT INTO" <+> sqlInsertWhat cmd <+>
     parenthesize (sqlConcatComma (map fst (sqlInsertSet cmd))) <+>
     emitClausesSep "VALUES" "," (map sqlConcatComma (transpose (map (makeLongEnough . snd) (sqlInsertSet cmd)))) <+>
+    emitClauseOnConflict (sqlInsertOnConflict cmd) <+>
     emitClausesSepComma "RETURNING" (sqlInsertResult cmd)
    where
-      -- this is the longest list of values
-      longest = maximum (1 : (map (lengthOfEither . snd) (sqlInsertSet cmd)))
-      lengthOfEither (Single _) = 1
-      lengthOfEither (Many x)   = length x
-      makeLongEnough (Single x) = take longest (repeat x)
-      makeLongEnough (Many x)   = take longest (x ++ repeat "DEFAULT")
+     emitClauseOnConflict = \case
+       Nothing                   -> ""
+       Just (condition, maction) -> emitClause "ON CONFLICT" $
+         condition <+> "DO" <+> fromMaybe "NOTHING" maction
 
+     -- this is the longest list of values
+     longest = maximum (1 : (map (lengthOfEither . snd) (sqlInsertSet cmd)))
+     lengthOfEither (Single _) = 1
+     lengthOfEither (Many x)   = length x
+     makeLongEnough (Single x) = take longest (repeat x)
+     makeLongEnough (Many x)   = take longest (x ++ repeat "DEFAULT")
+
 instance Sqlable SqlInsertSelect where
   toSQLCommand cmd =
     "INSERT INTO" <+> sqlInsertSelectWhat cmd <+>
@@ -503,7 +513,7 @@
 
 sqlInsert :: SQL -> State SqlInsert () -> SqlInsert
 sqlInsert table refine =
-  execState refine (SqlInsert table mempty [] [])
+  execState refine (SqlInsert table Nothing mempty [] [])
 
 sqlInsertSelect :: SQL -> SQL -> State SqlInsertSelect () -> SqlInsertSelect
 sqlInsertSelect table from refine =
@@ -800,6 +810,22 @@
 
 instance SqlSet SqlInsertSelect where
   sqlSet1 cmd name v = cmd { sqlInsertSelectSet = sqlInsertSelectSet cmd ++ [(name, v)] }
+
+sqlOnConflictDoNothing :: MonadState SqlInsert m => m ()
+sqlOnConflictDoNothing = modify $ \cmd -> cmd
+  { sqlInsertOnConflict = Just ("", Nothing)
+  }
+
+sqlOnConflictOnColumns
+  :: Sqlable sql => MonadState SqlInsert m => [SQL] -> sql -> m ()
+sqlOnConflictOnColumns columns sql = modify $ \cmd -> cmd
+  { sqlInsertOnConflict = Just (parenthesize $ sqlConcatComma columns, Just $ toSQLCommand sql)
+  }
+
+sqlOnConflictOnColumnsDoNothing :: MonadState SqlInsert m => [SQL] -> m ()
+sqlOnConflictOnColumnsDoNothing columns = modify $ \cmd -> cmd
+  { sqlInsertOnConflict = Just (parenthesize $ sqlConcatComma columns, Nothing)
+  }
 
 sqlSetCmd :: (MonadState v m, SqlSet v) => SQL -> SQL -> m ()
 sqlSetCmd name sql = modify (\cmd -> sqlSet1 cmd name sql)
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -5,6 +5,7 @@
 import Control.Monad.IO.Class
 import Control.Monad.Trans.Control
 
+import Data.Either
 import Data.Monoid
 import Prelude
 import qualified Data.Text as T
@@ -466,6 +467,57 @@
     sqlResult "id"
   (bankIds :: [UUID]) <- fetchMany runIdentity
   liftIO $ assertEqual "INSERT into 'bank' table" 5 (length bankIds)
+
+  -- Try to insert with existing ID to check that ON CONFLICT works properly
+  let bankId = head bankIds
+      name = "Santander" :: T.Text
+      location = "Spain" :: T.Text
+
+  runQuery_ . sqlInsert "bank" $ do
+    sqlSet "id" bankId
+    sqlSet "name" name
+    sqlSet "location" location
+    sqlOnConflictOnColumns ["id"] . sqlUpdate "" $ do
+      sqlSet "name" name
+      sqlSet "location" location
+  runQuery_ . sqlSelect "bank" $ do
+    sqlResult "name"
+    sqlResult "location"
+    sqlWhereEq "id" bankId
+  details1 <- fetchOne id
+  liftIO $ assertEqual "INSERT ON CONFLICT updates" (name, location) details1
+
+  runQuery_ . sqlInsert "bank" $ do
+    sqlSet "id" bankId
+    sqlSet "name" ("" :: T.Text)
+    sqlSet "location" ("" :: T.Text)
+    sqlOnConflictDoNothing
+  runQuery_ . sqlSelect "bank" $ do
+    sqlResult "name"
+    sqlResult "location"
+    sqlWhereEq "id" bankId
+  details3 <- fetchOne id
+  liftIO $ assertEqual "INSERT ON CONFLICT does nothing (1)" (name, location) details3
+
+  runQuery_ . sqlInsert "bank" $ do
+    sqlSet "id" bankId
+    sqlSet "name" ("" :: T.Text)
+    sqlSet "location" ("" :: T.Text)
+    sqlOnConflictOnColumnsDoNothing ["id"]
+  runQuery_ . sqlSelect "bank" $ do
+    sqlResult "name"
+    sqlResult "location"
+    sqlWhereEq "id" bankId
+  details4 <- fetchOne id
+  liftIO $ assertEqual "INSERT ON CONFLICT does nothing (2)" (name, location) details4
+
+  -- If NO CONFLICT is not specified, make sure we throw an exception.
+  eres :: Either DBException () <- E.try . withSavepoint "testDBSchema" $ do
+    runQuery_ . sqlInsert "bank " $ do
+      sqlSet "id" bankId
+      sqlSet "name" name
+      sqlSet "location" location
+  liftIO $ assertBool "If ON CONFLICT is not specified an exception is thrown" (isLeft eres)
 
   -- Populate the 'bad_guy' table.
   runQuery_ . sqlInsert "bad_guy" $ do
