diff --git a/.eslintrc.json b/.eslintrc.json
index df6b9f8a7cdb772f9b2df444ad4a98c11b4a4e5a..82b9a5e71713b3c51ed30587c80b34a3b62a473e 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -38,7 +38,8 @@
                 "selector": "ExportDefaultDeclaration",
                 "message": "Default exports are not allowed"
             }
-        ]
+        ],
+        "no-throw-literal": "error"
     },
     "overrides": [
         {
diff --git a/src/cli/cifschema/index.ts b/src/cli/cifschema/index.ts
index bfcdf04f719057032a2ff2633903335d38b083bf..8845afe9cdd49dfe239995b2ccb96d312748b9b8 100644
--- a/src/cli/cifschema/index.ts
+++ b/src/cli/cifschema/index.ts
@@ -124,7 +124,7 @@ async function getFieldNamesFilter(fieldNamesPath: string): Promise<Filter> {
     const csvFile = parsed.result;
 
     const fieldNamesCol = csvFile.table.getColumn('0');
-    if (!fieldNamesCol) throw 'error getting fields columns';
+    if (!fieldNamesCol) throw new Error('error getting fields columns');
     const fieldNames = fieldNamesCol.toStringArray();
 
     const filter: Filter = {};
diff --git a/src/extensions/anvil/algorithm.ts b/src/extensions/anvil/algorithm.ts
index 8c960bffc7c89468b8d95631b50a7aa9f837db2d..6ce492717fc095559495e0643ea1571daa74c4c0 100644
--- a/src/extensions/anvil/algorithm.ts
+++ b/src/extensions/anvil/algorithm.ts
@@ -446,7 +446,7 @@ function membraneSegments(ctx: ANVILContext, membrane: MembraneCandidate): Array
 }
 
 function notAtomic(): never {
-    throw 'Property only available for atomic models.';
+    throw new Error('Property only available for atomic models.');
 }
 
 /** Filter for membrane residues and calculate the final extent of the membrane layer */
diff --git a/src/mol-gl/_spec/gl.shim.ts b/src/mol-gl/_spec/gl.shim.ts
index f0304b670a71d0b5c6d7715969d12ca030585a15..99c5a340448c57a22d43d1ed14a1c3dba1b76707 100644
--- a/src/mol-gl/_spec/gl.shim.ts
+++ b/src/mol-gl/_spec/gl.shim.ts
@@ -601,7 +601,7 @@ export function createGl(width: number, height: number, contextAttributes: WebGL
             switch (pname) {
                 case gl.SHADER_TYPE: return items[shader as number].type;
                 case gl.COMPILE_STATUS: return true;
-                default: throw `getShaderParameter ${pname}`;
+                default: throw new Error(`getShaderParameter ${pname}`);
             }
         },
         shaderSource: function () { },
@@ -628,7 +628,7 @@ export function createGl(width: number, height: number, contextAttributes: WebGL
                 case gl.DELETE_STATUS: return false;
                 case gl.VALIDATE_STATUS: return true;
                 case gl.ATTACHED_SHADERS: return 2;
-                default: throw `getProgramParameter ${pname}`;
+                default: throw new Error(`getProgramParameter ${pname}`);
             }
         },
         deleteShader: function () { },
diff --git a/src/mol-math/linear-algebra/matrix/evd.ts b/src/mol-math/linear-algebra/matrix/evd.ts
index 06e81db62541760b0eb388275e6917143eff0cb6..eae45e574d4371c418ceaa712ca80ed2947656f4 100644
--- a/src/mol-math/linear-algebra/matrix/evd.ts
+++ b/src/mol-math/linear-algebra/matrix/evd.ts
@@ -263,7 +263,7 @@ function symmetricDiagonalize(a: number[], d: number[], e: number[], order: numb
                 // Check for convergence. If too many iterations have been performed,
                 // throw exception that Convergence Failed
                 if (iter >= maxiter) {
-                    throw 'SVD: Not converging.';
+                    throw new Error('SVD: Not converging.');
                 }
             } while (Math.abs(e[l]) > eps * tst1);
         }
diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts
index eed0dd230ee2b0ffea1a076e4aec201961856ffc..6615a4f87199336ab5497743a33ce49afd7c62e4 100644
--- a/src/mol-model/structure/export/mmcif.ts
+++ b/src/mol-model/structure/export/mmcif.ts
@@ -140,7 +140,7 @@ type encode_mmCIF_categories_Params = {
 export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures: Structure | Structure[], params?: encode_mmCIF_categories_Params) {
     const first = Array.isArray(structures) ? structures[0] : (structures as Structure);
     const models = first.models;
-    if (models.length !== 1) throw 'Can\'t export stucture composed from multiple models.';
+    if (models.length !== 1) throw new Error('Can\'t export stucture composed from multiple models.');
 
     const ctx: CifExportContext = params?.exportCtx || CifExportContext.create(structures);
 
diff --git a/src/mol-model/structure/structure/properties.ts b/src/mol-model/structure/structure/properties.ts
index 1157777ce160e3ab1f5845dc61622c7f381539bd..f526e642fb5e55cf63039280c12b41ea95b691df 100644
--- a/src/mol-model/structure/structure/properties.ts
+++ b/src/mol-model/structure/structure/properties.ts
@@ -21,12 +21,12 @@ const constant = {
 };
 
 function notAtomic(): never {
-    throw 'Property only available for atomic models.';
+    throw new Error('Property only available for atomic models.');
 }
 
 function notCoarse(kind?: string): never {
-    if (!!kind) throw `Property only available for coarse models (${kind}).`;
-    throw `Property only available for coarse models.`;
+    if (!!kind) throw new Error(`Property only available for coarse models (${kind}).`);
+    throw new Error('Property only available for coarse models.');
 }
 
 // TODO: remove the type checks?
diff --git a/src/mol-util/zip/bin.ts b/src/mol-util/zip/bin.ts
index ed36a8fe473cf47c096e14e29fe2dc0a5abad64f..9d2192f6ac3c7323bf8571c6acf0b09597026d01 100644
--- a/src/mol-util/zip/bin.ts
+++ b/src/mol-util/zip/bin.ts
@@ -81,7 +81,7 @@ export function writeUTF8(buff: Uint8Array, p: number, str: string) {
             buff[p + i + 2] = (128 | ((code >> 6) & 63));
             buff[p + i + 3] = (128 | ((code >> 0) & 63));
             i += 4;
-        } else throw 'e';
+        } else throw new Error('e');
     }
     return i;
 }
@@ -100,7 +100,7 @@ export function sizeUTF8(str: string) {
         } else if((code & (0xffffffff - (1 << 21) + 1)) === 0) {
             i += 4;
         } else {
-            throw 'e';
+            throw new Error('e');
         }
     }
     return i;
diff --git a/src/mol-util/zip/zip.ts b/src/mol-util/zip/zip.ts
index b38898adc02c4c7ac0aac27f9bcd6f8aa599e617..8086d307cc054522e205c8c89886d306b90b3e79 100644
--- a/src/mol-util/zip/zip.ts
+++ b/src/mol-util/zip/zip.ts
@@ -106,7 +106,7 @@ async function _readLocal(runtime: RuntimeContext, data: Uint8Array, o: number,
         await inflateRaw(runtime, file, buf);
         out[name] = buf;
     } else {
-        throw `unknown compression method: ${cmpr}`;
+        throw new Error(`unknown compression method: ${cmpr}`);
     }
 }
 
diff --git a/src/servers/model/server/api.ts b/src/servers/model/server/api.ts
index 76edf445d20ca4ad4038d4bdb930c50609c5c622..159ef411b821aba65077bcdff9e71016487c76c8 100644
--- a/src/servers/model/server/api.ts
+++ b/src/servers/model/server/api.ts
@@ -125,7 +125,7 @@ const RadiusParam: QueryParamInfo = {
     description: 'Value in Angstroms.',
     validation(v: any) {
         if (v < 1 || v > 10) {
-            throw `Invalid radius for residue interaction query (must be a value between 1 and 10).`;
+            throw new Error('Invalid radius for residue interaction query (must be a value between 1 and 10).');
         }
     }
 };
@@ -286,7 +286,7 @@ function _normalizeQueryParams(params: { [p: string]: string }, paramList: Query
         let el: any;
         if (typeof value === 'undefined' || (typeof value !== 'undefined' && value !== null && value['length'] === 0)) {
             if (p.required) {
-                throw `The parameter '${key}' is required.`;
+                throw new Error(`The parameter '${key}' is required.`);
             }
             if (typeof p.defaultValue !== 'undefined') el = p.defaultValue;
         } else {
diff --git a/src/servers/volume/server/query/execute.ts b/src/servers/volume/server/query/execute.ts
index 6975279a033014382a73db0c60925e6b056bd9d7..de874adebb2058a34cbdd67fc333b32a9c4783dd 100644
--- a/src/servers/volume/server/query/execute.ts
+++ b/src/servers/volume/server/query/execute.ts
@@ -166,11 +166,11 @@ function createQueryContext(data: Data.DataContext, params: Data.QueryParams, gu
 
     const dimensions = Box.dimensions(queryBox);
     if (dimensions.some(d => isNaN(d))) {
-        throw `The query box is not defined.`;
+        throw new Error('The query box is not defined.');
     }
 
     if (dimensions[0] * dimensions[1] * dimensions[2] > LimitsConfig.maxFractionalBoxVolume) {
-        throw `The query box volume is too big.`;
+        throw new Error('The query box volume is too big.');
     }
 
     const samplingInfo = pickSampling(data, queryBox, params.forcedSamplingLevel !== void 0 ? params.forcedSamplingLevel : 0, params.detail);