diff --git a/package.json b/package.json
index c852be0..cbbc486 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@ztimson/utils",
-	"version": "0.23.17",
+	"version": "0.23.18",
 	"description": "Utility library",
 	"author": "Zak Timson",
 	"license": "MIT",
diff --git a/src/csv.ts b/src/csv.ts
index 997f31a..ae218d3 100644
--- a/src/csv.ts
+++ b/src/csv.ts
@@ -23,29 +23,29 @@ export function fromCsv<T = any>(csv: string, hasHeaders = true): T[] {
 					i++;
 				} else inQuotes = !inQuotes;
 			} else if (char === ',' && !inQuotes) {
-				columns.push(current);
+				columns.push(current.trim()); // Trim column values
 				current = '';
 			} else current += char;
 		}
-		columns.push(current);
+		columns.push(current.trim()); // Trim last column value
 		return columns.map(col => col.replace(/^"|"$/g, '').replace(/""/g, '"'));
 	}
 
-	// Split rows
+	// Normalize line endings and split rows
 	const rows = [];
 	let currentRow = '', inQuotes = false;
-	for (const char of csv) {
+	for (const char of csv.replace(/\r\n/g, '\n')) { // Normalize \r\n to \n
 		if (char === '"') inQuotes = !inQuotes;
 		if (char === '\n' && !inQuotes) {
-			rows.push(currentRow);
+			rows.push(currentRow.trim()); // Trim row
 			currentRow = '';
 		} else currentRow += char;
 	}
-	if(currentRow) rows.push(currentRow);
+	if (currentRow) rows.push(currentRow.trim()); // Trim last row
 
-	// Figure out headers
+	// Extract headers
 	let headers: any = hasHeaders ? rows.splice(0, 1)[0] : null;
-	if (headers) headers = headers.match(/(?:[^,"']+|"(?:[^"]|"")*"|'(?:[^']|'')*')+/g);
+	if (headers) headers = headers.match(/(?:[^,"']+|"(?:[^"]|"")*"|'(?:[^']|'')*')+/g)?.map((h: any) => h.trim());
 
 	// Parse rows
 	return <T[]>rows.map(r => {
@@ -64,6 +64,7 @@ export function fromCsv<T = any>(csv: string, hasHeaders = true): T[] {
 	});
 }
 
+
 /**
  * Convert an array of objects to a CSV string
  *