diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Todd Mohney (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 Todd Mohney 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,29 @@
+# flipper
+
+A light-weight library providing an interface for minimally obtrusive feature
+toggles.
+
+This library provides the main user interface and an in-memory feature flag
+storage adapter.
+
+Persisted storage adapters (think Postgres, Redis, etc) will be created as
+separate packages.
+
+This library is heavily inspired by @jnunemaker's work on Ruby's [Flipper](https://github.com/jnunemaker/flipper).
+Thanks for paving the way!
+
+## Installation
+
+Add `feature-flipper` to your cabal file, then import `Control.Flipper` into
+your module.
+
+## Usage examples
+
+- [Configuring feature flags from environment variables](https://github.com/toddmohney/flipper/tree/master/examples/environment-config)
+
+## Roadmap
+
+### Add storage adapters
+
+- TODO: Create Postgres adapter
+- TODO: Create Redis adapter
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/examples/environment-config/Main.hs b/examples/environment-config/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/environment-config/Main.hs
@@ -0,0 +1,40 @@
+module Main where
+
+import Control.Monad.State
+import qualified Data.Map.Strict as Map
+import Data.Maybe (maybe)
+import qualified System.Environment as Env
+
+import Control.Flipper
+
+main :: IO ()
+main = do
+    features <- loadFeatures
+
+    -- StateT has a HasFeatureFlags instance defined
+    evalStateT runWithFeatureFlags features
+
+runWithFeatureFlags :: (MonadIO m, HasFeatureFlags m)
+                    => m ()
+runWithFeatureFlags = do
+    whenEnabled
+        "SOME_FEATURE"
+        (liftIO $ putStrLn "We're running SOME_FEATURE!")
+
+    whenEnabled
+        "SOME_OTHER_FEATURE"
+        (liftIO $ putStrLn "We're running SOME_OTHER_FEATURE!")
+
+    liftIO $ putStrLn "Done!"
+
+loadFeatures :: IO Features
+loadFeatures = do
+    -- load feature flags from the environment
+    someFeatureEnabled <- maybe False read <$> Env.lookupEnv "SOME_FEATURE"
+    someOtherFeatureEnabled <- maybe False read <$> Env.lookupEnv "SOME_OTHER_FEATURE"
+
+    -- build flipper feature type
+    pure . Features $ Map.fromList
+        [ ("SOME_FEATURE", someFeatureEnabled)
+        , ("SOME_OTHER_FEATURE", someOtherFeatureEnabled)
+        ]
diff --git a/feature-flipper.cabal b/feature-flipper.cabal
new file mode 100644
--- /dev/null
+++ b/feature-flipper.cabal
@@ -0,0 +1,73 @@
+-- This file has been generated from package.yaml by hpack version 0.17.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:                feature-flipper
+version:             0.1.0.0
+synopsis:            A minimally obtrusive feature flag library
+description:         A minimally obtrusive feature flag library
+homepage:            https://github.com/toddmohney/feature-flipper#readme
+bug-reports:         https://github.com/toddmohney/feature-flipper/issues
+license:             MIT
+license-file:        LICENSE
+author:              Todd Mohney
+maintainer:          toddmohney@gmail.com
+copyright:           2017 Todd Mohney
+category:            Web
+build-type:          Simple
+cabal-version:       >= 1.10
+
+extra-source-files:
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/toddmohney/feature-flipper
+
+library
+  hs-source-dirs:
+      src
+  default-extensions: OverloadedStrings
+  ghc-options: -Wall -fwarn-unused-matches -fwarn-unused-binds -fwarn-unused-imports
+  exposed-modules:
+      Control.Flipper
+      Control.Flipper.Types
+  other-modules:
+      Paths_feature_flipper
+  build-depends:
+      base >=4.7 && <5
+    , containers
+    , mtl
+    , text
+  default-language: Haskell2010
+
+executable environment-config
+  main-is: Main.hs
+  hs-source-dirs:
+      examples/environment-config
+  default-extensions: OverloadedStrings
+  ghc-options: -Wall -fwarn-unused-matches -fwarn-unused-binds -fwarn-unused-imports
+  build-depends:
+      base >=4.7 && <5
+    , containers
+    , feature-flipper
+    , mtl
+  default-language: Haskell2010
+
+test-suite feature-flipper-test
+  type: exitcode-stdio-1.0
+  hs-source-dirs:
+      test
+  default-extensions: OverloadedStrings
+  main-is: Spec.hs
+  build-depends:
+      base
+    , containers
+    , feature-flipper
+    , hspec
+    , mtl
+    , QuickCheck
+  other-modules:
+      Control.FlipperSpec
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  default-language: Haskell2010
diff --git a/src/Control/Flipper.hs b/src/Control/Flipper.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Flipper.hs
@@ -0,0 +1,90 @@
+{-|
+Module      : Flipper
+Description : Main user interface for the Flipper library
+-}
+module Control.Flipper
+    ( enabled
+    , enable
+    , disable
+    , toggle
+    , whenEnabled
+    , module Control.Flipper.Types
+    ) where
+
+import           Control.Monad   (void, when)
+import qualified Data.Map.Strict as M
+
+import           Control.Flipper.Types   (FeatureName(..), Features(..),
+                                          HasFeatureFlags(..), ModifiesFeatureFlags(..), mkFeatures)
+
+{- |
+The 'whenEnabled' function calls the supplied function, 'm ()', when the given
+'FeatureName' is enabled.
+
+When the feature specified by 'FeatureName' is disabled, 'm ()' is not
+evaluated.
+-}
+whenEnabled :: (HasFeatureFlags m)
+            => FeatureName -> m () -> m ()
+whenEnabled fName f = do
+    isEnabled <- enabled fName
+    when isEnabled f
+
+{- |
+The 'enabled' function returns a Bool indicating if the queried feature is
+active.
+
+When the queried FeatureName exists, the active state is returned.
+
+When the queried FeatureName does not exists, 'enabled' returns False.
+-}
+enabled :: HasFeatureFlags m
+        => FeatureName -> m Bool
+enabled fName = do
+    features <- unFeatures <$> getFeatures
+    if M.lookup fName features == Just True
+        then return True
+        else return False
+
+{- |
+The 'enable' function activates a feature.
+
+When the FeatureName exists in the store, it is set to active.
+
+When the FeatureName does not exist, it is created and set to active.
+-}
+enable :: ModifiesFeatureFlags m
+       => FeatureName -> m ()
+enable fName = update fName (\_ -> Just True)
+
+{- |
+The 'disable' function deactivates a feature.
+
+When the FeatureName exists in the store, it is set to inactive.
+
+When the FeatureName does not exist, it is created and set to inactive.
+-}
+disable :: ModifiesFeatureFlags m
+        => FeatureName -> m ()
+disable fName = update fName (\_ -> Just False)
+
+{- |
+The 'toggle' function flips the current state of a feature.
+
+When the FeatureName exists in the store, it flips the feature state.
+
+When the FeatureName does not exist, it is created and set to True.
+-}
+toggle :: ModifiesFeatureFlags m
+            => FeatureName -> m ()
+toggle fName = update fName flipIt'
+    where
+        flipIt' :: Maybe Bool -> Maybe Bool
+        flipIt' (Just a) = Just (not a)
+        flipIt' Nothing  = Just True
+
+update :: ModifiesFeatureFlags m
+       => FeatureName -> (Maybe Bool -> Maybe Bool) -> m ()
+update fName updateFn = do
+    features <- unFeatures <$> getFeatures
+    void . updateFeatures . Features $ M.alter updateFn fName features
diff --git a/src/Control/Flipper/Types.hs b/src/Control/Flipper/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Flipper/Types.hs
@@ -0,0 +1,68 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE TypeFamilies      #-}
+
+{-|
+Module      : Flipper.Types
+Description : Datatype and Typeclass definitions
+-}
+module Control.Flipper.Types
+    ( Features(..)
+    , FeatureName(..)
+    , HasFeatureFlags(..)
+    , ModifiesFeatureFlags(..)
+    , mkFeatures
+    ) where
+
+import           Control.Monad.State (StateT, get, put)
+import           Data.Map.Strict     (Map)
+import           Data.Monoid
+import           Data.String         (IsString (..))
+import           Data.Text           (Text)
+import qualified Data.Text           as T
+
+{- |
+The 'HasFeatureFlags' typeclass describes how to access the Features store
+within the current monad.
+-}
+class Monad m => HasFeatureFlags m where
+    -- | 'getFeatures' access the Features store within the current monad
+    getFeatures :: m Features
+
+instance (Monad m) => HasFeatureFlags (StateT Features m) where
+    getFeatures = get
+
+{- |
+The 'ModifiesFeatureFlags' typeclass describes how to modify the Features store
+within the current monad.
+-}
+class HasFeatureFlags m => ModifiesFeatureFlags m where
+    -- | 'updateFeatures' modifies the Features store within the current monad
+    updateFeatures :: Features -> m ()
+
+instance (Monad m) => ModifiesFeatureFlags (StateT Features m) where
+    updateFeatures = put
+
+{- |
+An abstraction representing the current state of the features store.
+-}
+newtype Features = Features { unFeatures :: Map FeatureName Bool }
+    deriving (Show, Eq)
+
+instance Monoid Features where
+    mempty = Features mempty
+    mappend a b = Features (unFeatures a <> unFeatures b)
+
+{- |
+The main identifier of a feature
+-}
+newtype FeatureName = FeatureName { unFeatureName :: Text }
+    deriving (Show, Eq, Ord)
+
+instance IsString FeatureName where
+    fromString s = FeatureName (T.pack s)
+
+{- |
+Convienience constructor
+-}
+mkFeatures :: Map FeatureName Bool -> Features
+mkFeatures = Features
diff --git a/test/Control/FlipperSpec.hs b/test/Control/FlipperSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Control/FlipperSpec.hs
@@ -0,0 +1,107 @@
+module Control.FlipperSpec (main, spec) where
+
+import           Control.Monad.State (evalState, execState)
+import qualified Data.Map.Strict     as M
+import           Test.Hspec
+
+import           Control.Flipper
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+    describe "whenEnabled" $ do
+        context "the feature is enabled" $
+            it "evaluates the given monad" $
+                let
+                    featureState = mkFeatures $ M.insert "ADD_ANOTHER_FEATURE" True mempty
+                    store = execState
+                        (whenEnabled "ADD_ANOTHER_FEATURE" (enable "ANOTHER_FEATURE"))
+                        featureState
+                in
+                    M.lookup "ANOTHER_FEATURE" (unFeatures store) `shouldBe` Just True
+
+        context "the feature is disabled" $
+            it "does not evaluate the given monad" $
+                let
+                    featureState = mkFeatures $ M.insert "ADD_ANOTHER_FEATURE" False mempty
+                    store = execState
+                        (whenEnabled "ADD_ANOTHER_FEATURE" (enable "ANOTHER_FEATURE"))
+                        featureState
+                in
+                    M.lookup "ANOTHER_FEATURE" (unFeatures store) `shouldBe` Nothing
+
+    describe "enable" $ do
+        it "enables a new feature key" $
+            let
+                featureState = mempty :: Features
+                result = evalState (enable "NEW_FEATURE" >> enabled "NEW_FEATURE") featureState
+            in
+                result `shouldBe` True
+
+        it "enables a existing feature key" $
+            let
+                featureState = mkFeatures $ M.insert "FEATURE" False mempty
+                result = evalState (enable "FEATURE" >> enabled "FEATURE") featureState
+            in
+                result `shouldBe` True
+
+    describe "disable" $ do
+        it "disables a new feature key" $
+            let
+                featureState = mempty :: Features
+                result = evalState (disable "NEW_FEATURE" >> enabled "NEW_FEATURE") featureState
+            in
+                result `shouldBe` False
+
+        it "disables a existing feature key" $
+            let
+                featureState = mkFeatures $ M.insert "FEATURE" True mempty
+                result = evalState (disable "FEATURE" >> enabled "FEATURE") featureState
+            in
+                result `shouldBe` False
+
+    describe "toggle" $ do
+        it "enables a new feature key" $
+            let
+                featureState = mempty :: Features
+                result = evalState (toggle "NEW_FEATURE" >> enabled "NEW_FEATURE") featureState
+            in
+                result `shouldBe` True
+
+        it "enables a disabled feature key" $
+            let
+                featureState = mkFeatures $ M.insert "FEATURE" False mempty
+                result = evalState (toggle "FEATURE" >> enabled "FEATURE") featureState
+            in
+                result `shouldBe` True
+
+        it "disables a enabled feature key" $
+            let
+                featureState = mkFeatures $ M.insert "FEATURE" True mempty
+                result = evalState (toggle "FEATURE" >> enabled "FEATURE") featureState
+            in
+                result `shouldBe` False
+
+    describe "enabled" $ do
+        it "returns True when the feature is enabled" $
+            let
+                featureState = mkFeatures $ M.insert "ENABLED_FEATURE" True mempty
+                result = evalState (enabled "ENABLED_FEATURE") featureState
+            in
+                result `shouldBe` True
+
+        it "returns False when the feature is not enabled" $
+            let
+                featureState = mkFeatures $ M.insert "DISABLED_FEATURE" False mempty
+                result = evalState (enabled "DISABLED_FEATURE") featureState
+            in
+                result `shouldBe` False
+
+        it "returns False when the feature key is not found" $
+            let
+                featureState = mempty :: Features
+                result = evalState (enabled "NON_EXISTANT_FEATURE") featureState
+            in
+                result `shouldBe` False
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
