Haskell web framework inspired by Ruby's Sinatra, using WAI and Warp

Version on this page:0.20.1@rev:1
LTS Haskell 22.29:0.20.1@rev:1
Stackage Nightly 2024-07-17:0.22@rev:2
Latest on Hackage:0.22@rev:2

See all snapshots scotty appears in

BSD-3-Clause licensed by Andrew Farmer
Maintained by The Scotty maintainers
This version can be pinned in stack with:scotty-0.20.1@sha256:c6c38727407c1e925ba44b55686f7a18526dbddb138317eb637a1cb1df3cff09,5382

Module documentation for 0.20.1

Scotty Hackage CI

A Haskell web framework inspired by Ruby’s Sinatra, using WAI and Warp.

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty

main = scotty 3000 $
    get "/:word" $ do
        beam <- captureParam "word"
        html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]

Scotty is the cheap and cheerful way to write RESTful, declarative web applications.

  • A page is as simple as defining the verb, URL pattern, and Text content.
  • It is template-language agnostic. Anything that returns a Text value will do.
  • Conforms to the web application interface (WAI).
  • Uses the very fast Warp webserver by default.

See examples/basic.hs to see Scotty in action. (basic.hs needs the wai-extra package)

> runghc examples/basic.hs
Setting phasers to stun... (port 3000) (ctrl-c to quit)
(visit localhost:3000/somepath)

As for the name: Sinatra + Warp = Scotty.

More Information

Tutorials and related projects can be found in the Scotty wiki.


Feel free to ask questions or report bugs on the Github issue tracker.

Github issues are now (September 2023) labeled, so newcomers to the Haskell language can start with easy fix ones and gradually progress to new features, bugs and R&D :)

Package versions

Scotty adheres to the Package Versioning Policy.


  • Fails to compile regex-posix on Windows
    • If you are using stack, add the following parameters to stack.yaml:
      •   extra-deps:
          - regex-posix-clib-2.7
              _regex-posix-clib: true
    • If you are using cabal, update the constraints section of cabal.project.local as follows:
      • constraints:
          regex-posix +_regex-posix-clib 



(c) 2012-Present, Andrew Farmer and Scotty contributors


next [????.??.??]

0.20.1 [2023.10.03]

  • remove dependencies on ‘base-compat’ and ‘base-compat-batteries’ (#318)
  • re-add MonadFail (ActionT m) instance (#325)
  • re-add MonadError (ActionT m) instance, but the error type is now specialized to ‘StatusError’ (#325)
  • raise lower bound on base ( > 4.14 ) to reflect support for GHC >= 8.10 (#325).

0.20 [2023.10.02]

  • Drop support for GHC < 8.10 and modernise the CI pipeline (#300).
  • Adds a new nested handler that allows you to place an entire WAI Application under a Scotty route (#233).
  • Disambiguate request parameters (#204). Adjust the Env type to have three [Param] fields instead of one, add captureParam, formParam, queryParam and the associated captureParams, formParams, queryParams. Add deprecation notices to param and params.
  • Add Scotty.Cookie module (#293).
  • Change body parsing behaviour such that calls to next don’t result in POST request bodies disappearing (#147).
  • (Internal) Remove unused type RequestBodyState (#313)
  • Rewrite ActionT using the “ReaderT pattern” (#310)


  • (#310) Introduce unliftio as a dependency, and base exception handling on catch.
  • (#310) Clarify the exception handling mechanism of ActionT, ScottyT. rescue changes signature to use proper Exception types rather than strings. Remove ScottyError typeclass.
  • (#310) All ActionT methods (text, html etc.) have now a MonadIO constraint on the base monad rather than Monad because the response is constructed in a TVar inside ActionEnv. rescue has a MonadUnliftIO constraint. The Alternative instance of ActionT also is based on MonadUnliftIO because <|> is implemented in terms of catch. ScottyT and ActionT do not have an exception type parameter anymore.
  • (#310) MonadError e (ActionT m) instance removed
  • (#310) MonadFail (ActionT m) instance is missing by mistake.

0.12.1 [2022.11.17]

  • Fix CPP bug that prevented tests from building on Windows.
  • Allow building with transformers-0.6.* and mtl-2.3.*. Because the MonadTrans t class gained a forall m. Monad m => Monad (t m) superclass in transformers-, the MonadTrans and MonadTransControl instances for ActionT e now have a ScottyError e instance context to match the Monad instance.

0.12 [2020.05.16]

  • Provide MonadReader and MonadState instances for ActionT.
  • Add HTTP Status code as a field to ActionError, and add a sister function to raise, raiseStatus. This makes throwing a specific error code and exiting much cleaner, and avoids the strange defaulting to HTTP 500. This will make internal functions easier to implement with the right status codes ‘thrown’, such as jsonData.
  • Correct http statuses returned by jsonData (#228).
  • Better error message when no data is provided to jsonData (#226).
  • Add Semigroup and Monoid instances for ActionT and ScottyT
  • ScottyT: Use strict StateT instead of lazy
  • Handle adjacent slashes in the request path as one (thanks @SkyWriter)

0.11.5 [2019.09.07]

  • Allow building the test suite with hspec-wai-0.10.

0.11.4 [2019.05.02]

  • Allow building with base-4.13 (GHC 8.8).

0.11.3 [2019.01.08]

  • Drop the test suite’s dependency on hpc-coveralls, which is unmaintained and does not build with GHC 8.4 or later.

0.11.2 [2018.07.02]

  • Migrate from Network to Network.Socket to avoid deprecation warnings.

0.11.1 [2018.04.07]

  • Add MonadThrow and MonadCatch instances for ActionT [abhinav]
  • Fix matchAny so that all methods are matched, not just standard ones [taphu]


  • IO exceptions are no longer automatically turned into ScottyErrors by liftIO. Use liftAndCatchIO to get that behavior.
  • New finish function.
  • Text values are now leniently decoded from ByteStrings.
  • Added MonadFail instance for ScottyT
  • Lots of bound bumps on dependencies.


  • Removed debug statement from routes


  • Parsable instances for Word, Word8, Word16, Word32, Word64 [adamflott]
  • Parsable instances for Int8, Int16, Int32, Int64, and Natural
  • Removed redundant Monad constraint on middleware


  • The monad parameters to ScottyT have been decoupled, causing the type of the ScottyT constructor to change. As a result, ScottyT is no longer a MonadTrans instance, and the type signatures ofscottyT, scottyAppT, and scottyOptsT have been simplified. [ehamberg]

  • socketDescription no longer uses the deprecated PortNum constructor. Instead, it uses the Show instance for PortNumber. This changes the bytes from host to network order, so the output of socketDescription could change. [ehamberg]

  • Alternative, MonadPlus instances for ActionT

  • scotty now depends on transformers-compat. As a result, ActionT now uses ExceptT, regardless of which version of transformers is used. As a result, several functions in Web.Scotty.Trans no longer require a ScottyError constraint, since ExceptT does not require an Error constraint (unlike ErrorT).

  • Added support for OPTIONS routes via the options function [alvare]

  • Add scottySocket and scottySocketT, exposing Warp Unix socket support [hakujin]

  • Parsable instance for lazy ByteString [tattsun]

  • Added streaming uploads via the bodyReader function, which retrieves chunks of the request body. [edofic]

    • ActionEnv had a getBodyChunk field added (in Web.Scotty.Internal.Types)
    • RequestBodyState and BodyPartiallyStreamed added to Web.Scotty.Internal.Types
  • jsonData uses aeson’s eitherDecode instead of just decode [k-bx]


  • text/html/json only set Content-Type header when not already set


  • Add charset=utf-8 to Content-Type for text, html and json

  • Assume HTTP status 500 for defaultHandler

  • Remove deprecated source method.

  • No longer depend on conduit.


  • Bump aeson upper bound

  • Fix mtl related deprecation warnings


  • Export internal types

  • Added MonadBase, MonadTransControl and MonadBaseControl instances for ActionT


  • Upgrade to wai/wai-extra/warp 3.0

  • No longer depend on conduit-extra.

  • The source response method has been deprecated in favor of a new stream response, matching changes in WAI 3.0.

  • Removed the deprecated reqHeader function.


  • Bump upper bound for case-insensitive, mtl and transformers.


  • Bump lower bound on conduit, add conduit-extra to cabal build depends.


  • Default warp settings now use setFdCacheDuration 0 to work around a warp issue where file changes are not getting picked up.


  • Renamed reqHeader to header. Added headers function to get all headers.

  • Changed MonadIO instance for ActionT such that IO exceptions are lifted into ScottyErrors via stringError.

  • Make Bool parsing case-insensitive. Goal: support both Haskell’s True/False and Javascript’s true/false. Thanks to Ben Gamari for suggesting this.

  • Bump aeson/text upper bounds.

  • Bump wai/wai-extra/warp bounds, including new lower bound for warp, which fixes a security issue related to Slowloris protection.


  • Bump upper bound for text.


  • Match changes in wai-extra.


  • The Scotty transformers (ScottyT and ActionT) are now parameterized over a custom exception type, allowing one to extend Scotty’s ErrorT layer with something richer than Text errors. See the exceptions example for use. ScottyM and ActionM remain specialized to Text exceptions for simplicity.

  • Both monads are now instances of Functor and Applicative.

  • There is a new cookies example.

  • Internals brought up-to-date with WAI 2.0 and related packages.


  • The Scotty monads (ScottyM and ActionM) are now monad transformers, allowing Scotty applications to be embedded in arbitrary MonadIOs. The old API continues to be exported from Web.Scotty where:

      type ScottyM = ScottyT IO
      type ActionM = ActionT IO

    The new transformers are found in Web.Scotty.Trans. See the globalstate example for use. Special thanks to Dan Frumin (co-dan) for much of the legwork here.

  • Added support for HTTP PATCH method.

  • Removed lambda action syntax. This will return when we have a better story for typesafe routes.

  • reqHeader :: Text -> ActionM Text ==> reqHeader :: Text -> ActionM (Maybe Text)

  • New raw method to set body to a raw ByteString

  • Parse error thrown by jsonData now includes the body it couldn’t parse.

  • header split into setHeader and addHeader. The former replaces a response header (original behavior). The latter adds a header (useful for multiple Set-Cookies, for instance).