diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Changelog for composite-tuple
+
+## (v0.1.0.0)
+
+* Add composite tuple functions `toFst`, `toSnd`, `fmapToFst`, `fmapToSnd`, `traverseToFst`, traverseToSnd`, `fanout`, `fanoutM`.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author name here (c) 2020
+
+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 Author name here 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,8 @@
+# composite-tuple
+
+Tuple functions for [composite](https://hackage.haskell.org/package/composite),
+inspired by
+[relude](http://hackage.haskell.org/package/relude-0.7.0.0/docs/Relude-Extra-Tuple.html).
+
+It's possible these be made to work for vinyl in general, but at the moment
+they are composite-specific.
diff --git a/composite-tuple.cabal b/composite-tuple.cabal
new file mode 100644
--- /dev/null
+++ b/composite-tuple.cabal
@@ -0,0 +1,37 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.34.2.
+--
+-- see: https://github.com/sol/hpack
+
+name:           composite-tuple
+version:        0.1.0.0
+synopsis:       Tuple functions for composite records.
+description:    Tuple functions for composite records.
+category:       Data Structures
+author:         Daniel Firth
+maintainer:     dan.firth@homotopic.tech
+copyright:      2020 Daniel Firth
+license:        MIT
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://gitlab.com/homotopic-tech/composite-tuple
+
+library
+  exposed-modules:
+      Composite.Record.Tuple
+  other-modules:
+      Paths_composite_tuple
+  hs-source-dirs:
+      src
+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints
+  build-depends:
+      base >=4.7 && <5
+    , composite-base
+  default-language: Haskell2010
diff --git a/src/Composite/Record/Tuple.hs b/src/Composite/Record/Tuple.hs
new file mode 100644
--- /dev/null
+++ b/src/Composite/Record/Tuple.hs
@@ -0,0 +1,54 @@
+{-# LANGUAGE DataKinds     #-}
+{-# LANGUAGE GADTs         #-}
+{-# LANGUAGE TypeOperators #-}
+{- |
+   Module     : Composite.Record.Tuple
+   License    : MIT
+   Stability  : experimental
+
+Tuple functions for composite records, inspired by relude.
+-}
+module Composite.Record.Tuple (
+  toFst
+, toSnd
+, fmapToFst
+, fmapToSnd
+, traverseToFst
+, traverseToSnd
+, fanout
+, fanoutM
+) where
+
+import Composite.Record
+
+-- | Apply a function, with the result in the fst slot, and the value in the other.
+toFst :: (a -> b) -> a -> Record (s :-> b : s' :-> a : '[])
+toFst f x = f x :*: x :*: RNil
+
+-- | Apply a function with the result in the snd slot, and the value in the other.
+toSnd :: (a -> b) -> a -> Record (s :-> a : s' :-> b : '[])
+toSnd f x = x :*: f x :*: RNil
+
+-- | Like fmap, but also keep the original value in the snd position.
+fmapToFst :: Functor f => (a -> b) -> f a -> f (Record (s :-> b : s' :-> a : '[]))
+fmapToFst = fmap . toFst
+
+-- | Like fmap, but also keep the original value in the fst position.
+fmapToSnd :: Functor f => (a -> b) -> f a -> f (Record (s :-> a : s' :-> b : '[]))
+fmapToSnd = fmap . toSnd
+
+-- | Apply a function that returns a value inside of a functor, with the output in the first slot, the input in the second, and the entire tuple inside the functor.
+traverseToFst :: Functor m => (a -> m b) -> a -> m (Record (s :-> b : s' :-> a : '[]))
+traverseToFst f x =  (:*: x :*: RNil) <$> f x
+
+-- | Apply a function that returns a value inside of a functor, with the output in the second slot, the input in the fist, and the entire tuple inside the functor.
+traverseToSnd :: Functor m => (a -> m b) -> a -> m (Record (s :-> a : s' :-> b : '[]))
+traverseToSnd f x =  (\y -> x :*: y :*: RNil) <$> f x
+
+-- | Apply two functions to a single value and store the results in each slot.
+fanout :: (x -> a) -> (x -> b) -> x -> Record (s :-> a : s' :-> b : '[])
+fanout f g x = f x :*: g x :*: RNil
+
+-- | Apply two applicative functions to a single value and store the results in each slot.
+fanoutM :: Applicative m => (x -> m a) -> (x -> m b) -> x -> m (Record (s :-> a : s' :-> b : '[]))
+fanoutM f g x = (\y z -> y :*: z :*: RNil) <$> f x <*> g x
