Track Who Viewed Each Ticket in GLPI with Ticket Tracker

Stock GLPI keeps no read record. The free Ticket Tracker module logs every ticket view - count, first and last read, and a per-user access log - and even yields the SQL queries to find tickets the owner never opened.

Stock GLPI has no read record: the ticket history keeps what changed, never who simply opened and read it. In our work maintaining client GLPI estates, this becomes the classic standoff of "the technician never even looked at the ticket" versus "yes, I read it" - and with no objective data it is one person's word against the other's. The free Ticket Tracker module closes that gap by logging every ticket view. This guide shows how it stores the data, how to configure it without inflating counters, and three SQL queries that answer "who read what" on the spot.

Why GLPI does not know who read a ticket

The native history (glpi_logs) only records changes: creation, status change, new followup, technician reassignment. Opening the form and reading it changes no field, so it produces no row at all. There is no "view" event in the GLPI core. Ticket Tracker solves this by hooking into the moment the ticket form is rendered (the Ticket postShowItem hook): every time an authenticated session displays a ticket, the module logs the view. No manual action, no new field on the form.

The two storage layers: summary and log

The module writes to two tables with distinct purposes. The summary answers "how many times and when"; the log answers "each access, one by one":

-- Summary per (ticket, user): one row, updated on each access.
CREATE TABLE `glpi_plugin_nextool_tickettracker_views` (
  `id`         int unsigned NOT NULL AUTO_INCREMENT,
  `tickets_id` int unsigned NOT NULL,
  `users_id`   int unsigned NOT NULL,
  `view_count` int unsigned NOT NULL DEFAULT 1,
  `first_view` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `last_view`  timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_ticket_user` (`tickets_id`, `users_id`)
);

-- Detailed log: one row per individual access.
CREATE TABLE `glpi_plugin_nextool_tickettracker_viewlog` (
  `id`         int unsigned NOT NULL AUTO_INCREMENT,
  `tickets_id` int unsigned NOT NULL,
  `users_id`   int unsigned NOT NULL,
  `view_date`  timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
);

The UNIQUE key (tickets_id, users_id) is the heart of the summary table: each ticket/user pair has exactly one row. Writes use INSERT ... ON DUPLICATE KEY UPDATE view_count = view_count + 1, an atomic upsert - two simultaneous accesses by the same user neither create a duplicate row nor lose a count. The "Views" tab on the ticket reads the summary (a table with all viewers, count, first and last read) and offers an expandable cascade that pulls each individual access from the log, with date and time.

Activation and configuration checklist

The module is free and turns on from the NexTool catalog. Once enabled, the settings live under Modules > Ticket Tracker, Features tab:

  1. Module active - the same toggle as on the Modules page. Off, nothing is tracked.
  2. Tracking active (tracking_enabled) - turns on view logging. It can stay on with the tab hidden if you want to collect without exposing.
  3. Show tab on ticket (show_tab_on_ticket) - shows or hides the "Views" tab on the ticket form.
  4. Minimum interval (s) (min_interval_seconds, default 60) - a debounce window: accesses by the same user to the same ticket within that interval count as one.
  5. Ignored (Additional permissions tab) - users, profiles or groups that view without being tracked. All blank = every access counts.

The field mistake that inflates (or zeroes) the counters

The common mistake is zeroing the minimum interval, thinking "the more granular, the better". The hook fires on every render of the form - including reloads, back navigation and reopening the tab. With min_interval_seconds = 0, a technician who leaves the ticket open and hits refresh three times becomes "three reads", and view_count loses its meaning of "how many times they actually came back to the matter". The 60-second default exists precisely to collapse the burst of reloads into a single read. Use 0 only for a one-off diagnostic and return to the default afterwards.

The second mistake is subtler and we hit it at a client: up to module version 1.2.0, logging depended on permission to see the Views tab - meaning only administrative profiles were counted. The requester and the observers opened the ticket and nothing was logged, so the data under-reported exactly the people who matter most in an SLA dispute. We decoupled logging from the tab-view permission: today any authenticated session that renders the ticket is counted - requester, observer or technician - regardless of profile. If you run an older version, update before trusting the numbers.

Decision matrix: interval and ignored list by scenario

The right setting depends on what you want to prove. The matrix we use during design:

Operating scenarioTrackingMinimum interval (s)Who to put in "ignored"
Read audit / SLA (default)On60Nobody
Estate with auto-refresh or wall dashboardOn180 to 300Integration accounts / bots
Supervision that must not inflate the counterOn60Manager / auditor profile
One-off diagnostic (count every access)On0 (temporary)Nobody

Three SQL queries that answer on the spot

The tab handles the case by case; for an estate-wide view, go straight to the database. Open assigned tickets the responsible technician has not opened yet (type = 2 in glpi_tickets_users is the assigned technician):

-- Open assigned tickets the responsible technician has NEVER viewed.
SELECT t.id AS ticket, t.name AS title, u.name AS technician, t.date AS opened_at
FROM glpi_tickets t
JOIN glpi_tickets_users tu ON tu.tickets_id = t.id AND tu.type = 2
JOIN glpi_users u ON u.id = tu.users_id
LEFT JOIN glpi_plugin_nextool_tickettracker_views v
       ON v.tickets_id = t.id AND v.users_id = tu.users_id
WHERE t.is_deleted = 0
  AND t.status IN (1, 2, 3, 4)   -- new, assigned, planned, pending
  AND v.id IS NULL               -- no read logged by the technician
ORDER BY t.date ASC;

Read latency - how many minutes the ticket stayed open until the assigned technician's first read. It is the metric that precedes the first reply and often explains a breached SLA:

-- Minutes between opening and the 1st read by the assigned technician.
SELECT t.id AS ticket,
       TIMESTAMPDIFF(MINUTE, t.date, v.first_view) AS minutes_to_first_read
FROM glpi_tickets t
JOIN glpi_tickets_users tu ON tu.tickets_id = t.id AND tu.type = 2
JOIN glpi_plugin_nextool_tickettracker_views v
       ON v.tickets_id = t.id AND v.users_id = tu.users_id
WHERE t.is_deleted = 0
ORDER BY minutes_to_first_read DESC;

The most active viewers of a specific ticket, from the summary, to see who actually follows it (replace 12345 with the ticket ID):

-- Who opened ticket 12345 the most, from most recurrent to least.
SELECT u.name AS username, v.view_count, v.first_view, v.last_view
FROM glpi_plugin_nextool_tickettracker_views v
JOIN glpi_users u ON u.id = v.users_id
WHERE v.tickets_id = 12345
ORDER BY v.view_count DESC;

A read is not a reply: what the counter proves

The data is powerful, but it has an honest limit: it proves the form was rendered for that user, not that the person read it carefully or acted on it. A high view_count with no followup is a yellow flag - someone is opening and closing without resolving. A ticket with zero views from the owner while the SLA runs is a red flag - nobody has even looked. In sustainment, the real value is not policing technicians, it is closing the evidentiary gap: when the client asks "why did this ticket sit for three days?", the answer stops being an opinion and gains a date-and-time stamp.

Need real visibility over service delivery in your GLPI, with the right modules enabled and tuned to your workflow? Discover NexTool's GLPI sustainment service.


This content was produced with the assistance of artificial intelligence and reviewed by the Nextool Solutions team.

Frequently Asked Questions

No. The history (glpi_logs) only records field changes - status, followups, technician reassignment. Opening and reading changes nothing, so there is no native view record. Ticket Tracker fills the gap by logging every render of the ticket form.

Not in the current version. The detailed log stores ticket, user and timestamp of each access; identification is by the authenticated GLPI user, not by IP. For per-person auditing, users_id is more reliable than IP, which changes with VPN, NAT and proxy.

No, if the minimum interval is set. The 60-second default makes accesses by the same user to the same ticket within the window count as one. Only with interval 0 does each refresh become a read - use 0 for diagnostics only and return to the default.

Add the user, profile or group to the "ignored" list (Additional permissions tab). Anyone matching it views normally, but the access is not logged - handy for supervision and for integration accounts.

Join glpi_tickets_users (type = 2, assigned technician) with the module's summary table via LEFT JOIN and filter v.id IS NULL. The query in the article lists exactly the open, assigned tickets never read by the owner.

Yes. It is a free NexTool module, available for GLPI 10 and 11, and it turns on straight from the catalog, with no mandatory extra configuration.

Need help?