-
-
Notifications
You must be signed in to change notification settings - Fork 27
Checklists and SOPs Developer Guide
How FreeITSM stores reusable Standard Operating Procedures and attaches them to tickets. Shipped in 2.0.0, proposed in #138 and contributed as PR #141 by Santhosh Srinivasan (Sandy) β FreeITSM's first community-contributed module.
For the changes made during integration, and why, see Checklists module β house style. The administrator-facing page is not written yet.
A knowledge article is something you read. A task is something scheduled and delegated. An SOP is neither: it is a list of things that must be ticked off on this ticket, by whoever is holding it, before it can finish.
That gap is the whole justification for the module, and it was argued out in the proposal before a line was written. Worth reading #138 for it:
- Knowledge is passive. You cannot tick a paragraph, and nothing records who read it.
- Tasks are for distinct, scheduled, delegated work. Ten sub-tasks for a routine ten-step onboarding floods the global Tasks board, generates its own calendar entries and notifications, and makes the analyst context-switch out of the ticket to tick a box.
So: a lightweight checklist that lives inside the ticket, with per-step attribution and a completion gate.
Six tables. Two are lookup lists, two are the reusable template, two are the copy attached to a ticket.
checklist_categories id, name β grouping for templates
checklist_roles id, name β "who usually does this"
checklist_templates id, title, category, scope, keywords, description, β¦
checklist_template_items id, template_id, title, suggested_role,
is_mandatory, requires_input, input_placeholder, sort_order
ticket_checklists id, ticket_id, template_id, title
ticket_checklist_items id, ticket_checklist_id, title, suggested_role,
is_mandatory, is_completed, completed_by_id,
completed_by_name, completed_datetime,
requires_input, input_placeholder, response_value, sort_order
ticket_checklist_items copies the step's text. It does not point at checklist_template_items.
That looks like duplication and it is exactly right. A ticket's checklist is a record of what somebody was asked to do at the time. If the rows pointed at the template, then editing the template next year would silently rewrite the history of every ticket that ever used it β and an SOP checklist exists precisely so that history can be trusted.
The same reasoning is why completed_by_name is stored alongside completed_by_id: the analyst's account may be renamed or removed, and the record should still say who ticked the box.
β οΈ Do not "normalise" this later. It will look like a redundancy to somebody eventually. It is a snapshot, and snapshots are supposed to diverge from their source.
ticket | task | both. Only the ticket half is implemented; the task half is declared and unused, which is honest β see What is not built yet.
database/freeitsm.sql declares three foreign keys with ON DELETE CASCADE. Database Verification does not create foreign keys at all, so an installation that grew through upgrades has none of them.
Both are supported configurations, so nothing may rely on a cascade. Every delete path removes its children explicitly, inside a transaction:
$conn->beginTransaction();
$conn->prepare("DELETE FROM ticket_checklist_items WHERE ticket_checklist_id = ?")->execute([$chkId]);
$conn->prepare("DELETE FROM ticket_checklists WHERE id = ?")->execute([$chkId]);
$conn->commit();This is a general rule in FreeITSM, not a checklists one: Database Integrity.
Three routes, all landing in the same place:
| Route | Where |
|---|---|
| The analyst picks one | the SOP panel in the ticket reading pane |
| Suggested by relevance |
suggest_template scores templates against the ticket's subject and first five notes |
| A workflow action |
attach_checklist, dispatched by the workflow engine |
api/tickets/ticket_checklists.php, case "suggest_template". Deliberately simple and readable rather than clever:
- template title appearing in the ticket text: +5
- each matching keyword: +2
- anything scoring below 2 is not offered at all
The score is then turned into a confidence percentage for display. There is no index and no full-text search here β the template library is tens of rows, not thousands, and a LIKE sweep over it costs nothing. If a site ever has thousands of SOPs this is the first thing to revisit.
Ticking a step (toggle_item) writes four things: the flag, the analyst id, the analyst's name as it is now, and the timestamp.
UPDATE ticket_checklist_items
SET is_completed = 1, response_value = ?, completed_by_id = ?,
completed_by_name = ?, completed_datetime = UTC_TIMESTAMP()
WHERE id = ?π΄
UTC_TIMESTAMP(), neverNOW()and never PHP'sdate(). FreeITSM stores UTC at rest (#126) and renders in the viewer's zone. PHP and MySQL do not necessarily agree on the local zone β on the reference dev box PHP runsEurope/Londonwhile MySQL runs UTC, sodate('Y-m-d H:i:s')stored timestamps an hour in the future during BST. See Date and time formats.
Steps that ask for a value. requires_input makes the tick prompt for something β an asset tag, a serial number, a note β stored in response_value. This is the feature that turns a checklist into a record: "laptop issued" is an assertion, "laptop issued, asset tag A-4471" is evidence.
A ticket can be closed with mandatory steps outstanding. The override is recorded.
Where this lives matters more than what it does:
// includes/services/checklists.php
ChecklistsService::assertClosureAllowed($conn, $ticketId, $tenantId); // may throw
ChecklistsService::recordClosureOverride($conn, $ctx, $ticketId); // writes the noteBoth are called from TicketsService::updateTicket(), which is the choke point for the inbox, bulk actions, assign/schedule, and the v1 REST API. Putting the rule in the browser would mean the API ignored it, and a compliance control the API ignores is not a control.
π΄ And from
WorkflowEngine::action_set_ticket_status(), which writes the status itself and does not go throughupdateTicket(). Until the mandatory fields work, this gate did not apply to workflows at all, although the help card said it did. A workflow acts asActorContext(0, null, 'workflow').ticket_notes.analyst_idcannot hold 0 (it has a foreign key), so a workflow close writes its override as aWorkflow Noteinticket_audit, the same place the engine's own notes go.
Tickets β Settings β Checklists chooses the behaviour:
| Setting | Behaviour |
|---|---|
| Warn, and record it (default) | the close proceeds; an internal note names the skipped steps, who closed it and through which interface |
| Refuse the close |
ServiceError('validation', 'mandatory_steps_outstanding', β¦) and the ticket does not move |
π΄ The refusal is checked BEFORE the status is written. Checked afterwards, a
blockinstall would throw having already closed the ticket: the caller sees an error while the database disagrees with it. The check sits in the status-resolution branch ofupdateTicket, ahead of theUPDATE.
Why warn is the default: assets/js/inbox.js already carried the house position, written for #83 about unfinished tasks β
"closing a ticket that still has unfinished tasks WARNS, it never blocks β¦ a warning that cannot be shown must not become a block that cannot be cleared."
A hard block traps any ticket whose remaining step has become impossible: the person who owned it has left, the step is obsolete, and there is no way out but SQL. Recording the exception is also the better compliance answer β an auditor does not want a system where the bad outcome was impossible, they want one where it is attributable.
Either way the note is written. The setting decides whether it is allowed, never whether it is logged.
checklists/index.php the template library (cards, filters, search)
checklists/edit/index.php the template editor β a SCREEN, not a modal
checklists/settings/index.php categories, suggested roles, left-panel preference
checklists/api.php template get / save / delete
checklists/ticket_view.js the SOP panel inside a ticket
api/checklists/*.php category + role CRUD, over the service
api/tickets/ticket_checklists.php attach, suggest, toggle, remove
includes/services/checklists.php the closure rule and the lookup rules
database/demo-data/checklists.json five worked SOPs for evaluation
forms/edit/ is a full screen for the same shape of problem: a parent with an unbounded list of children, each child having attributes of its own. A 680px modal at 88vh is a poor place to drag a fifteen-step procedure into order β the drop target is regularly off-screen, and a scrolling modal body will not auto-scroll during a drag without a lot of bespoke work. A screen also gets a URL, so ?id=N is linkable and the browser's back button behaves.
The drag-and-drop is forms' implementation, copied deliberately β same handle-gated dragstart, same midpoint drop calculation, same .dragging / .drag-over-top / .drag-over-bottom class names.
β οΈ The handle gate is the part that is easy to leave out. Without it the whole row isdraggable, and click-dragging to select text inside a step's title starts a reorder instead:let dragAllowed = false; document.addEventListener('mousedown', e => { dragAllowed = !!e.target.closest('.step-drag'); }); function onStepDragStart(e, i) { if (!dragAllowed) { e.preventDefault(); return; } β¦ }
Declared honestly in the proposal and still true:
-
the Tasks half of
scopeβ the column acceptstaskandboth; nothing consumes it - per-step approval by a watcher or manager
- enforced sequential dependencies (the panel shows the next step but does not prevent ticking a later one)
- per-step routing, sub-SLAs, branching checklists, CMDB links at step level
- version history for templates
- Checklists module β house style β what changed during integration and why
- β¬ An administrator-facing page for this module is still to be written
- Service layer Β· Database Integrity Β· Database Verification
-
Workflows β the
attach_checklistaction
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π Mobile: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π REST API: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π·οΈ QR asset labels
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Domains
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ π’ One board across every company
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)
- What this is
-
π Bugs resolved
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96