diff --git a/frontend/src/__tests__/utils/queueEta.test.ts b/frontend/src/__tests__/utils/queueEta.test.ts
new file mode 100644
index 000000000..807ac5950
--- /dev/null
+++ b/frontend/src/__tests__/utils/queueEta.test.ts
@@ -0,0 +1,27 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import { formatETA } from '../../utils/date';
+import { formatQueueItemETA } from '../../utils/queueEta';
+
+describe('formatQueueItemETA', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date('2026-07-31T18:00:00Z'));
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('calculates the ETA from the current time and job duration', () => {
+ expect(formatQueueItemETA(90 * 60, '24h')).toBe(
+ formatETA(90, '24h'),
+ );
+ });
+
+ it('returns null without a usable print duration', () => {
+ expect(formatQueueItemETA(null)).toBeNull();
+ expect(formatQueueItemETA(undefined)).toBeNull();
+ expect(formatQueueItemETA(0)).toBeNull();
+ expect(formatQueueItemETA(-60)).toBeNull();
+ });
+});
diff --git a/frontend/src/pages/QueuePage.tsx b/frontend/src/pages/QueuePage.tsx
index 212dca0bb..02c984060 100644
--- a/frontend/src/pages/QueuePage.tsx
+++ b/frontend/src/pages/QueuePage.tsx
@@ -66,6 +66,7 @@ import { api, ApiError } from '../api/client';
import { PipelineRunsView } from './PipelineRunsPage';
import { type TimeFormat, formatETA, formatDuration, formatRelativeTime, parseUTCDate } from '../utils/date';
import { getBedTypeInfo } from '../utils/bedType';
+import { formatQueueItemETA } from '../utils/queueEta';
import type { PrintQueueItem, PrintQueueBulkUpdate, Permission, CalibrationMode } from '../api/client';
import { Card } from '../components/Card';
import { Button } from '../components/Button';
@@ -598,10 +599,20 @@ function SortableQueueItem({
{item.print_time_seconds && (
-
-
- {formatDuration(item.print_time_seconds)}
-
+ <>
+
+
+ {formatDuration(item.print_time_seconds)}
+
+ {!item.waiting_reason && (
+
+ ETA {formatQueueItemETA(item.print_time_seconds, timeFormat, t)}
+
+ )}
+ >
)}
{item.filament_used_grams && (
diff --git a/frontend/src/utils/queueEta.ts b/frontend/src/utils/queueEta.ts
new file mode 100644
index 000000000..5282af121
--- /dev/null
+++ b/frontend/src/utils/queueEta.ts
@@ -0,0 +1,18 @@
+import { formatETA, type TimeFormat } from './date';
+
+/**
+ * Formats the estimated completion time for a queue item if it were
+ * started at the current time.
+ *
+ * This is deliberately a per-job estimate rather than a cumulative
+ * queue forecast.
+ */
+export function formatQueueItemETA(
+ printTimeSeconds: number | null | undefined,
+ timeFormat: TimeFormat = 'system',
+ t?: Parameters[2],
+): string | null {
+ if (printTimeSeconds == null || printTimeSeconds <= 0) return null;
+
+ return formatETA(printTimeSeconds / 60, timeFormat, t);
+}