# Querying Jobs: State, Counts and Results

Read the queue back: fetch a job by id, list by state, count per state and priority, read results and progress, and understand what each state means.

Canonical: https://bunqueue.dev/guide/queue/querying/

---

import { Tabs, TabItem } from '@astrojs/starlight/components';

<div class="bq-wrap bq-hero">
  <span class="bq-eyebrow">guide · queue</span>
  <h1 class="bq-hero-h1 bq-bench-h1">Asking the queue <em>what it holds.</em></h1>
  <p class="bq-hero-sub">Dashboards, health checks and debugging all come down to the same questions: what is in there, what state is it in, and what did that one job actually return.</p>
</div>

## Query jobs

<Tabs syncKey="lang">
<TabItem label="Bun">

```typescript
// One job
const job = await queue.getJob('job-id');
const state = await queue.getJobState('job-id');
// 'waiting' | 'prioritized' | 'delayed' | 'active' | 'completed'
// | 'failed' | 'waiting-children' | 'unknown'

// Counts per state
const counts = await queue.getJobCountsAsync();
// { waiting, prioritized, active, completed, failed, delayed,
//   'waiting-children', paused }

// Lists, filtered by state
const failed = await queue.getJobsAsync({ state: 'failed', start: 0, end: 50 });

// Exhaustive traversal; TCP mode transparently drains consecutive pages
const everyFailedJob = await queue.getFailedAsync(0, -1);
```

</TabItem>
<TabItem label="Node.js / Deno">

```typescript
// One job
const job = await queue.getJob('job-id');
const state = await queue.getJobState('job-id');
// 'waiting' | 'prioritized' | 'delayed' | 'active' | 'completed'
// | 'failed' | 'waiting-children' | 'unknown'

// Counts per state
const counts = await queue.getJobCountsAsync();
// { waiting, prioritized, active, completed, failed, delayed,
//   'waiting-children', paused }

// Lists, filtered by state
const failed = await queue.getJobsAsync({ state: 'failed', start: 0, end: 50 });

// Exhaustive traversal; TCP mode transparently drains consecutive pages
const everyFailedJob = await queue.getFailedAsync(0, -1);
```

</TabItem>
<TabItem label="Python">

```python
# One job
job = queue.get_job("job-id")       # None when missing
state = queue.get_state("job-id")
# "waiting" | "prioritized" | "delayed" | "active" | "completed"
# | "failed" | "waiting-children"

# Counts per state
counts = queue.get_job_counts()

# Lists, filtered by state
failed = queue.get_jobs("failed", 0, 50)
```

</TabItem>
<TabItem label="PHP">

```php
// One job
$job = $queue->getJob('job-id');       // null when missing
$state = $queue->getState('job-id');
// 'waiting' | 'prioritized' | 'delayed' | 'active' | 'completed'
// | 'failed' | 'waiting-children'

// Counts per state
$counts = $queue->getJobCounts();

// Lists, filtered by state
$failed = $queue->getJobs('failed', 0, 50);
```

</TabItem>
<TabItem label="Go">

```go
// One job
job, _ := queue.GetJob("job-id")       // nil when missing
state, _ := queue.GetState("job-id")
// "waiting" | "prioritized" | "delayed" | "active" | "completed"
// | "failed" | "waiting-children"

// Counts per state
counts, _ := queue.GetJobCounts()

// Lists, filtered by state
failed, _ := queue.GetJobs("failed", 0, 50)
```

</TabItem>
<TabItem label="Rust">

```rust
// One job
let job = queue.get_job("job-id")?;      // None when missing
let state = queue.get_state("job-id")?;

// Counts per state
let counts = queue.get_job_counts()?;

// Lists, filtered by state (offset, limit)
let failed = queue.get_jobs(Value::from("failed"), 0, 50)?;
```

</TabItem>
<TabItem label="Elixir">

```elixir
# One job
{:ok, job} = Bunqueue.Queue.get_job(queue, "job-id")     # nil when missing
{:ok, state} = Bunqueue.Queue.get_state(queue, "job-id")

# Counts per state
{:ok, counts} = Bunqueue.Queue.get_job_counts(queue)

# Lists, filtered by state (offset, limit)
{:ok, failed} = Bunqueue.Queue.get_jobs(queue, "failed", 0, 50)
```

</TabItem>
</Tabs>

Most read methods come in two flavors: a sync version that only works in embedded mode (`getJobs()`, `getCountsPerPriority()`, `count()`, `isPaused()`; `getJobCounts()` instead delegates to the async path in TCP mode, returning a Promise of the real server-side counts — await it there) and an async version that works in both modes (`getJobCountsAsync()`, `getJobsAsync()`, `getCountsPerPriorityAsync()`, `countAsync()`, `isPausedAsync()`). Prefer the async ones unless you know you're embedded.

Jobs returned by `getJob()` and `getJobsAsync()` reflect the authoritative
broker generation in both modes. In particular, `attemptsMade`,
`attemptsStarted`, `stalledCounter`, progress, priority, processing/completion
timestamps, and their `toJSON()` / `asJSON()` representations are not reset by
the TCP query proxy.

Per-state shortcuts:

<Tabs syncKey="lang">
<TabItem label="Bun">

```typescript
// Sync (embedded only): getWaiting, getActive, getCompleted, getFailed, getDelayed
const waiting = queue.getWaiting(0, 10);

// Async (both modes): same names + Async
const failed = await queue.getFailedAsync(0, 10);

// Counts (async, both modes)
const failedCount = await queue.getFailedCount();
// also: getWaitingCount, getActiveCount, getCompletedCount, getDelayedCount

// BullMQ-compatible extras
const prioritized = await queue.getPrioritized(0, 10);        // jobs with priority > 0
const waitingChildren = await queue.getJobsAsync({
  state: 'waiting-children', start: 0, end: 10,
});
```

</TabItem>
<TabItem label="Node.js / Deno">

```typescript
// Awaitable state shortcuts work over TCP
const waiting = await queue.getWaitingAsync(0, 10);

// All state lists have an Async variant
const failed = await queue.getFailedAsync(0, 10);

// Counts (async, both modes)
const failedCount = await queue.getFailedCount();
// also: getWaitingCount, getActiveCount, getCompletedCount, getDelayedCount

// BullMQ-compatible extras
const prioritized = await queue.getPrioritized(0, 10);        // jobs with priority > 0
const waitingChildren = await queue.getJobsAsync({
  state: 'waiting-children', start: 0, end: 10,
});
```

</TabItem>
<TabItem label="Python">

```python
# Per-state lists: get_waiting, get_active, get_completed, get_failed, get_delayed
waiting = queue.get_waiting(0, 10)
failed = queue.get_failed(0, 10)

# Counts
failed_count = queue.get_failed_count()
# also: get_waiting_count, get_active_count, get_completed_count, get_delayed_count

# BullMQ-compatible extras
prioritized = queue.get_prioritized(0, 10)          # jobs with priority > 0
waiting_children = queue.get_waiting_children(0, 10)
```

</TabItem>
<TabItem label="PHP">

```php
// These SDKs use the general state-filtered query.
$waiting = $queue->getJobs('waiting', 0, 10);
$failed = $queue->getJobs('failed', 0, 10);

$counts = $queue->getJobCounts();
$failedCount = $counts['failed'] ?? 0;

$prioritized = $queue->getJobs('prioritized', 0, 10);
$waitingChildren = $queue->getJobs('waiting-children', 0, 10);
```

</TabItem>
<TabItem label="Go">

```go
// These SDKs use the general state-filtered query.
waiting, err := queue.GetJobs("waiting", 0, 10)
failed, err := queue.GetJobs("failed", 0, 10)

counts, err := queue.GetJobCounts()
failedCount := counts["failed"]

prioritized, err := queue.GetJobs("prioritized", 0, 10)
waitingChildren, err := queue.GetJobs("waiting-children", 0, 10)
```

</TabItem>
<TabItem label="Rust">

```rust
use bunqueue_client::Value;

// Rust takes an offset and a limit.
let waiting = queue.get_jobs(Value::from("waiting"), 0, 10)?;
let failed = queue.get_jobs(Value::from("failed"), 0, 10)?;

let counts = queue.get_job_counts()?;
let failed_count = counts.get("failed").copied().unwrap_or(0);

let prioritized = queue.get_jobs(Value::from("prioritized"), 0, 10)?;
let waiting_children = queue.get_jobs(Value::from("waiting-children"), 0, 10)?;
```

</TabItem>
<TabItem label="Elixir">

```elixir
# Elixir takes an offset and a limit.
{:ok, waiting} = Bunqueue.Queue.get_jobs(queue, "waiting", 0, 10)
{:ok, failed} = Bunqueue.Queue.get_jobs(queue, "failed", 0, 10)

{:ok, counts} = Bunqueue.Queue.get_job_counts(queue)
failed_count = Map.get(counts, "failed", 0)

{:ok, prioritized} = Bunqueue.Queue.get_jobs(queue, "prioritized", 0, 10)
{:ok, waiting_children} = Bunqueue.Queue.get_jobs(queue, "waiting-children", 0, 10)
```

</TabItem>
</Tabs>

*Per-state shortcuts are available in TypeScript and Python; in PHP, Go, Rust, and Elixir use `getJobs` with a state filter.*

`getPrioritized()` and `getWaitingChildren()` work in embedded and TCP modes.
Both use the asynchronous job-query path; waiting children are selected through
the dedicated state rather than inferred from job data.

In both TypeScript packages, `end: -1` is an explicit exhaustive read for `getJobs`,
`getJobsAsync`, every sync/async state shortcut, `getPrioritized`, and
`getWaitingChildren`. TCP mode requests 1,000-row pages until exhaustion and
removes duplicate IDs defensively; a finite `end` still denotes the exclusive
end of one page — except for `getWaitingChildren`, which treats a finite `end`
as inclusive (`getWaitingChildren(0, 10)` returns up to 11 jobs). `getJobs[Async]({ asc: false })` reverses the stable
createdAt/job-id order before that pagination. Because the broker protocol uses numeric offsets rather than a
snapshot cursor, concurrent inserts/removals can shift later pages. Pause
mutation or reconcile IDs in application code when a stable snapshot is
required.

:::note[Two states worth knowing]
**Prioritized:** jobs with `priority > 0` report the state `'prioritized'`, not `'waiting'` (BullMQ v5 behavior). Both are pullable; prioritized jobs go first.

**Paused:** while a queue is paused, its ready jobs are counted under `paused`, never `waiting` or `prioritized`. On `resume()` each job returns to its logical `waiting` or `prioritized` state. Pause state survives server restarts when persistence is on.
:::

Jobs that exhaust their retries move to the dead letter queue (DLQ, a holding area for permanently failed jobs) but remain visible: they are counted by `failed`, returned by `getJob(id)`, and listed by `getFailed()`.

## Where to go next

| Guide | What it covers |
|---|---|
| [Queue API](/guide/queue/) | Create a queue in embedded or TCP mode |
| [Adding Jobs](/guide/queue/adding-jobs/) | add, addBulk, priorities, delays, durability |
| [Deduplication and Idempotent Job Adds](/guide/queue/deduplication/) | Idempotent adds, dedup keys, custom job ids |
| [Queue Control and Maintenance](/guide/queue/control/) | Pause, drain, obliterate, clean and repair |
| [Progress, Job Logs and Dependencies](/guide/queue/progress/) | Progress, per-job logs and dependencies |
| [Queue Rate Limiting and Global Concurrency](/guide/queue/limits/) | Rate limits and global concurrency caps |
| [Job Schedulers from the Queue](/guide/queue/schedulers/) | Named repeatable schedules from the queue |
| [DLQ Operations from the Queue Object](/guide/queue/dlq/) | Failed-job operations from the Queue object |
| [Workers, Stats and Metrics from the Queue](/guide/queue/metrics/) | Registered workers, stats and metrics windows |
| [Namespaces, Auto-Batching and Store-and-Forward](/guide/queue/advanced/) | Namespaces, auto-batching, store-and-forward |
| [JobOptions Reference](/guide/queue/options/) | Every JobOptions field, with defaults |