diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,10 @@
 # Revision history for selda
 
+## 0.1.4.0 -- 2017-05-04
+
+* Add uniqueness constraints and foreign keys.
+
+
 ## 0.1.3.3 -- 2017-05-04
 
 * Fix cache invalidation race when using transactions.
diff --git a/selda.cabal b/selda.cabal
--- a/selda.cabal
+++ b/selda.cabal
@@ -1,5 +1,5 @@
 name:                selda
-version:             0.1.3.3
+version:             0.1.4.0
 synopsis:            Type-safe, high-level EDSL for interacting with relational databases.
 description:         This package provides an EDSL for writing portable, type-safe, high-level
                      database code. Its feature set includes querying and modifying databases,
@@ -62,6 +62,7 @@
     Database.Selda.SqlType
     Database.Selda.Table
     Database.Selda.Table.Compile
+    Database.Selda.Table.Foreign
     Database.Selda.Transform
     Database.Selda.Types
   other-extensions:
diff --git a/src/Database/Selda.hs b/src/Database/Selda.hs
--- a/src/Database/Selda.hs
+++ b/src/Database/Selda.hs
@@ -100,6 +100,7 @@
   , table, tableWithSelectors, selectors
   , required, optional
   , primary, autoPrimary
+  , fk, unique
     -- * Creating and dropping tables
   , createTable, tryCreateTable
   , dropTable, tryDropTable
@@ -125,6 +126,7 @@
 import Database.Selda.SqlType
 import Database.Selda.Table
 import Database.Selda.Table.Compile
+import Database.Selda.Table.Foreign
 import Database.Selda.Types
 import Database.Selda.Unsafe
 import Control.Exception (throw)
diff --git a/src/Database/Selda/Generic.hs b/src/Database/Selda/Generic.hs
--- a/src/Database/Selda/Generic.hs
+++ b/src/Database/Selda/Generic.hs
@@ -260,12 +260,13 @@
         { colName = mkColName . pack $ selName ((M1 undefined) :: M1 S c a b)
         , colType = colType ci
         , colAttrs = colAttrs ci
+        , colFKs = colFKs ci
         }
   gMkDummy = M1 <$> gMkDummy
 
 instance (Traits a, SqlType a) => GRelation (K1 i a) where
   gToRel (K1 x) = x
-  gTblCols _    = [ColInfo "" (sqlType (Proxy :: Proxy a)) optReq]
+  gTblCols _    = [ColInfo "" (sqlType (Proxy :: Proxy a)) optReq []]
     where
       optReq
         | isMaybeType (Proxy :: Proxy a) = [Optional]
diff --git a/src/Database/Selda/Selectors.hs b/src/Database/Selda/Selectors.hs
--- a/src/Database/Selda/Selectors.hs
+++ b/src/Database/Selda/Selectors.hs
@@ -38,8 +38,9 @@
 with = foldl' upd
 
 -- | A column selector. Column selectors can be used together with the '!' and
---   '!=' operators to get and set values on inductive tuples.
-newtype Selector t a = Selector Int
+--   'with' functions to get and set values on inductive tuples, or to indicate
+--   foreign keys.
+data Selector t a = Selector Int
 
 -- | The inductive tuple of selectors for a table of type @a@.
 type family Selectors t a where
diff --git a/src/Database/Selda/Table.hs b/src/Database/Selda/Table.hs
--- a/src/Database/Selda/Table.hs
+++ b/src/Database/Selda/Table.hs
@@ -44,6 +44,7 @@
   { colName  :: !ColName
   , colType  :: !Text
   , colAttrs :: ![ColAttr]
+  , colFKs   :: ![(Table (), ColName)]
   }
 
 newCol :: forall a. SqlType a => ColName -> ColSpec a
@@ -51,6 +52,7 @@
   { colName  = name
   , colType  = sqlType (Proxy :: Proxy a)
   , colAttrs = []
+  , colFKs   = []
   }]
 
 -- | A table column specification.
@@ -75,7 +77,7 @@
 --   When adding elements, make sure that they are added in the order
 --   required by SQL syntax, as this list is only sorted before being
 --   pretty-printed.
-data ColAttr = Primary | AutoIncrement | Required | Optional
+data ColAttr = Primary | AutoIncrement | Required | Optional | Unique
   deriving (Show, Eq, Ord)
 
 -- | A non-nullable column with the given name.
@@ -83,25 +85,31 @@
 required = addAttr Required . newCol
 
 -- | A nullable column with the given name.
-optional :: SqlType a => ColName -> ColSpec (Maybe a)
+optional :: NonNull a => ColName -> ColSpec (Maybe a)
 optional = addAttr Optional . newCol
 
 -- | Marks the given column as the table's primary key.
 --   A table may only have one primary key; marking more than one key as
---   primary will result in a run-time error.
+--   primary will result in 'ValidationError' during validation.
 primary :: NonNull a => ColName -> ColSpec a
-primary = addAttr Primary . required
+primary = addAttr Primary . unique . required
 
 -- | Automatically increment the given attribute if not specified during insert.
---   Also adds the @PRIMARY KEY@ attribute on the column.
+--   Also adds the @PRIMARY KEY@ and @UNIQUE@ attributes on the column.
 autoPrimary :: ColName -> ColSpec Int
-autoPrimary n = ColSpec [c {colAttrs = [Primary, AutoIncrement, Required]}]
+autoPrimary n = ColSpec [c {colAttrs = [Primary, AutoIncrement, Required, Unique]}]
   where ColSpec [c] = newCol n :: ColSpec Int
 
+-- | Add a uniqueness constraint to the given column.
+--   Adding a uniqueness constraint to a column that is already implied to be
+--   unique, such as a primary key, is a no-op.
+unique :: SqlType a => ColSpec a -> ColSpec a
+unique = addAttr Unique
+
 -- | Add an attribute to a column. Not for public consumption.
 addAttr :: SqlType a => ColAttr -> ColSpec a -> ColSpec a
 addAttr attr (ColSpec [ci]) = ColSpec [ci {colAttrs = attr : colAttrs ci}]
-addAttr _ _                 = error "impossible: SqlType ColSpec with several columns"
+addAttr _ _                 = error "impossible: ColSpec with several columns"
 
 -- | An inductive tuple where each element is a column specification.
 type family ColSpecs a where
@@ -154,6 +162,7 @@
       , nulIdents
       , emptyIdents
       , emptyTableName
+      , nonPkFks
       ]
     emptyTableName
       | fromTableName name == "\"\"" = ["table name is empty"]
@@ -172,6 +181,15 @@
       ["duplicate column: " <> fromColName x | (x:_:_) <- soup $ map colName cis]
     pkDupes =
       ["multiple primary keys" | (Primary:_:_) <- soup $ concatMap colAttrs cis]
+    nonPkFks =
+      [ "column is used as a foreign key, but is not a primary key of its table: "
+          <> fromTableName ftn <> "." <> fromColName fcn
+      | ci <- cis
+      , (Table ftn fcs, fcn) <- colFKs ci
+      , fc <- fcs
+      , colName fc == fcn
+      , not (Unique `elem` colAttrs fc)
+      ]
 
     -- This should be impossible, but...
     optionalRequiredMutex =
diff --git a/src/Database/Selda/Table/Compile.hs b/src/Database/Selda/Table/Compile.hs
--- a/src/Database/Selda/Table/Compile.hs
+++ b/src/Database/Selda/Table/Compile.hs
@@ -17,12 +17,26 @@
 compileCreateTable customColType ifex tbl = mconcat
   [ "CREATE TABLE ", ifNotExists ifex, fromTableName (tableName tbl), "("
   , intercalate ", " (map (compileTableCol customColType) (tableCols tbl))
+  , case allFKs of
+      [] -> ""
+      _  -> ", " <> intercalate ", " compFKs
   , ")"
   ]
   where
     ifNotExists Fail   = ""
     ifNotExists Ignore = "IF NOT EXISTS "
+    allFKs = [(colName ci, fk) | ci <- tableCols tbl, fk <- colFKs ci]
+    compFKs = zipWith (uncurry compileFK) allFKs [0..]
 
+-- | Compile a foreign key constraint.
+compileFK :: ColName -> (Table (), ColName) -> Int -> Text
+compileFK col (Table ftbl _, fcol) n = mconcat
+  [ "CONSTRAINT ", fkName, " FOREIGN KEY (", fromColName col, ") "
+  , "REFERENCES ", fromTableName ftbl, "(", fromColName fcol, ")"
+  ]
+  where
+    fkName = fromColName $ addColPrefix col ("fk" <> pack (show n) <> "_")
+
 -- | Compile a table column.
 compileTableCol :: (Text -> [ColAttr] -> Maybe Text) -> ColInfo -> Text
 compileTableCol customColType ci = Text.unwords
@@ -98,3 +112,4 @@
 compileColAttr AutoIncrement = "AUTOINCREMENT"
 compileColAttr Required      = "NOT NULL"
 compileColAttr Optional      = "NULL"
+compileColAttr Unique        = "UNIQUE"
diff --git a/src/Database/Selda/Table/Foreign.hs b/src/Database/Selda/Table/Foreign.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Selda/Table/Foreign.hs
@@ -0,0 +1,18 @@
+-- | Foreign key support.
+module Database.Selda.Table.Foreign where
+import Database.Selda.Selectors
+import Database.Selda.Table
+
+-- | Add a foreign key constraint to the given column, referencing
+--   the column indicated by the given table and selector.
+--   If the referenced column is not a primary key or has a
+--   uniqueness constraint, a 'ValidationError' will be thrown
+--   during validation.
+fk :: ColSpec a -> (Table t, Selector t a) -> ColSpec a
+fk (ColSpec [c]) (Table tn tcs, Selector i) =
+    ColSpec [c {colFKs = thefk : colFKs c}]
+  where
+    thefk = (Table tn tcs, colName (tcs !! i))
+fk _ _ =
+  error "impossible: ColSpec with several columns"
+
diff --git a/src/Database/Selda/Types.hs b/src/Database/Selda/Types.hs
--- a/src/Database/Selda/Types.hs
+++ b/src/Database/Selda/Types.hs
@@ -8,7 +8,7 @@
   ( (:*:)(..), Head, Append (..), (:++:), ToDyn (..), Tup (..)
   , first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth
   , ColName, TableName
-  , mkColName, mkTableName, addColSuffix
+  , mkColName, mkTableName, addColSuffix, addColPrefix
   , fromColName, fromTableName
   ) where
 import Data.Dynamic
@@ -31,6 +31,10 @@
 -- | Name of a database table.
 newtype TableName = TableName Text
   deriving (Ord, Eq, Show, IsString)
+
+-- | Add a prefix to a column name.
+addColPrefix :: ColName -> Text -> ColName
+addColPrefix (ColName cn) s = ColName $ Data.Text.append s cn
 
 -- | Add a suffix to a column name.
 addColSuffix :: ColName -> Text -> ColName
