diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d0efa108..726d736c7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@
 - Expand options in completion to make working with options a bit more ergonomic. https://github.com/rescript-lang/rescript-vscode/pull/690
 - Let `_` trigger completion in patterns. https://github.com/rescript-lang/rescript-vscode/pull/692
 - Support inline records in completion. https://github.com/rescript-lang/rescript-vscode/pull/695
+- Add way to autocomplete an exhaustive switch statement for identifiers. Example: an identifier that's a variant can have a switch autoinserted matching all variant cases. https://github.com/rescript-lang/rescript-vscode/pull/699
 
 #### :nail_care: Polish
 
diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml
index fab77ce18..64ad93a5e 100644
--- a/analysis/src/CompletionBackEnd.ml
+++ b/analysis/src/CompletionBackEnd.ml
@@ -232,6 +232,7 @@ let detail name (kind : Completion.kind) =
           |> String.concat ", ")
         ^ ")")
     ^ "\n\n" ^ s
+  | Snippet s -> s
 
 let findAllCompletions ~(env : QueryEnv.t) ~prefix ~exact ~namesUsed
     ~(completionContext : Completable.completionContext) =
@@ -552,6 +553,7 @@ let mkItem ~name ~kind ~detail ~deprecated ~docstring =
       sortText = None;
       insertText = None;
       insertTextFormat = None;
+      filterText = None;
     }
 
 let completionToItem
@@ -563,6 +565,7 @@ let completionToItem
       sortText;
       insertText;
       insertTextFormat;
+      filterText;
     } =
   let item =
     mkItem ~name
@@ -570,7 +573,7 @@ let completionToItem
       ~deprecated ~detail:(detail name kind) ~docstring
   in
   if !Cfg.supportsSnippets then
-    {item with sortText; insertText; insertTextFormat}
+    {item with sortText; insertText; insertTextFormat; filterText}
   else item
 
 let completionsGetTypeEnv = function
@@ -1304,3 +1307,69 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover
           in
           items @ regularCompletions
         | _ -> items)))
+  | CexhaustiveSwitch {contextPath; exprLoc} ->
+    let range = Utils.rangeOfLoc exprLoc in
+    let printFailwithStr num =
+      "${" ^ string_of_int num ^ ":failwith(\"todo\")}"
+    in
+    let withExhaustiveItem ~cases ?(startIndex = 0) (c : Completion.t) =
+      (* We don't need to write out `switch` here since we know that's what the
+         user has already written. Just complete for the rest. *)
+      let newText =
+        c.name ^ " {\n"
+        ^ (cases
+          |> List.mapi (fun index caseText ->
+                 "| " ^ caseText ^ " => "
+                 ^ printFailwithStr (startIndex + index + 1))
+          |> String.concat "\n")
+        ^ "\n}"
+        |> Utils.indent range.start.character
+      in
+      [
+        c;
+        {
+          c with
+          name = c.name ^ " (exhaustive switch)";
+          filterText = Some c.name;
+          insertTextFormat = Some Snippet;
+          insertText = Some newText;
+          kind = Snippet "insert exhaustive switch for value";
+        };
+      ]
+    in
+    let completionsForContextPath =
+      contextPath
+      |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
+           ~exact:forHover ~scope
+    in
+    completionsForContextPath
+    |> List.map (fun (c : Completion.t) ->
+           match c.kind with
+           | Value typExpr -> (
+             match typExpr |> TypeUtils.extractType ~env:c.env ~package with
+             | Some (Tvariant v) ->
+               withExhaustiveItem c
+                 ~cases:
+                   (v.constructors
+                   |> List.map (fun (constructor : Constructor.t) ->
+                          constructor.cname.txt
+                          ^
+                          match constructor.args with
+                          | Args [] -> ""
+                          | _ -> "(_)"))
+             | Some (Tpolyvariant v) ->
+               withExhaustiveItem c
+                 ~cases:
+                   (v.constructors
+                   |> List.map (fun (constructor : polyVariantConstructor) ->
+                          "| #" ^ constructor.name
+                          ^
+                          match constructor.args with
+                          | [] -> ""
+                          | _ -> "(_)"))
+             | Some (Toption (_env, _typ)) ->
+               withExhaustiveItem c ~cases:["Some($1)"; "None"] ~startIndex:1
+             | Some (Tbool _) -> withExhaustiveItem c ~cases:["true"; "false"]
+             | _ -> [c])
+           | _ -> [c])
+    |> List.flatten
diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml
index 4af54f4a7..eee350c40 100644
--- a/analysis/src/CompletionFrontEnd.ml
+++ b/analysis/src/CompletionFrontEnd.ml
@@ -353,14 +353,18 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
   let unsetLookingForPat () = lookingForPat := None in
   (* Identifies expressions where we can do typed pattern or expr completion. *)
   let typedCompletionExpr (exp : Parsetree.expression) =
-    if
-      exp.pexp_loc
-      |> CursorPosition.classifyLoc ~pos:posBeforeCursor
-      = HasCursor
-    then
+    if exp.pexp_loc |> CursorPosition.locHasCursor ~pos:posBeforeCursor then
       match exp.pexp_desc with
-      | Pexp_match (_exp, []) ->
-        (* No cases means there's no `|` yet in the switch *) ()
+      (* No cases means there's no `|` yet in the switch *)
+      | Pexp_match (({pexp_desc = Pexp_ident _} as expr), []) -> (
+        if locHasCursor expr.pexp_loc then
+          (* We can do exhaustive switch completion if this is an ident we can
+             complete from. *)
+          match exprToContextPath expr with
+          | None -> ()
+          | Some contextPath ->
+            setResult (CexhaustiveSwitch {contextPath; exprLoc = exp.pexp_loc}))
+      | Pexp_match (_expr, []) -> ()
       | Pexp_match
           ( exp,
             [
diff --git a/analysis/src/Protocol.ml b/analysis/src/Protocol.ml
index bd464b91d..b9b82b08f 100644
--- a/analysis/src/Protocol.ml
+++ b/analysis/src/Protocol.ml
@@ -47,6 +47,7 @@ type completionItem = {
   tags: int list;
   detail: string;
   sortText: string option;
+  filterText: string option;
   insertTextFormat: insertTextFormat option;
   insertText: string option;
   documentation: markupContent option;
@@ -129,6 +130,7 @@ let stringifyCompletionItem c =
           | None -> null
           | Some doc -> stringifyMarkupContent doc) );
       ("sortText", optWrapInQuotes c.sortText);
+      ("filterText", optWrapInQuotes c.filterText);
       ("insertText", optWrapInQuotes c.insertText);
       ( "insertTextFormat",
         match c.insertTextFormat with
diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml
index 9448da7a8..132b05dee 100644
--- a/analysis/src/SharedTypes.ml
+++ b/analysis/src/SharedTypes.ml
@@ -305,11 +305,13 @@ module Completion = struct
     | PolyvariantConstructor of polyVariantConstructor * string
     | Field of field * string
     | FileModule of string
+    | Snippet of string
 
   type t = {
     name: string;
     sortText: string option;
     insertText: string option;
+    filterText: string option;
     insertTextFormat: Protocol.insertTextFormat option;
     env: QueryEnv.t;
     deprecated: string option;
@@ -317,7 +319,7 @@ module Completion = struct
     kind: kind;
   }
 
-  let create ~kind ~env ?(docstring = []) name =
+  let create ~kind ~env ?(docstring = []) ?filterText name =
     {
       name;
       env;
@@ -327,10 +329,11 @@ module Completion = struct
       sortText = None;
       insertText = None;
       insertTextFormat = None;
+      filterText;
     }
 
-  let createWithSnippet ~name ?insertText ~kind ~env ?sortText ?(docstring = [])
-      () =
+  let createWithSnippet ~name ?insertText ~kind ~env ?sortText ?filterText
+      ?(docstring = []) () =
     {
       name;
       env;
@@ -340,6 +343,7 @@ module Completion = struct
       sortText;
       insertText;
       insertTextFormat = Some Protocol.Snippet;
+      filterText;
     }
 
   (* https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion *)
@@ -354,6 +358,7 @@ module Completion = struct
     | Field (_, _) -> 5
     | Type _ -> 22
     | Value _ -> 12
+    | Snippet _ -> 15
 end
 
 module Env = struct
@@ -613,6 +618,7 @@ module Completable = struct
         prefix: string;
         fallback: t option;
       }
+    | CexhaustiveSwitch of {contextPath: contextPath; exprLoc: Location.t}
 
   (** An extracted type from a type expr *)
   type extractedType =
@@ -720,6 +726,8 @@ module Completable = struct
         ^ (nestedPaths
           |> List.map (fun nestedPath -> nestedPathToString nestedPath)
           |> String.concat ", "))
+    | CexhaustiveSwitch {contextPath} ->
+      "CexhaustiveSwitch " ^ contextPathToString contextPath
 end
 
 module CursorPosition = struct
diff --git a/analysis/src/Utils.ml b/analysis/src/Utils.ml
index 19f41d93c..c5dfed716 100644
--- a/analysis/src/Utils.ml
+++ b/analysis/src/Utils.ml
@@ -181,4 +181,28 @@ let rec getUnqualifiedName txt =
   match txt with
   | Longident.Lident fieldName -> fieldName
   | Ldot (t, _) -> getUnqualifiedName t
-  | _ -> ""
\ No newline at end of file
+  | _ -> ""
+
+let indent n text =
+  let spaces = String.make n ' ' in
+  let len = String.length text in
+  let text =
+    if len != 0 && text.[len - 1] = '\n' then String.sub text 0 (len - 1)
+    else text
+  in
+  let lines = String.split_on_char '\n' text in
+  match lines with
+  | [] -> ""
+  | [line] -> line
+  | line :: lines ->
+    line ^ "\n"
+    ^ (lines |> List.map (fun line -> spaces ^ line) |> String.concat "\n")
+
+let mkPosition (pos : Pos.t) =
+  let line, character = pos in
+  {Protocol.line; character}
+
+let rangeOfLoc (loc : Location.t) =
+  let start = loc |> Loc.start |> mkPosition in
+  let end_ = loc |> Loc.end_ |> mkPosition in
+  {Protocol.start; end_}
diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml
index dc8fa8cb9..dd4f1b395 100644
--- a/analysis/src/Xform.ml
+++ b/analysis/src/Xform.ml
@@ -252,21 +252,6 @@ module AddTypeAnnotation = struct
         | _ -> ()))
 end
 
-let indent n text =
-  let spaces = String.make n ' ' in
-  let len = String.length text in
-  let text =
-    if len != 0 && text.[len - 1] = '\n' then String.sub text 0 (len - 1)
-    else text
-  in
-  let lines = String.split_on_char '\n' text in
-  match lines with
-  | [] -> ""
-  | [line] -> line
-  | line :: lines ->
-    line ^ "\n"
-    ^ (lines |> List.map (fun line -> spaces ^ line) |> String.concat "\n")
-
 let parse ~filename =
   let {Res_driver.parsetree = structure; comments} =
     Res_driver.parsingEngine.parseImplementation ~forPrinter:false ~filename
@@ -283,7 +268,7 @@ let parse ~filename =
     structure
     |> Res_printer.printImplementation ~width:!Res_cli.ResClflags.width
          ~comments:(comments |> filterComments ~loc:expr.pexp_loc)
-    |> indent range.start.character
+    |> Utils.indent range.start.character
   in
   let printStructureItem ~(range : Protocol.range)
       (item : Parsetree.structure_item) =
@@ -291,7 +276,7 @@ let parse ~filename =
     structure
     |> Res_printer.printImplementation ~width:!Res_cli.ResClflags.width
          ~comments:(comments |> filterComments ~loc:item.pstr_loc)
-    |> indent range.start.character
+    |> Utils.indent range.start.character
   in
   (structure, printExpr, printStructureItem)
 
diff --git a/analysis/tests/src/ExhaustiveSwitch.res b/analysis/tests/src/ExhaustiveSwitch.res
new file mode 100644
index 000000000..eb23a1aff
--- /dev/null
+++ b/analysis/tests/src/ExhaustiveSwitch.res
@@ -0,0 +1,19 @@
+type someVariant = One | Two | Three(option<bool>)
+type somePolyVariant = [#one | #two | #three(option<bool>)]
+
+let withSomeVariant = One
+let withSomePoly: somePolyVariant = #one
+let someBool = true
+let someOpt = Some(true)
+
+// switch withSomeVarian
+//                      ^com
+
+// switch withSomePol
+//                   ^com
+
+// switch someBoo
+//               ^com
+
+// switch someOp
+//              ^com
diff --git a/analysis/tests/src/expected/ExhaustiveSwitch.res.txt b/analysis/tests/src/expected/ExhaustiveSwitch.res.txt
new file mode 100644
index 000000000..c9616503c
--- /dev/null
+++ b/analysis/tests/src/expected/ExhaustiveSwitch.res.txt
@@ -0,0 +1,80 @@
+Complete src/ExhaustiveSwitch.res 8:24
+XXX Not found!
+Completable: CexhaustiveSwitch Value[withSomeVarian]
+[{
+    "label": "withSomeVariant",
+    "kind": 12,
+    "tags": [],
+    "detail": "someVariant",
+    "documentation": null
+  }, {
+    "label": "withSomeVariant (exhaustive switch)",
+    "kind": 15,
+    "tags": [],
+    "detail": "insert exhaustive switch for value",
+    "documentation": null,
+    "filterText": "withSomeVariant",
+    "insertText": "withSomeVariant {\n   | One => ${1:failwith(\"todo\")}\n   | Two => ${2:failwith(\"todo\")}\n   | Three(_) => ${3:failwith(\"todo\")}\n   }",
+    "insertTextFormat": 2
+  }]
+
+Complete src/ExhaustiveSwitch.res 11:21
+XXX Not found!
+Completable: CexhaustiveSwitch Value[withSomePol]
+[{
+    "label": "withSomePoly",
+    "kind": 12,
+    "tags": [],
+    "detail": "somePolyVariant",
+    "documentation": null
+  }, {
+    "label": "withSomePoly (exhaustive switch)",
+    "kind": 15,
+    "tags": [],
+    "detail": "insert exhaustive switch for value",
+    "documentation": null,
+    "filterText": "withSomePoly",
+    "insertText": "withSomePoly {\n   | | #one => ${1:failwith(\"todo\")}\n   | | #three(_) => ${2:failwith(\"todo\")}\n   | | #two => ${3:failwith(\"todo\")}\n   }",
+    "insertTextFormat": 2
+  }]
+
+Complete src/ExhaustiveSwitch.res 14:17
+XXX Not found!
+Completable: CexhaustiveSwitch Value[someBoo]
+[{
+    "label": "someBool",
+    "kind": 12,
+    "tags": [],
+    "detail": "bool",
+    "documentation": null
+  }, {
+    "label": "someBool (exhaustive switch)",
+    "kind": 15,
+    "tags": [],
+    "detail": "insert exhaustive switch for value",
+    "documentation": null,
+    "filterText": "someBool",
+    "insertText": "someBool {\n   | true => ${1:failwith(\"todo\")}\n   | false => ${2:failwith(\"todo\")}\n   }",
+    "insertTextFormat": 2
+  }]
+
+Complete src/ExhaustiveSwitch.res 17:16
+XXX Not found!
+Completable: CexhaustiveSwitch Value[someOp]
+[{
+    "label": "someOpt",
+    "kind": 12,
+    "tags": [],
+    "detail": "option<bool>",
+    "documentation": null
+  }, {
+    "label": "someOpt (exhaustive switch)",
+    "kind": 15,
+    "tags": [],
+    "detail": "insert exhaustive switch for value",
+    "documentation": null,
+    "filterText": "someOpt",
+    "insertText": "someOpt {\n   | Some($1) => ${2:failwith(\"todo\")}\n   | None => ${3:failwith(\"todo\")}\n   }",
+    "insertTextFormat": 2
+  }]
+
diff --git a/snippets.json b/snippets.json
index 62cc64980..47281539a 100644
--- a/snippets.json
+++ b/snippets.json
@@ -9,17 +9,6 @@
 			"}"
 		]
 	},
-	"Switch": {
-		"prefix": [
-			"switch"
-		],
-		"body": [
-			"switch ${1:value} {",
-			"| ${2:pattern1} => ${3:expression}",
-			"${4:| ${5:pattern2} => ${6:expression}}",
-			"}"
-		]
-	},
 	"Try": {
 		"prefix": [
 			"try"