Skip to main content

Database Schema & Patterns

The ticket system uses MySQL with the ost_ table prefix. It combines osTicket's core tables with ~20 custom tables added via migrations in the deploy/ directory.


Database Access Patterns

osTicket Standard — Simple Queries

$result = db_query(
'SELECT * FROM ' . TICKET_TABLE . ' WHERE ticket_id = %d',
db_input($ticketId)
);
while ($row = db_fetch_array($result)) {
// Process row
}

Suma\Ticket — Medoo PDO Wrapper

The Suma\Ticket class uses Medoo (a PDO wrapper) via getDB(). Always use safeQuery() instead of chaining ->query()->fetchAll() directly — Medoo's query() returns null on failure, causing fatal errors.

// ✅ Correct — handles null gracefully
return $this->safeQuery("SELECT col FROM table WHERE id = ?", [$id]);

// ❌ Fatal error if query fails (null->fetchAll())
$this->getDB()->query("SELECT col FROM table WHERE id = ?", [$id])->fetchAll();

Core osTicket Tables

These tables are defined in bootstrap.php and form the base of the system:

TablePurpose
ost_ticketMain ticket records (number, status, dept, staff, created, updated)
ost_ticket__cdataTicket custom data (linked form field values)
ost_threadThread containers (one per ticket)
ost_thread_entryIndividual messages/responses/notes within threads
ost_thread_eventAudit trail events (assigned, transferred, status changes)
ost_staffStaff/agent accounts
ost_departmentDepartment definitions
ost_teamTeam definitions
ost_userEnd users (clients)
ost_user_accountUser authentication accounts
ost_organizationClient organizations
ost_form_entryLinks custom forms to objects (object_type='T' for tickets)
ost_form_entry_valuesCustom field values (AI fields stored here)
ost_emailEmail account configurations
ost_slaSLA plan definitions
ost_queueCustom queue definitions
ost_sessionSession management
ost_configSystem configuration key-value store
ost_syslogSystem log entries
ost_fileFile attachment metadata
ost_attachmentAttachment-to-object linkage
ost_faqKnowledge base FAQ entries
ost_lockRecord locking
ost_filterTicket filter rules
ost_pluginInstalled plugins
ost_scheduleBusiness hour schedules

Custom Tables

All custom tables are created via SQL migrations in the deploy/ directory:

AI & Automation

TableMigration FilePurpose
ost_gemini_queuedeploy-production-migration.sqlTickets queued for Gemini AI processing (ticket_id, processed)
ost_ticket_ai_historydeploy-ai-history-table.sqlAI field change log (ticket_id, field_name, old_value, new_value, changed_at)
ost_ticket_embeddingsdeploy-ai-assistant.sqlAI embedding text cache for vector search
ost_ai_conversationsdeploy-ai-assistant.sqlAI Assistant chat sessions
ost_ai_conversation_messagesdeploy-ai-assistant.sqlIndividual chat messages within conversations

Budget & Time Tracking

TableMigration FilePurpose
ost_ticket_over_budget_lockdeploy-over-budget-lock.sqlBudget lock audit trail (lock/unlock events)
ost_ticket_harvest_entriesdeploy-harvest-time-entries.sqlHarvest time entry sync tracking
ost_manual_tickets(pre-existing)Manual hour tracking (assignee_id, estimated)
ost_suma_ticket_opened_first_time(pre-existing)Tracks first staff acceptance per ticket

Escalation & Alerts

TableMigration FilePurpose
ost_ticket_escalationsdeploy-escalation-tracking.sqlEscalation tracking + daily digest queue
ost_ticket_sms_alertdeploy-sms-tracking.sqlTwilio SMS dedup (UNIQUE on ticket_id)
ost_twilio_sms_optoutdeploy-sms-tracking.sqlSMS opt-out phone numbers
ost_twilio_delivery_statusdeploy-sms-tracking.sqlTwilio webhook delivery events
ost_twilio_daily_counterdeploy-sms-tracking.sqlDaily SMS rate limiting counter

Documentation & Notifications

TableMigration FilePurpose
ost_ticket_documentation_ackdeploy-documentation-required.sqlDocumentation acknowledgments by PMs
ost_ticket_docs_autoclose_skipdeploy-documentation-required.sqlAuto-close skip tracking for doc-required tickets
ost_ticket_client_closed_docs_pendingdeploy-client-closed-docs-tracking.sqlClient-closed tickets pending PM doc review
ost_staff_notificationsdeploy-browser-notifications.sqlNotification rows for staff
ost_staff_notification_prefsdeploy-browser-notifications.sqlBrowser push permission state
ost_staff_notification_subscriptionsdeploy-notification-subscriptions.sqlPer-event notification subscriptions

Custom Form Fields (AI Fields)

These custom form fields are stored in ost_form_entry_values and updated by the Gemini AI processing system:

Field NameData TypePurpose
ai-urgencyVARCHARUrgency level: LOW, MEDIUM, HIGH, CRITICAL
ai-urgency-percentageINTNumeric urgency score (0–100)
ai-estimated-hoursDECIMALAI-estimated effort in hours
ticket-summaryTEXT (HTML)AI-generated summary of the ticket
ai-timelineTEXT (HTML)AI-generated timeline of ticket events
ai-suggested-titleVARCHARAI-suggested title for the ticket

How Form Fields Are Stored

ost_form_entry (links form to ticket)
├── object_type = 'T' (ticket)
├── object_id = ticket_id
└── form_id → references the custom form definition

ost_form_entry_values (actual values)
├── entry_id → references ost_form_entry.id
├── field_id → references the field definition
└── value = the stored value

AI fields are updated directly via:

INSERT INTO ost_form_entry_values (entry_id, field_id, value)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE value = VALUES(value)

Thread Entry Types

Thread entries are the core communication records. Each ticket has a thread containing entries:

Type CodeClassDescription
MMessageThreadEntryOriginal client message
RResponseThreadEntryPublic staff reply (visible to client)
NNoteThreadEntryInternal staff note (hidden from client)

Fetching Thread Entries

// ✅ Correct — always specify types explicitly
$types = ['M', 'R', 'N'];
$entries = $ticket->getThreadEntries($types);

// ❌ Never use $thread->getEntries() directly
// It is filtered by context and only returns type M in CLI/cron

Key Relationships

ost_ticket (1) ──── (1) ost_thread

├── (N) ost_thread_entry (messages/responses/notes)
└── (N) ost_thread_event (audit trail)

ost_ticket (1) ──── (N) ost_form_entry ──── (N) ost_form_entry_values

ost_ticket (1) ──── (1) ost_gemini_queue (pending AI processing)
ost_ticket (1) ──── (N) ost_ticket_ai_history (AI change log)
ost_ticket (1) ──── (N) ost_ticket_escalations
ost_ticket (1) ──── (1) ost_ticket_sms_alert (dedup)
ost_ticket (1) ──── (N) ost_ticket_over_budget_lock

ost_staff (1) ──── (N) ost_staff_notifications
ost_staff (1) ──── (N) ost_staff_notification_subscriptions

ost_user (N) ──── (1) ost_organization

Running Migrations

Migrations are applied directly via MySQL. Each file in deploy/ is a standalone SQL script:

# Apply a specific migration
mysql -u root -p managerhinogroup < deploy/deploy-over-budget-lock.sql

# Or via a MySQL client
SOURCE deploy/deploy-browser-notifications.sql;
caution

Always review migration files before running. Some contain DROP TABLE IF EXISTS statements that will destroy existing data. Back up the database before applying migrations in production.


Table Prefix Convention

All tables use the ost_ prefix, defined as TABLE_PREFIX in bootstrap.php. Custom code should always reference the constant:

$table = TABLE_PREFIX . 'gemini_queue';  // 'ost_gemini_queue'

The GEMINI_QUEUE_TABLE constant is also pre-defined for convenience.