diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+# 1.1.4
+* enhance API documentation.
+* add MonadHas class.
+* add devFile action.
+
 # 1.1.3
 * wildcard, parameter Accept header.
 
diff --git a/apiary.cabal b/apiary.cabal
--- a/apiary.cabal
+++ b/apiary.cabal
@@ -1,5 +1,5 @@
 name:                apiary
-version:             1.1.3
+version:             1.1.4
 synopsis:            Simple and type safe web framework that can be automatically generate API documentation.
 description:
   Simple and type safe web framework that can be automatically generate API documentation.
@@ -40,7 +40,7 @@
   .
     * Auto generate API documentation(example: <http://best-haskell.herokuapp.com/api/documentation>).
   .
-  more examples: <https://github.com/philopon/apiary/blob/v1.1.3/examples/>
+  more examples: <https://github.com/philopon/apiary/blob/v1.1.4/examples/>
   .
   live demo: <http://best-haskell.herokuapp.com/> (source code: <https://github.com/philopon/best-haskell>)
 
diff --git a/src/Control/Monad/Apiary/Action.hs b/src/Control/Monad/Apiary/Action.hs
--- a/src/Control/Monad/Apiary/Action.hs
+++ b/src/Control/Monad/Apiary/Action.hs
@@ -22,6 +22,7 @@
     , showing
     , string, char
     , file
+    , devFile
 
     -- ** monolithic action
     -- *** redirect
@@ -38,12 +39,12 @@
     , getRequest
     , getHeaders
     , getParams
-    , getExt
     , getQueryParams
     , getReqBodyParams
     , getReqBodyFiles
     -- ** setter
     , file'
+    , devFile'
     , stream
     , rawResponse
     , StreamingBody
diff --git a/src/Control/Monad/Apiary/Action/Internal.hs b/src/Control/Monad/Apiary/Action/Internal.hs
--- a/src/Control/Monad/Apiary/Action/Internal.hs
+++ b/src/Control/Monad/Apiary/Action/Internal.hs
@@ -353,6 +353,9 @@
     ask     = lift ask
     local f = hoistActionT $ local f
 
+instance (Monad m, Has e exts) => MonadHas e (ActionT exts prms m) where
+    getExt p = liftM (getExtension p . actionExts) getEnv
+
 --------------------------------------------------------------------------------
 
 getEnv :: Monad m => ActionT exts prms m (ActionEnv exts)
@@ -368,10 +371,6 @@
 getConfig :: Monad m => ActionT exts prms m ApiaryConfig
 getConfig = liftM actionConfig getEnv
 
--- | get extension.
-getExt :: (Has e exts, Monad m) => proxy e -> ActionT exts prms m e
-getExt p = liftM (getExtension p . actionExts) getEnv
-
 getParams :: Monad m => ActionT exts prms m (Dict prms)
 getParams = ActionT $ \d _ s c -> c d s
 
@@ -573,6 +572,21 @@
             contentType (mime f)
             maybe (return ()) (addHeader "Last-Modified" . formatHTTPDate) t
             file' f p
+
+{-# WARNING devFile' "use file' in production." #-}
+devFile' :: MonadIO m => FilePath -> ActionT exts prms m ()
+devFile' f = liftIO (fileExist f) >>= \e ->
+    if e
+    then liftIO (L.readFile f) >>= lazyBytes
+    else mzero
+
+-- | send file contents as lazy bytestring response. since v1.1.4.
+{-# WARNING devFile "use file in production." #-}
+devFile :: MonadIO m => FilePath -> ActionT exts prms m ()
+devFile f = do
+    mime <- mimeType <$> getConfig
+    contentType (mime f)
+    devFile' f
 
 -- | append response body from builder. since 0.1.0.0.
 builder :: Monad m => Builder -> ActionT exts prms m ()
diff --git a/src/Control/Monad/Apiary/Internal.hs b/src/Control/Monad/Apiary/Internal.hs
--- a/src/Control/Monad/Apiary/Internal.hs
+++ b/src/Control/Monad/Apiary/Internal.hs
@@ -220,14 +220,19 @@
     liftBaseWith = defaultLiftBaseWith StMApiary'
     restoreM     = defaultRestoreM   unStMApiary'
 
+instance (Has e exts, Monad actM) => MonadHas e (ApiaryT exts prms actM m) where
+    getExt p = getExtension p . envExts <$> getApiaryEnv
+    {-# INLINE getExt #-}
+
 --------------------------------------------------------------------------------
 
 getApiaryEnv :: Monad actM => ApiaryT exts prms actM m (ApiaryEnv exts prms actM)
 getApiaryEnv = ApiaryT $ \env cont -> cont env mempty
 
+{-# DEPRECATED apiaryExt "use getExt" #-}
 -- | get Apiary extension.
 apiaryExt :: (Has e exts, Monad actM) => proxy e -> ApiaryT exts prms actM m e
-apiaryExt p = getExtension p . envExts <$> getApiaryEnv
+apiaryExt = getExt
 
 -- | get Apiary configuration.
 apiaryConfig :: Monad actM => ApiaryT exts prms actM m ApiaryConfig
diff --git a/src/Data/Apiary/Document/Html.hs b/src/Data/Apiary/Document/Html.hs
--- a/src/Data/Apiary/Document/Html.hs
+++ b/src/Data/Apiary/Document/Html.hs
@@ -11,6 +11,7 @@
     ) where
 
 import Language.Haskell.TH
+import Language.Haskell.TH.Syntax(addDependentFile)
 
 import Data.Monoid hiding (Any)
 import Data.Default.Class
@@ -106,10 +107,8 @@
         [ H.title (toHtml documentTitle)
         , if documentUseCDN then cdns else embeds
         , $(runIO (readFile "static/jquery.cookie-1.4.1.min.js") >>= \c -> [|H.script $ preEscapedToHtml (c::String)|])
---        , H.script "" ! A.src "/static/api-documentation.js"
---        , H.link ! A.rel "stylesheet" ! A.href "/static/api-documentation.css"
-        , $(runIO (readFile "static/api-documentation.min.js")   >>= \c -> [|H.script $ preEscapedToHtml (c::String)|])
-        , $(runIO (readFile "static/api-documentation.min.css")  >>= \c -> [|H.style  $ preEscapedToHtml (c::String)|])
+        , $(addDependentFile "static/api-documentation.min.js"  >> runIO (readFile "static/api-documentation.min.js")   >>= \c -> [|H.script $ preEscapedToHtml (c::String)|])
+        , $(addDependentFile "static/api-documentation.min.css" >> runIO (readFile "static/api-documentation.min.css")  >>= \c -> [|H.style  $ preEscapedToHtml (c::String)|])
         , maybe mempty analytics documentGoogleAnalytics
         ]
 
@@ -151,8 +150,7 @@
         ]
 
     action (MethodDoc qs pc a d) = H.div ! A.class_ "action col-sm-offset-1 col-md-offset-1" $ mconcat
-        [ preconds $ maybe pc (\ac -> ("Accept: " <> toHtml (T.decodeUtf8 ac)) : pc) a
-        , maybe mempty (\ac -> H.script ! A.class_ "accept" ! A.type_ "text/plain" $ (toHtml $ T.decodeUtf8 ac)) a
+        [ preconds $ maybe pc (\ac -> H.span ("Accept: " <> H.span (toHtml $ T.decodeUtf8 ac)) ! A.class_ "precondition-accept" : pc) a
         , H.div (H.p $ toHtml d)
         , queriesH qs
         ]
diff --git a/src/Data/Apiary/Extension.hs b/src/Data/Apiary/Extension.hs
--- a/src/Data/Apiary/Extension.hs
+++ b/src/Data/Apiary/Extension.hs
@@ -1,10 +1,12 @@
 {-# LANGUAGE DataKinds #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeOperators #-}
 {-# LANGUAGE Rank2Types #-}
 {-# LANGUAGE ConstraintKinds #-}
 
 module Data.Apiary.Extension
     ( Has(getExtension)
+    , MonadHas(..)
     , Extension(..)
     , Extensions
     , noExtension
@@ -19,6 +21,9 @@
     ) where
 
 import Data.Apiary.Extension.Internal
+
+class MonadHas e m where
+    getExt :: proxy e -> m e
 
 type Initializer' m a = forall i. Initializer m i (a ': i)
 
diff --git a/src/Web/Apiary.hs b/src/Web/Apiary.hs
--- a/src/Web/Apiary.hs
+++ b/src/Web/Apiary.hs
@@ -7,7 +7,7 @@
 
     -- | Method(..)
     , module Data.Apiary.Method
-    -- | Has, Extensions, Initializer, Initializer', (+>)
+    -- | Has, MonadHas, Extensions, Initializer, Initializer', (+>)
     , module Data.Apiary.Extension
     -- | key, Member, Members, NotMember, Elem((:=))
     , module Data.Apiary.Dict
@@ -54,7 +54,7 @@
     , text,  lazyText
     , showing
     , string, char
-    , file
+    , file, devFile
     , redirect, redirectPermanently, redirectTemporary
     , defaultDocumentationAction
     , DefaultDocumentConfig(..)
@@ -82,7 +82,7 @@
     )
 
 import Data.Apiary.Method(Method(..))
-import Data.Apiary.Extension(Has, Extensions, Initializer, Initializer', (+>))
+import Data.Apiary.Extension(Has, MonadHas(..), Extensions, Initializer, Initializer', (+>))
 import Data.Apiary.Dict(key, Member, Members, NotMember, Elem((:=)))
 
 import Network.HTTP.Types.Status hiding (mkStatus)
diff --git a/static/api-documentation.min.js b/static/api-documentation.min.js
--- a/static/api-documentation.min.js
+++ b/static/api-documentation.min.js
@@ -1,1 +1,1 @@
-"use strict";$.cookie.json=true;$.cookie.defaults.path=window.location.pathname;$.cookie.defaults.expires=365;(function(){function get_panel_ids_common(selector){var ids=[];$(selector).each(function(){ids.push($(this).attr("id"))});return ids}function get_opened_panel_ids(){return get_panel_ids_common(".collapse.in")}function get_all_panel_ids(){return get_panel_ids_common(".collapse")}function open_panel_by_id(id){$('[id="'+id+'"]').addClass("in")}function save_panel_state(){$.cookie("panel",get_opened_panel_ids())}function restore_panel_state(){$.each($.cookie("panel")||[],function(){open_panel_by_id(this)})}function close_all_panels(){$(".collapse.in").collapse("hide");save_panel_state([])}function open_all_panels(){$(".collapse").not(".in").collapse("show");save_panel_state(get_all_panel_ids())}function toggle_all_panels(){get_opened_panel_ids().length==0?open_all_panels():close_all_panels()}$(function(){restore_panel_state();$(".collapse").on("shown.bs.collapse",save_panel_state);$(".collapse").on("hidden.bs.collapse",save_panel_state);$("h1").click(toggle_all_panels)})})();if(location.protocol.slice(0,4)=="http"){(function(){$(function(){var update_test_route_functions=[];var update_test_query_functions=[];$(".panel").each(function(){var panel=$(this);var panel_id=panel.find(".panel-collapse").attr("id");panel.find(".route-parameters tr:first").append($("<th>value</th>"));panel.find(".route-parameters tr:gt(0)").append($('<td><input class="form-control input-sm" /></td>'));panel.find(".query-parameters tr:first").append($("<th>value</th>"));panel.find(".query-parameters tr:gt(0)").append($('<td><div class="input-group"><span class="input-group-addon"><input type="checkbox"/></span><input class="form-control input-sm"/></div></td>'));panel.find(".method").each(function(){var method=$(this);var h4=method.find("h4:first");var method_str=h4.text();var div=$('<div class="request-test">');method.find("h4:first").replaceWith(div);div.append($('<button class="test-method btn btn-default">'+method_str+"</button>"));var span=$('<span class="route-string">');div.append(span);span.append($('<span class="route-url"></span>'));span.append($('<span class="route-params"></span>'));var callout=$('<div class="hidden request-result bs-callout"><div class="clearfix title"><h4 class="pull-left"></h4></div><div class="well headers"><pre><code></code></pre></div><div class="body"><pre><code></code></pre></div></div>');var close=$('<div class="close-btn pull-right">&times;</div>');callout.find(".title").append(close);close.click(function(){callout.addClass("hidden")});method.find(".action:last").append(callout)});var route_params={};panel.find(".route-parameters tr:gt(0)").each(function(){var p=$(this);route_params[p.find("td:first").text()]=p.find("td:last input")});function get_route(title){var ret="";title.children().each(function(){var p=$(this);if(p.hasClass("splitter")){ret+="/"}else if(p.hasClass("path")){ret+=p.text()}else if(p.hasClass("fetch")){ret+=route_params[p.text()].val()}});return ret}var route_template=panel.find(".panel-title");var route_url=panel.find(".route-url");function update_test_route(){var route=get_route(route_template);route_url.text(route)}update_test_route_functions.push(update_test_route);function save_route_parameters(){var save={};$.each(route_params,function(k,v){save[k]=v.val()});var rp=$.cookie("route")||{};rp[panel_id]=save;$.cookie("route",rp)}panel.find(".route-parameters tr:gt(0) input").keyup(update_test_route).change(save_route_parameters);panel.find(".method").each(function(){var method=$(this);var method_str=method.find(".test-method").text();var query_params={};method.find(".query-parameters tr:gt(0)").each(function(){var p=$(this);var checkbox=p.find('td:last input[type="checkbox"]');var input=p.find("td:last input.form-control");query_params[p.find("td:first").text()]={checkbox:checkbox[0],input:input}});function get_query_param(){var ret="";$.each(query_params,function(name,value){if(value.checkbox.checked){var val=value.input.val();if(val.length==0){ret+="&"+name}else{$.each(val.split(" "),function(){ret+="&"+name+"="+this})}}});if(ret.length>0){ret="?"+ret.slice(1)}return ret}var query_param=method.find(".route-params");function update_query_param(){query_param.text(get_query_param())}update_test_query_functions.push(update_query_param);function save_query_param(){var save={};$.each(query_params,function(name,value){save[name]={checkbox:value.checkbox.checked,value:value.input.val()}});var qp=$.cookie("query")||{};if(!qp[panel_id]){qp[panel_id]={}}qp[panel_id][method_str]=save;$.cookie("query",qp)}function update_and_save_query_param(){update_query_param();save_query_param()}method.find('.query-parameters input[type="checkbox"]').change(update_and_save_query_param);method.find(".query-parameters input.form-control").keyup(update_query_param).change(save_query_param);var method_btn=method.find(".test-method");var route_url=method.find(".route-url");var result=method.find(".request-result");var accept=method.find(".accept");function request_test(){if(method_btn.hasClass("disabled")){return}var url=route_url.text()+query_param.text();var meth=method_str=="*"?"GET":method_str;method_btn.addClass("disabled");function request_callback(data,status,xhr){var ct=xhr.getResponseHeader("Content-Type");var code=xhr.status;result.find("h4").text(code+" "+status+' "'+ct+'"');result.find(".headers code").text(xhr.getAllResponseHeaders());if(ct.slice(0,16)=="application/json"){try{var json=JSON.stringify(JSON.parse(data),undefined,2);result.find(".body code").text(json)}catch(e){result.find(".body code").text(e+"\n\n"+data)}}else{result.find(".body code").text(data)}}function beforeSend(xhr){if(accept.length==1){xhr.setRequestHeader("Accept",accept.text())}}$.ajax({type:meth,url:url,dataType:"text",beforeSend:beforeSend}).done(function(data,status,xhr){request_callback(data,status,xhr);result.addClass("bs-callout-primary");result.removeClass("bs-callout-danger")}).fail(function(xhr,status,err){request_callback(err,status,xhr);result.addClass("bs-callout-danger");result.removeClass("bs-callout-primary")}).always(function(){result.removeClass("hidden");method_btn.removeClass("disabled")})}method.find(".test-method").click(request_test)})});function restore_route_parameters(){$.each($.cookie("route")||{},function(id,dict){var panel=$('[id="'+id+'"]');panel.find(".route-parameters tr:gt(0)").each(function(){var row=$(this);var key=row.find("td:first").text();row.find("td:last input").val(dict[key]||"")})})}function restore_query_parameters(){$.each($.cookie("query")||{},function(id,dict){var panel=$('[id="'+id+'"]');panel.find(".method").each(function(){var method=$(this);var method_str=method.find(".test-method").text();method.find(".query-parameters tr:gt(0)").each(function(){var row=$(this);var key=row.find("td:first").text();var checkbox=row.find('td:last input[type="checkbox"]')[0];var input=row.find("td:last input.form-control");checkbox.checked=dict[method_str][key].checkbox||false;input.val(dict[method_str][key].value||"")})})})}restore_route_parameters();restore_query_parameters();$.each(update_test_route_functions,function(){this()});$.each(update_test_query_functions,function(){this()})})})()}
+"use strict";$.cookie.json=true;$.cookie.defaults.path=window.location.pathname;$.cookie.defaults.expires=365;(function(){function get_panel_ids_common(selector){var ids=[];$(selector).each(function(){ids.push($(this).attr("id"))});return ids}function get_opened_panel_ids(){return get_panel_ids_common(".collapse.in")}function get_all_panel_ids(){return get_panel_ids_common(".collapse")}function open_panel_by_id(id){$('[id="'+id+'"]').addClass("in")}function save_panel_state(){$.cookie("panel",get_opened_panel_ids())}function restore_panel_state(){$.each($.cookie("panel")||[],function(){open_panel_by_id(this)})}function close_all_panels(){$(".collapse.in").collapse("hide");save_panel_state([])}function open_all_panels(){$(".collapse").not(".in").collapse("show");save_panel_state(get_all_panel_ids())}function toggle_all_panels(){get_opened_panel_ids().length==0?open_all_panels():close_all_panels()}$(function(){restore_panel_state();$(".collapse").on("shown.bs.collapse",save_panel_state);$(".collapse").on("hidden.bs.collapse",save_panel_state);$("h1").click(toggle_all_panels)})})();if(location.protocol.slice(0,4)=="http"){(function(){$(function(){var update_test_route_functions=[];var update_test_query_functions=[];$(".panel").each(function(){var panel=$(this);var panel_id=panel.find(".panel-collapse").attr("id");panel.find(".route-parameters tr:first").append($("<th>value</th>"));panel.find(".route-parameters tr:gt(0)").append($('<td><input class="form-control input-sm" /></td>'));panel.find(".method").each(function(){var method=$(this);var h4=method.find("h4:first");var method_str=h4.text();h4.remove();method.find(".action").each(function(){var action=$(this);action.detach();var action_wrapper=$('<div class="action-wrapper">');method.append(action_wrapper);var div=$('<div class="request-test clearfix">');div.append($('<button class="test-method btn btn-default">'+method_str+"</button>"));var span=$('<span class="route-string">');div.append(span);span.append($('<span class="route-url"></span>'));span.append($('<span class="route-params"></span>'));action_wrapper.append(div);action_wrapper.append(action);var callout=$('<div class="hidden request-result bs-callout"><div class="clearfix title"><h4 class="pull-left"></h4></div><div class="well headers"><pre><code></code></pre></div><div class="body"><pre><code></code></pre></div></div>');var close=$('<div class="close-btn pull-right">&times;</div>');callout.find(".title").append(close);close.click(function(){callout.addClass("hidden")});method.find(".action:last").append(callout);action.find(".query-parameters tr:first").append($("<th>value</th>"));action.find(".query-parameters tr:gt(0)").append($('<td><div class="input-group"><span class="input-group-addon"><input type="checkbox"/></span><input class="form-control input-sm"/></div></td>'))})});var route_params={};panel.find(".route-parameters tr:gt(0)").each(function(){var p=$(this);route_params[p.find("td:first").text()]=p.find("td:last input")});function get_route(title){var ret="";title.children().each(function(){var p=$(this);if(p.hasClass("splitter")){ret+="/"}else if(p.hasClass("path")){ret+=p.text()}else if(p.hasClass("fetch")){ret+=route_params[p.text()].val()}});return ret}var route_template=panel.find(".panel-title");var route_url=panel.find(".route-url");function update_test_route(){var route=get_route(route_template);route_url.text(route)}update_test_route_functions.push(update_test_route);function save_route_parameters(){var save={};$.each(route_params,function(k,v){save[k]=v.val()});var rp=$.cookie("route")||{};rp[panel_id]=save;$.cookie("route",rp)}panel.find(".route-parameters tr:gt(0) input").keyup(update_test_route).change(save_route_parameters);panel.find(".method").each(function(){var method=$(this);var method_str=method.find(".test-method:first").text();var action_wrapper=method.find(".action-wrapper");var action_length=action_wrapper.length;action_wrapper.each(function(action_index){var action=$(this);var query_params={};action.find(".query-parameters tr:gt(0)").each(function(){var p=$(this);var checkbox=p.find('td:last input[type="checkbox"]');var input=p.find("td:last input.form-control");query_params[p.find("td:first").text()]={checkbox:checkbox[0],input:input}});function get_query_param(){var ret="";$.each(query_params,function(name,value){if(value.checkbox.checked){var val=value.input.val();if(val.length==0){ret+="&"+name}else{$.each(val.split(" "),function(){ret+="&"+name+"="+this})}}});if(ret.length>0){ret="?"+ret.slice(1)}return ret}var query_param=action.find(".route-params");function update_query_param(){query_param.text(get_query_param())}update_test_query_functions.push(update_query_param);function save_query_param(){var save={};$.each(query_params,function(name,value){save[name]={checkbox:value.checkbox.checked,value:value.input.val()}});var qp=$.cookie("query")||{};if(!qp[panel_id]){qp[panel_id]={}}if(!qp[panel_id][method_str]){qp[panel_id][method_str]=new Array(action_length)}qp[panel_id][method_str][action_index]=save;$.cookie("query",qp)}function update_and_save_query_param(){update_query_param();save_query_param()}action.find('.query-parameters input[type="checkbox"]').change(update_and_save_query_param);action.find(".query-parameters input.form-control").keyup(update_query_param).change(save_query_param);var action_btn=action.find(".test-method");var route_url=action.find(".route-url");var result=action.find(".request-result");var accept=action.find(".precondition-accept span");function request_test(){if(action_btn.hasClass("disabled")){return}var url=route_url.text()+query_param.text();var meth=method_str=="*"?"GET":method_str;action_btn.addClass("disabled");function request_callback(data,status,xhr){var ct=xhr.getResponseHeader("Content-Type");var code=xhr.status;result.find("h4").text(code+" "+status+' "'+ct+'"');result.find(".headers code").text(xhr.getAllResponseHeaders());if(ct.slice(0,16)=="application/json"){try{var json=JSON.stringify(JSON.parse(data),undefined,2);result.find(".body code").text(json)}catch(e){result.find(".body code").text(e+"\n\n"+data)}}else{result.find(".body code").text(data)}}function beforeSend(xhr){if(accept.length!=0){xhr.setRequestHeader("Accept",accept.text())}}$.ajax({type:meth,url:url,dataType:"text",beforeSend:beforeSend}).done(function(data,status,xhr){request_callback(data,status,xhr);result.addClass("bs-callout-primary");result.removeClass("bs-callout-danger")}).fail(function(xhr,status,err){request_callback(err,status,xhr);result.addClass("bs-callout-danger");result.removeClass("bs-callout-primary")}).always(function(){result.removeClass("hidden");action_btn.removeClass("disabled")})}action.find(".test-method").click(request_test);action.find("input.form-control").keypress(function(e){if(e.which==13){request_test()}})})})});function restore_route_parameters(){$.each($.cookie("route")||{},function(id,dict){var panel=$('[id="'+id+'"]');panel.find(".route-parameters tr:gt(0)").each(function(){var row=$(this);var key=row.find("td:first").text();row.find("td:last input").val(dict[key]||"")})})}function restore_query_parameters(){try{$.each($.cookie("query")||{},function(id,dict){var panel=$('[id="'+id+'"]');panel.find(".method").each(function(){var method=$(this);var method_str=method.find(".test-method:first").text();var action=method.find(".action-wrapper");if(action.length!=dict[method_str].length){throw"action length mismatch"}action.each(function(idx){$(this).find(".query-parameters tr:gt(0)").each(function(){var row=$(this);var key=row.find("td:first").text();var checkbox=row.find('td:last input[type="checkbox"]')[0];var input=row.find("td:last input.form-control");if(dict[method_str][idx]){checkbox.checked=dict[method_str][idx][key].checkbox||false;input.val(dict[method_str][idx][key].value||"")}})})})})}catch(e){console.log(e);$.removeCookie("query")}}restore_route_parameters();restore_query_parameters();$.each(update_test_route_functions,function(){this()});$.each(update_test_query_functions,function(){this()})})})()}
