diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,20 @@
-# 1.0.0.0
+# Changelog
+
+## 1.0.1.0 - 2019-10-15
+
+### Added
+
+  - `encodeQuoted`: Encodes with all keys/strings quoted
+  - `encodeQuotedDocuments`: Encodes documents with all keys/strings quoted
+
+### Changed
+
+  - Simple strings (scalars) are now written unquoted.
+
+  - Multi-line strings (with trailing newlines) are now written as literal
+    block scalars.
+
+
+## 1.0.0.0 - 2019-09-30
 
 Initial version
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,8 +1,6 @@
 # aeson-yaml
 
-BSD3-licensed library to encode any Aeson value as YAML, without a
-dependency on an external YAML library like `yaml` (libyaml C FFI) or
-`HsYaml` (GPL).
+BSD3-licensed, pure Haskell library to encode any Aeson value as YAML.
 
 ## Usage
 
@@ -30,3 +28,11 @@
 ## License
 
 [BSD3](LICENSE)
+
+## Motivation
+
+This library does not depend on any external YAML library with C bindings,
+like `yaml`, or a restrictive license, like `HsYaml` (GPLv3). Note, though,
+that this library can only be used for encoding, not decoding.
+
+This library also works with GHCJS and Eta.
diff --git a/aeson-yaml.cabal b/aeson-yaml.cabal
--- a/aeson-yaml.cabal
+++ b/aeson-yaml.cabal
@@ -1,7 +1,7 @@
 cabal-version: 1.12
 
 name:           aeson-yaml
-version:        1.0.0.0
+version:        1.0.1.0
 homepage:       https://github.com/clovyr/aeson-yaml
 bug-reports:    https://github.com/clovyr/aeson-yaml/issues
 author:         Patrick Nielsen
@@ -38,7 +38,7 @@
       Data.Aeson.Yaml
   build-depends:
       aeson >= 0.4.0.0 && < 1.5
-    , base >= 4.5.0.0 && < 4.13
+    , base >= 4.8.2.0 && < 5
     , bytestring >= 0.10.4.0 && < 0.11
     , text >= 0.1 && < 1.3
     , unordered-containers >= 0.1.0.0 && < 0.3
diff --git a/src/Data/Aeson/Yaml.hs b/src/Data/Aeson/Yaml.hs
--- a/src/Data/Aeson/Yaml.hs
+++ b/src/Data/Aeson/Yaml.hs
@@ -8,12 +8,13 @@
 
 This module is meant to be imported qualified.
 -}
-
 {-# LANGUAGE OverloadedStrings #-}
 
 module Data.Aeson.Yaml
   ( encode
   , encodeDocuments
+  , encodeQuoted
+  , encodeQuotedDocuments
   ) where
 
 import Data.Aeson hiding (encode)
@@ -23,10 +24,12 @@
 import qualified Data.ByteString.Builder as ByteString.Builder
 import qualified Data.ByteString.Lazy as ByteString.Lazy
 import qualified Data.ByteString.Short as ByteString.Short
+import Data.Char (isDigit)
 import qualified Data.HashMap.Strict as HashMap
-import Data.List (sortOn)
-import Data.List (intersperse)
+import Data.List (intersperse, sortOn)
 import Data.Monoid ((<>), mconcat, mempty)
+import qualified Data.Text as Text
+import Data.Text (Text)
 import qualified Data.Text.Encoding as Text.Encoding
 import qualified Data.Vector as Vector
 
@@ -43,23 +46,41 @@
 indent 0 = mempty
 indent n = bs "  " <> (indent $! n - 1)
 
-enc :: ToJSON a => a -> ByteString.Lazy.ByteString
-enc = Data.Aeson.encode
-
 -- | Encode a value as YAML (lazy bytestring).
 encode :: ToJSON a => a -> ByteString.Lazy.ByteString
-encode = ByteString.Builder.toLazyByteString . encodeBuilder False 0 . toJSON
+encode v =
+  ByteString.Builder.toLazyByteString $
+  encodeBuilder False False 0 (toJSON v) <> bs "\n"
 
--- | Encode multiple values separated by '---'. To encode values of different
+-- | Encode multiple values separated by '\n---\n'. To encode values of different
 -- types, @import Data.Aeson(ToJSON(toJSON))@ and do
 -- @encodeDocuments [toJSON x, toJSON y, toJSON z]@.
 encodeDocuments :: ToJSON a => [a] -> ByteString.Lazy.ByteString
-encodeDocuments =
-  ByteString.Builder.toLazyByteString .
-  mconcat . intersperse (bs "\n---\n") . map ((encodeBuilder False 0) . toJSON)
+encodeDocuments vs = ByteString.Builder.toLazyByteString $ output <> bs "\n"
+  where
+    output =
+      mconcat $
+      intersperse (bs "\n---\n") $ map (encodeBuilder False False 0 . toJSON) vs
 
-encodeBuilder :: Bool -> Int -> Data.Aeson.Value -> Builder
-encodeBuilder newlineBeforeObject level value =
+-- | Encode a value as YAML (lazy bytestring). Keys and strings are always
+-- quoted.
+encodeQuoted :: ToJSON a => a -> ByteString.Lazy.ByteString
+encodeQuoted v =
+  ByteString.Builder.toLazyByteString $
+  encodeBuilder True False 0 (toJSON v) <> bs "\n"
+
+-- | Encode multiple values separated by '\n---\n'. Keys and strings are always
+-- quoted.
+encodeQuotedDocuments :: ToJSON a => [a] -> ByteString.Lazy.ByteString
+encodeQuotedDocuments vs =
+  ByteString.Builder.toLazyByteString $ output <> bs "\n"
+  where
+    output =
+      mconcat $
+      intersperse (bs "\n---\n") $ map (encodeBuilder True False 0 . toJSON) vs
+
+encodeBuilder :: Bool -> Bool -> Int -> Data.Aeson.Value -> Builder
+encodeBuilder alwaysQuote newlineBeforeObject level value =
   case value of
     Object hm ->
       mconcat $
@@ -72,20 +93,75 @@
       mconcat $
       (prefix :) $
       intersperse prefix $
-      map (encodeBuilder False (level + 1)) (Vector.toList vec)
+      map (encodeBuilder alwaysQuote False (level + 1)) (Vector.toList vec)
       where prefix = bs "\n" <> indent level <> bs "- "
-    String s -> bl (enc s)
-    Number n -> bl (enc n)
-    Bool bool -> bl (enc bool)
+    String s -> encodeText True alwaysQuote level s
+    Number n -> bl (Data.Aeson.encode n)
+    Bool bool -> bl (Data.Aeson.encode bool)
     Null -> bs "null"
   where
     keyValue level' (k, v) =
       mconcat
-        [ b (Text.Encoding.encodeUtf8 k)
+        [ encodeText False alwaysQuote level k
         , ":"
         , case v of
             Object _ -> ""
             Array _ -> ""
             _ -> " "
-        , encodeBuilder True (level' + 1) v
+        , encodeBuilder alwaysQuote True (level' + 1) v
         ]
+
+encodeText :: Bool -> Bool -> Int -> Text -> Builder
+encodeText canMultiline alwaysQuote level s
+  | canMultiline && "\n" `Text.isSuffixOf` s = encodeLines level (Text.lines s)
+  | alwaysQuote && unquotable =
+    bs "'" <> b (Text.Encoding.encodeUtf8 s) <> bs "'"
+  | alwaysQuote || not unquotable = bl $ Data.Aeson.encode s
+  | otherwise = b (Text.Encoding.encodeUtf8 s)
+  where
+    unquotable =
+      s /= "" &&
+      (not $ isSpecial s) &&
+      isSafeAscii (Text.head s) &&
+      not (Text.all isNumberRelated s) && Text.all isAllowed s
+    isSpecial s'
+      | Text.length s > 5 = False
+      | otherwise =
+        case Text.toLower s' of
+          "true" -> True
+          "false" -> True
+          "on" -> True
+          "off" -> True
+          "y" -> True
+          "yes" -> True
+          "n" -> True
+          "no" -> True
+          _ -> False
+    isSafeAscii c =
+      (c >= 'a' && c <= 'z') ||
+      (c >= 'A' && c <= 'Z') ||
+      (c >= '0' && c <= '9') || c == '/' || c == '_' || c == '.' || c == '='
+    isNumberRelated c = isDigit c || c == '.' || c == 'e'
+    isAllowed c
+      -- We don't include ' ' here to avoid sequences like " -" and ": "
+     = isSafeAscii c || c == '-' || c == ':'
+
+encodeLines :: Int -> [Text] -> Builder
+encodeLines level ls =
+  mconcat $
+  (prefix :) $
+  intersperse (bs "\n" <> indent level) $ map (b . Text.Encoding.encodeUtf8) ls
+  where
+    prefix =
+      mconcat
+        [ bs "|"
+        , if needsIndicator
+            then bs "2"
+            else mempty
+        , "\n"
+        , indent level
+        ]
+    needsIndicator =
+      case ls of
+        (line:_) -> " " `Text.isPrefixOf` line
+        _ -> False
diff --git a/test/Test/Data/Aeson/Yaml.hs b/test/Test/Data/Aeson/Yaml.hs
--- a/test/Test/Data/Aeson/Yaml.hs
+++ b/test/Test/Data/Aeson/Yaml.hs
@@ -18,13 +18,14 @@
 import Test.Tasty (TestTree, testGroup)
 import Test.Tasty.HUnit (assertEqual, testCase)
 
-import qualified Data.Aeson.Yaml
+import Data.Aeson.Yaml
 
 data TestCase =
   TestCase
     { tcName :: String
     , tcInput :: Data.ByteString.Lazy.ByteString
     , tcOutput :: Data.ByteString.Lazy.ByteString
+    , tcAlwaysQuote :: Bool
     }
 
 testCases :: [TestCase]
@@ -34,6 +35,18 @@
       , tcInput =
           [s|
 {
+   "nullValue": null,
+   "isTrue": true,
+   "isFalse": false,
+   "numberString": "12345",
+   "quoted ! key": true,
+   "ON": "ON",
+   "off": "off",
+   "piString": "3.14",
+   "expString": "1e3",
+   "multiLine": "The first line is followed by the\nsecond line\n",
+   "multiLineWithSpaces": "  This has\nextra spaces at the beginning\n",
+   "notMultiline": "This won't be\nmulti-lined",
    "apiVersion": "apps/v1",
    "kind": "Deployment",
    "metadata": {
@@ -70,6 +83,7 @@
                         "containerPort": 7654
                      }
                   ],
+                  "script": "#!/bin/bash\necho hello world\n",
                   "volumeMounts": [
                      {
                         "mountPath": "/data/mount1",
@@ -88,37 +102,67 @@
 }
 |]
       , tcOutput =
-          [s|apiVersion: "apps/v1"
-kind: "Deployment"
+          [s|"ON": "ON"
+apiVersion: apps/v1
+expString: "1e3"
+isFalse: false
+isTrue: true
+kind: Deployment
 metadata:
   labels:
-    app: "foo"
+    app: foo
   name: "{{ .Release.Name }}-deployment"
+multiLine: |
+  The first line is followed by the
+  second line
+multiLineWithSpaces: |2
+    This has
+  extra spaces at the beginning
+notMultiline: "This won't be\nmulti-lined"
+nullValue: null
+numberString: "12345"
+"off": "off"
+piString: "3.14"
+"quoted ! key": true
 spec:
   replicas: 1
   selector:
     matchLabels:
-      app: "foo"
+      app: foo
   template:
     metadata:
       labels:
-        app: "foo"
+        app: foo
       name: "{{ .Release.Name }}-pod"
     spec:
       containers:
         - command:
-            - "/data/bin/foo"
+            - /data/bin/foo
             - "--port=7654"
-          image: "ubuntu:latest"
+          image: ubuntu:latest
           name: "{{ .Release.Name }}-container"
           ports:
             - containerPort: 7654
+          script: |
+            #!/bin/bash
+            echo hello world
           volumeMounts:
-            - mountPath: "/data/mount1"
+            - mountPath: /data/mount1
               name: "{{ .Release.Name }}-volume-mount1"
-            - mountPath: "/data/mount2"
-              name: "{{ .Release.Name }}-volume-mount2"|]
+            - mountPath: /data/mount2
+              name: "{{ .Release.Name }}-volume-mount2"
+|]
+      , tcAlwaysQuote = False
       }
+  , TestCase
+      { tcName = "Quoted"
+      , tcInput = [s|{"foo": "bar", "baz": "quux"}|]
+      , tcOutput =
+          [s|'baz': 'quux'
+'foo': 'bar'
+|]
+      , tcAlwaysQuote = True
+      }
   ]
 
 foo :: Data.Aeson.Value
@@ -134,12 +178,21 @@
           "libyaml decodes the original value"
           decodedInput
           decodedYaml
-        assertEqual
-          "Expected documents output"
-          ("foo: \"bar\"" <> "\n---\n" <> output)
-          (Data.Aeson.Yaml.encodeDocuments [foo, decodedInput])
+        if tcAlwaysQuote
+          then assertEqual
+                 "Expected documents output"
+                 ("'foo': 'bar'" <> "\n---\n" <> output)
+                 (encodeQuotedDocuments [foo, decodedInput])
+          else assertEqual
+                 "Expected documents output"
+                 ("foo: bar" <> "\n---\n" <> output)
+                 (encodeDocuments [foo, decodedInput])
       where
-        output = Data.Aeson.Yaml.encode decodedInput
+        output =
+          (if tcAlwaysQuote
+             then encodeQuoted
+             else encode)
+            decodedInput
         decodedInput :: Data.Aeson.Value
         decodedInput =
           fromRight (error "couldn't decode JSON") $
