diff --git a/devTools/types/FC/RA.d.ts b/devTools/types/FC/RA.d.ts
index d7fbd09d62bf3e47f32332b909a959b9b1b1d2b1..37ee9b6b548fe1ca808df724b1d2ca3c97402208 100644
--- a/devTools/types/FC/RA.d.ts
+++ b/devTools/types/FC/RA.d.ts
@@ -5,6 +5,11 @@ declare namespace FC {
 			val: number;
 		}
 
+		interface ExpressiveNumericTarget {
+			cond: string;
+			val: string | number;
+		}
+
 		interface NumericRange {
 			min: number;
 			max: number;
@@ -47,11 +52,11 @@ declare namespace FC {
 		}
 
 		interface RuleGrowthSetters {
-			boobs: NumericTarget;
-			butt: NumericTarget;
-			lips: NumericTarget;
-			dick: NumericTarget;
-			balls: NumericTarget;
+			boobs: ExpressiveNumericTarget;
+			butt: ExpressiveNumericTarget;
+			lips: ExpressiveNumericTarget;
+			dick: ExpressiveNumericTarget;
+			balls: ExpressiveNumericTarget;
 			intensity: number;
 		}
 
diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js
index 1523f20d3a9b7eeb740d5d086e719b16ce08289a..76b215d039868c3e5f79d982b7c32668b8dd90d8 100644
--- a/js/003-data/gameVariableData.js
+++ b/js/003-data/gameVariableData.js
@@ -1188,7 +1188,8 @@ App.Data.resetOnNGPlus = {
 		food: 0,
 		animalOvaries: 0,
 		dinnerParty: 0,
-		reportMissingClothing: 0
+		reportMissingClothing: 0,
+		raGrowthExpr: 0
 	},
 	NaNArray: [],
 
diff --git a/src/gui/options/options.js b/src/gui/options/options.js
index 037fc71801063b67c1f78870b5f0f2af9bc0372f..489a617626f2c9b3e5610caff3bd340970f5f4b7 100644
--- a/src/gui/options/options.js
+++ b/src/gui/options/options.js
@@ -1000,6 +1000,10 @@ App.UI.optionsPassage = function() {
 				.addValue("Enabled", 1).on().addValue("Disabled", 0).off();
 		}
 
+		options.addOption("Rules Assistant target growth expressions", "raGrowthExpr", V.experimental)
+			.addValue("Enabled", 1).on().addValue("Disabled", 0).off()
+			.addComment("This will allow javascript expressions to be used in the Rules Assistant growth target setters.");
+
 		options.addOption("New event", "tempEventToggle")
 			.addValue("Enabled", 1).on().addValue("Disabled", 0).off();
 
diff --git a/src/js/DefaultRules.js b/src/js/DefaultRules.js
index 4ec6ae7de1b2090b828ef9584a62f941b7b22814..4d128718a86385122d4a74b6111cb7de0958de04 100644
--- a/src/js/DefaultRules.js
+++ b/src/js/DefaultRules.js
@@ -1173,7 +1173,15 @@ globalThis.DefaultRules = (function() {
 			r += `<br>${slave.slaveName} is on ${slave.drugs} and will not be considered for drug enhancement until that regime is complete.`;
 			ProcessOtherDrugs(slave, rule);
 			return;
-		} else if (rule.growth.boobs === null && rule.growth.butt === null && rule.growth.lips === null && rule.growth.dick === null && rule.growth.balls === null) {
+		} else if (
+			[
+				rule.growth.boobs,
+				rule.growth.butt,
+				rule.growth.lips,
+				rule.growth.dick,
+				rule.growth.balls
+			].every(r => r === null)  // Check if all objects in list equal null
+		){
 			ProcessOtherDrugs(slave, rule);
 			return;
 		}
@@ -1264,7 +1272,7 @@ globalThis.DefaultRules = (function() {
 		/**
 		 * @param {App.Entity.SlaveState} slave
 		 * @param {DrugTarget} asset
-		 * @param {FC.RA.NumericTarget} target
+		 * @param {FC.RA.NumericTarget | FC.RA.ExpressiveNumericTarget} target
 		 * @param {{drug: FC.Drug, weight: number}[]} priorities
 		 * @param {number} step
 		 */
@@ -1273,6 +1281,14 @@ globalThis.DefaultRules = (function() {
 				return;
 			}
 
+			if (V.experimental.raGrowthExpr === 1 && typeof target.val === 'string') {
+				const interpreter = new Function("slave", "return (" + target.val + ");");
+				target.val = runWithReadonlyProxy(() => interpreter(createReadonlyProxy(slave)));
+				if (V.debugMode) {
+					console.log(asset + " expression for '" + slave.slaveName + "' resolves to " + target.val.toString());
+				}
+			}
+
 			const flesh = fleshFunc[asset](slave);
 			if (growDrugs[asset] !== null && App.RA.shallGrow(flesh, target, step) && maxAssetSize[asset] > slave[asset]) {
 				priorities.push({
diff --git a/src/js/rulesAssistantOptions.js b/src/js/rulesAssistantOptions.js
index 1404d02b08e6d834c27505be5df6c77e063d3e54..c71f8ae59cc0be5ee1d33b969eede4b14304546a 100644
--- a/src/js/rulesAssistantOptions.js
+++ b/src/js/rulesAssistantOptions.js
@@ -887,6 +887,73 @@ App.RA.options = (function() {
 		}
 	}
 
+	//  Basically just a copy of NumericTargetEditor modified to handle strings as well
+	class ExpressiveNumericTargetEditor extends EditorWithShortcuts {
+		/**
+		 * @param {string} prefix
+		 * @param {Array} [data=[]]
+		 * @param {boolean} [allowNullValue=true]
+		 * @param {number} [min=0]
+		 * @param {number} [max=100]
+		 * @param {boolean} [spinBox=false]
+		 */
+		constructor(prefix, data = [], allowNullValue = true, min = 0, max = 100, spinBox = false) {
+			super(prefix, data, allowNullValue, spinBox, true, min, max);
+		}
+
+		createEditor(min, max) {
+			function makeOp(op, ui) {
+				return {op: op, ui: ui};
+			}
+			this.opSelector = document.createElement("select");
+			for (const o of [makeOp('==', '='), makeOp('>=', "⩾"), makeOp('<=', '⩽'), makeOp('>', '>'), makeOp('<', '<')]) {
+				let opt = document.createElement("option");
+				opt.textContent = o.ui;
+				opt.value = o.op;
+				this.opSelector.appendChild(opt);
+			}
+			this.opSelector.classList.add("rajs-list");
+			this.opSelector.onchange = () => {
+				this.inputEdited();
+			};
+
+			this.numEditor = document.createElement("input");
+			this.numEditor.type = "text";
+			this.numEditor.classList.add("rajs-value");
+			this.numEditor.onblur = () => {
+				this.inputEdited();
+			};
+			this.numEditor.onkeypress = (e) => {
+				if (returnP(e)) { this.inputEdited(); }
+			};
+
+			const res = document.createElement("span");
+			res.appendChild(this.opSelector);
+			res.appendChild(this.numEditor);
+			return res;
+		}
+
+		setTextValue(what) {
+			if (typeof what === 'number') {
+				this.numEditor.value = what.toString();
+			} else if (typeof what === 'string') {
+				this.numEditor.value = what;
+			} else if (what === null) {
+				this.numEditor.value = null;
+				this.opSelector.value = '==';
+			} else if (typeof what === 'object') {
+				this.opSelector.value = what.cond;
+				this.numEditor.value = what.val;
+			}
+		}
+
+		getTextData() {
+			const n = this.numEditor.value !== "" ? Number(this.numEditor.value) : Number.NaN;  // Attempt to convert numEditor.value to number,
+			const v = isNaN(n) ? this.numEditor.value : Math.floor(n);                         // return numEditor.value as number if !NaN (should result in realValue being of number)
+			return v === null || v === "" ? null : { cond: this.opSelector.value, val: v };
+		}
+	}
+
 	// a way to organize lists with too many elements in subsections
 	// children are bound to the master list
 	class ListSubSection extends ElementWithLabel {
@@ -2072,14 +2139,25 @@ App.RA.options = (function() {
 			];
 			pairs.forEach(pair => this.appendChild(new OptionsItem(...pair)));
 
-			this.breasts = new BreastGrowthList();
-			this.butts = new ButtGrowthList();
-			this.lips = new LipGrowthList();
+			if (!V.experimental.raGrowthExpr) {
+				this.breasts = new BreastGrowthList();
+				this.butts = new ButtGrowthList();
+				this.lips = new LipGrowthList();
+			} else {
+				this.breasts = new ExprBreastGrowthList();
+				this.butts = new ExprButtGrowthList();
+				this.lips = new ExprLipGrowthList();
+			}
 			this.sublists.push(this.breasts, this.butts, this.lips);
 
 			if (V.seeDicks > 0 || V.makeDicks > 0) {
-				this.dicks = new DickGrowthList();
-				this.balls = new BallGrowthList();
+				if (!V.experimental.raGrowthExpr) {
+					this.dicks = new DickGrowthList();
+					this.balls = new BallGrowthList();
+				} else {
+					this.dicks = new ExprDickGrowthList();
+					this.balls = new ExprBallGrowthList();
+				}
 				this.sublists.push(this.dicks, this.balls);
 			}
 		}
@@ -2212,6 +2290,78 @@ App.RA.options = (function() {
 		}
 	}
 
+	class ExprBreastGrowthList extends ExpressiveNumericTargetEditor {
+		constructor() {
+			const pairs = [
+				["B-Cup", 350],
+				["D-Cup", 1000],
+				["monstrous", 9000],
+				["unlimited", 48000],
+				["none", 0]
+			];
+			super("Breasts", pairs, true, 0, 48000, true);
+			this.setValue(current_rule.set.growth.boobs);
+			this.onchange = (value) => current_rule.set.growth.boobs = value;
+		}
+	}
+
+	class ExprButtGrowthList extends ExpressiveNumericTargetEditor {
+		constructor() {
+			const pairs = [
+				["cute", 2],
+				["big", 4],
+				["huge", 6],
+				["unlimited", 20],
+				["none", 0]
+			];
+			super("Butts", pairs, true, 0, 20, true);
+			this.setValue(current_rule.set.growth.butt);
+			this.onchange = (value) => current_rule.set.growth.butt = value;
+		}
+	}
+
+	class ExprLipGrowthList extends ExpressiveNumericTargetEditor {
+		constructor() {
+			const pairs = [
+				["plump", 25],
+				["beestung", 45],
+				["facepussy", 100],
+				["none", 0]
+			];
+			super("Lips", pairs, true, 0, 100, true);
+			this.setValue(current_rule.set.growth.lips);
+			this.onchange = (value) => current_rule.set.growth.lips = value;
+		}
+	}
+
+	class ExprDickGrowthList extends ExpressiveNumericTargetEditor {
+		constructor() {
+			const pairs = [
+				["above average", 4],
+				["pornstar", 6],
+				["unlimited", 30],
+				["none", 0]
+			];
+			super("Dicks, if present", pairs, true, 0, 30, true);
+			this.setValue(current_rule.set.growth.dick);
+			this.onchange = (value) => current_rule.set.growth.dick = value;
+		}
+	}
+
+	class ExprBallGrowthList extends ExpressiveNumericTargetEditor {
+		constructor() {
+			const pairs = [
+				["sizable", 4],
+				["cumslave", 6],
+				["unlimited", 125],
+				["none", 0]
+			];
+			super("Balls, if present", pairs, true, 0, 125, true);
+			this.setValue(current_rule.set.growth.balls);
+			this.onchange = (value) => current_rule.set.growth.balls = value;
+		}
+	}
+
 	class CurativesList extends ListSelector {
 		constructor() {
 			const pairs = [