
Checkin in Joomla
Every Joomla editor has seen the yellow warning: "This item is currently being edited by..." - on a record that nobody is editing any more. The browser was closed mid-edit, the session expired, or a fatal error stopped the save halfway. The lock stays behind and blocks the next person who tries to open the record.
Joomla solves this with a small built-in component called Check-in (com_checkin). It is one backend screen and one click. Tick the affected tables, press Check-in, and every stale lock is gone.
How Joomla prevents editors from overwriting each other's work, and how to fix it when a lock gets stuck.
This article explains how Joomla's check-out / check-in system works, why it exists, where the locks live in the database, and how to use the Check-in component, the lock icon, the scheduled task plugin, and the developer API. The goal is to make this part of Joomla feel obvious instead of mysterious.
1. The Basics
1.1 What is the Check-in Component?
The Check-in component (com_checkin) is Joomla's built-in administrator tool for releasing edit locks on database rows. It has shipped with Joomla core since version 1.6.
What it does in short:
- It scans every database table whose name starts with your configured prefix.
- It lists the tables that have
checked_outandchecked_out_timecolumns and at least one locked row. - It clears the lock by resetting both columns to their default value or NULL.
Think of it as the force-eject button for stuck records. It is not the way records normally get unlocked - saving a record does that. Check-in only cleans up what saving missed.
1.2 The Library Book Analogy
It helps to think of every editable record as a library book:
- Opening the edit form = borrowing the book. Your name goes on the slip.
- Clicking Save or Cancel = returning the book. The slip is cleared.
- Closing the browser mid-edit = walking out with the book. The slip stays in your name, nobody else can borrow it.
- Global Check-in = the librarian walks the shelves and clears every slip whose borrower has clearly forgotten to come back.
1.3 What is a Check-out, Really?
When you open an article (or category, module, menu item) for editing, Joomla writes a row-level "borrow slip":
UPDATE #__content
SET checked_out = 42, -- your user id
checked_out_time = '2026-05-27 14:23:00' -- now
WHERE id = 1234;
While those columns are filled in, anyone else who opens the row sees a warning and cannot save. When you click Save & Close or Cancel, Joomla performs the matching check-in:
UPDATE #__content
SET checked_out = NULL,
checked_out_time = NULL
WHERE id = 1234;
The lock is gone, the next editor can open the row. This is the happy path. com_checkin only matters when this cycle breaks.
1.4 Where Do I Find It?
In the Joomla 6 backend the path is:
System → Maintenance → Global Check-in
The component lives at administrator/components/com_checkin/. There is no frontend folder - by design. There is no menu item type and no API endpoint either. It is a Super User tool.
2. The Race That Check-in Prevents
Without a check-out system, two editors saving five seconds apart produce a quiet bug called last-write-wins:
14:23:00 Editor A opens article #42, edits the title
14:23:05 Editor B opens article #42, edits the intro paragraph
14:23:40 Editor A clicks Save → title saved, intro reverted to original
14:23:45 Editor B clicks Save → intro saved, title reverted to A's old value
Both editors believe they saved successfully. Both are wrong. The second save silently overwrote half of the first.
Check-out prevents this race from ever starting. Editor B sees a warning before they type a single character:
This article is currently being edited by Editor A since 14:23.
This is pessimistic application-level locking. Joomla warns the second editor up-front, instead of detecting the conflict at write time. The two columns checked_out and checked_out_time are the entire mechanism.
3. When the Lock Gets Stuck
The check-in step is skipped when something interrupts the editor:
- The browser tab is closed without clicking Save or Cancel.
- The user's session expires while the edit form is open.
- The browser crashes, the laptop sleeps, the wifi drops mid-save.
- A PHP fatal error throws before the controller reaches
checkin(). - A second tab opens the same article - the first tab's check-in fires, then closing the second tab leaves the row locked from the second check-out.
The result is the same in every case: a row marked as checked out by a user who is no longer editing it. That is when Global Check-in earns its place in the toolbar.
4. The Global Check-in Screen
4.1 The List
Open System → Maintenance → Global Check-in. The screen shows a list with three columns:
| Column | Meaning |
|---|---|
| Checkbox | Tick the tables you want to bulk check-in. |
| Database Table | Full table name including the prefix, e.g. jos_content. |
| # of Items | How many rows in that table are currently locked. |
Tick the boxes, press the Check-in toolbar button, and Joomla shows the message "N items successfully checked in".
4.2 The Empty State
If no tables have locked rows, you see a friendly "nothing to do" panel - no list, no toolbar action. This is the state you want to see. Visiting this screen and finding it empty is the normal, healthy outcome.
4.3 Blunt or Surgical
Global Check-in is blunt. It releases every locked row in the tables you tick, including rows other editors may still be working on. For one row at a time, use the lock icon in the list view of that component (see section 5).
5. The Editor Experience
5.1 The Lock Icon in List Views
Open Content → Articles. Each row shows a small lock icon next to its title when checked_out is set:
- Hover the icon - tooltip with the locking user's name and the time of check-out.
- Click the icon - if you have the right ACL on the row, it checks the row in immediately.
- Without permission - the click does nothing, the row stays locked.
This per-row check-in is the fast path. Editors rarely need to open Global Check-in. They just click the lock icon on the article list.
5.2 The Lock Warning on the Edit Form
If Editor B opens an article that Editor A is editing, B sees a yellow notice at the top of the form. B can still view the data, but the Save button is disabled. B's options are:
- Close the tab and come back later.
- Ask A to save and close.
- Ask a Super User (or click the lock icon if allowed) to release the lock.
5.3 Per-row vs Global - Pick the Smallest Tool
| Path | Scope | Best for |
|---|---|---|
| Lock icon in list view | One row | Editor unsticking their own forgotten lock. |
Global Check-in (com_checkin) |
All locked rows across all tables | Cleanup after a crash or maintenance window. |
Scheduled task (plg_task_globalcheckin) |
All tables, age-filtered | Routine housekeeping on multi-editor sites. |
6. Which Tables Are Scanned?
6.1 The Convention
A table appears in Global Check-in if and only if it has both of these columns:
checked_out INT (nullable in Joomla 4+, was 0-default in 3.x)
checked_out_time DATETIME (nullable in Joomla 4+)
The component reads dbprefix from configuration, walks every table whose name starts with that prefix, fetches the column list, and silently skips anything that does not have both columns. This is why third-party extensions benefit automatically - if their table follows the convention, it shows up in Global Check-in without any registration, plugin, or XML.
6.2 Typical Core Tables
When something is locked, expect to see tables like:
| Table | What is locked |
|---|---|
#__content |
Articles |
#__categories |
Categories (any extension) |
#__menu |
Menu items |
#__modules |
Modules |
#__users |
User profiles |
#__fields / #__fields_groups |
Custom fields |
#__redirect_links |
Redirects |
#__tags |
Tags |
Anything not in the list either has no checked_out column (for example #__session or #__assets) or simply has no currently-locked rows.
6.3 The Prefix Filter
The component refuses to touch any table whose name does not start with the configured prefix. This has two practical consequences:
- Shared databases work cleanly. If you host two Joomla sites in one database (
abc_contentandxyz_content), checking in on site A only touchesabc_*tables. - Prefixless or wrong-prefix tables are invisible. A custom extension whose table is named
mytable_data(no#__) never appears, even if it haschecked_outcolumns.
Always use #__ in your table definitions. Joomla replaces it at runtime with the configured prefix.
7. The Underlying API
7.1 Why Not Real Database Locks?
Joomla deliberately does not use SELECT ... FOR UPDATE or table locks for editor concurrency. The reasons:
- Edit sessions outlive DB connections. Databases are optimized for short transactions, not long human editing sessions. A real row lock held while an editor stares at a form for 20 minutes would exhaust the connection pool. Joomla therefore stores the lock as metadata in the row itself.
- Cross-engine portability. Joomla targets MySQL, MariaDB, PostgreSQL, and SQL Server. Lock semantics differ across all four. Advisory columns work the same everywhere. This keeps Joomla portable and predictable.
- No deadlocks. Two columns cannot deadlock. Two real DB locks racing for the same row can.
- Cluster-safe. The lock state is the database row, so every web node sees the same answer. File-based or in-memory locks would fail in a load-balanced setup.
- Visible to humans.
SELECT * FROM #__content WHERE checked_out IS NOT NULLis something a developer can actually run. It's human-readable and easy to debug as administrators can inspect locks directly.
7.2 Table::checkOut()
Every Joomla table that supports locking extends Joomla\CMS\Table\Table, which provides:
public function checkOut($userId, $pk = null): bool
The method dispatches a "before" event, updates the row's checked_out and checked_out_time columns, and dispatches an "after" event. The checked_out integer is the user id, not a session id. Joomla can show who holds the lock, by name, in the warning message.
7.3 Table::checkIn()
The mirror method clears the columns:
public function checkIn($pk = null): bool
checkIn() is called by:
AdminModel::checkin()- the backend Save and Cancel buttons.Table::store()- the regular save path. Saving a record always checks it in.Table::delete()- before deletion, to release any lock first.
A successful save is therefore the normal way locks are released. The Check-in component only sweeps up the leftovers.
7.4 Table::isCheckedOut() and Session Metadata
The check that decides whether to show the warning is more subtle than people think. The isCheckedOut() method consults the #__session table when session tracking is on:
if (Factory::getApplication()->get('session_metadata', true)) {
// SELECT COUNT(userid) FROM #__session WHERE userid = $against
// If 0 → the row is treated as not-checked-out,
// even though the columns are still filled in
}
What this means in practice:
- With session_metadata = true (the default): once the locking user's session ends, the row behaves as unlocked. The next editor can open and save it, even though the columns still look "locked".
- With session_metadata = false: the predicate trusts the columns blindly. Stale locks then really do block other editors until Global Check-in clears them.
Common myth: "Joomla never auto-releases stale locks". Reality: with the default settings, the behaviour already adapts when sessions expire. Global Check-in and the scheduled task tidy up the columns afterwards.
The toggle lives at System → Global Configuration → System → Session Settings → Track Session Metadata.
7.5 The Four Table Events
Developers can hook into the lock lifecycle through four per-row events:
| Event | When it fires | Common use |
|---|---|---|
onTableBeforeCheckout |
Just before the lock is set | Audit log: "user X is about to lock row Y". |
onTableAfterCheckout |
Just after the lock is set | Push a notification to other open editors. |
onTableBeforeCheckin |
Just before the lock is cleared | Refuse the check-in if business logic requires. |
onTableAfterCheckin |
Just after the lock is cleared | Invalidate a cached lock-status indicator. |
7.6 The Component-level onAfterCheckin Event
Global Check-in fires its own event after each table is processed. The event class is Joomla\CMS\Event\Checkin\AfterCheckinEvent (Joomla 5.0 and later). A typical subscriber:
public function onAfterCheckin(AfterCheckinEvent $event): void
{
$table = $event->getTableName();
Log::add("Global check-in cleared locks in {$table}", Log::INFO, 'audit');
}
This is useful for compliance trails - you want to know who released locks in bulk and when. Note that the event fires once per table, not once per row.
8. Automated Check-in with the Task Plugin
8.1 What It Is
Since Joomla 5.0, core ships with a Scheduled Tasks plugin called Global Check-in:
plugins/task/globalcheckin/
It does what the Check-in component does, but on a cron-like schedule and without a human ticking checkboxes.
8.2 Enabling It
Two steps:
- Go to
System → Manage → Plugins, search for Global Check-in, and enable it. - Create a task at
System → Manage → Scheduled Tasks → New.
Pick the following values:
| Field | Pick |
|---|---|
| Type | Global Check-in |
| Rule | Cron expression or interval (for example, every hour) |
| Delay (hours) | Minimum age of a lock before it is released (default 1) |
| Status | Enabled |
8.3 The Delay Parameter
The delay protects editors who are actually editing. With delay = 1, only locks older than one hour are released. An editor who is still typing is safe.
Pick the delay slightly longer than your longest expected editing session:
- Typical: 2 hours.
- Aggressive: 1 hour.
- Cautious: 24 hours.
8.4 When to Use Which Tool
Most production sites should enable the scheduled task with a delay of at least 2 hours. The Global Check-in screen then becomes the "break-glass" tool - rarely needed, but ready when something goes wrong.
9. Permissions and ACL
9.1 The Two Actions
Check-in's ACL is configured at System → Global Configuration → Permissions → Check-in:
| Action | What it gates |
|---|---|
core.admin |
Configure the component (mostly just ACL itself - there are no other options). |
core.manage |
Access the Global Check-in screen and run the check-in action. |
By default only Super Users inherit both. You can give a Site Manager group access by allowing Manage on com_checkin.
9.2 The Per-row Permission Is Different
Releasing a single row via the lock icon in a list view is governed by the component that owns the row, not by com_checkin. To click-to-unlock an article, you need the right permission on com_content, not on com_checkin.
A user with com_checkin Manage can do a bulk check-in but might not be allowed to click the lock icon on a single article. The two paths use two different permissions.
9.3 Suggested Role Matrix
| Role | com_checkin Manage | Per-row check-in | Daily use |
|---|---|---|---|
| Super User | Yes | Yes | Only when needed. |
| Site Manager | Yes | Yes | Run Global Check-in after maintenance. |
| Content Editor | No | Yes (own component) | Click lock icon on own articles. |
| Author | No | No | Save and close properly, ask if stuck. |
Global Check-in is a maintenance privilege, while single-row check-in is a content-management privilege.
10. Build It Into Your Own Extension
10.1 The Recipe
To make your component participate in Joomla's check-in system, four steps are enough.
1. Add the two columns to your table definition.
CREATE TABLE `#__myext_items` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
-- ... your fields ...
`checked_out` INT UNSIGNED NULL DEFAULT NULL,
`checked_out_time` DATETIME NULL DEFAULT NULL,
KEY `idx_checked_out` (`checked_out`)
);
2. Extend Joomla\CMS\Table\Table in your Table class. You get checkOut() and checkIn() for free.
3. Extend Joomla\CMS\MVC\Model\AdminModel in your edit-form model. Its save() already calls checkin() automatically. Its getItem() performs the checkOut().
4. (Optional) Add the lock icon to your list view:
echo HTMLHelper::_('jgrid.checkedout',
$i, $item->editor, $item->checked_out_time, 'items.', $canCheckin);
That is all. Your table now appears in Global Check-in when it has locked rows, the scheduled task releases its stale locks automatically, and the lock icon works in your list view.
10.2 Schema Evolution
Joomla 4 changed the checked_out columns from NOT NULL DEFAULT 0 to nullable, where not-locked means NULL. The Check-in component and the task plugin handle both shapes. If your legacy extension still uses the old "default zero" pattern, Global Check-in still works against it. You can migrate at your own pace.
11. Multi-server, API, and Headless
11.1 Load-balanced Clusters
Because the lock state lives entirely in the database, Joomla check-in works correctly in clustered hosting without any extra effort:
- Editor A opens a row on web node A -
checked_outis written to the shared database. - Editor B's request hits web node B - the same row is loaded, and the lock is visible.
- Either node can perform the check-in. Both see the result immediately.
No sticky sessions are required for the lock itself.
11.2 The Web Services API
Joomla 4 and later ship a JSON API at /api/index.php/v1/. The same AdminModel code powers the API endpoints and the backend forms, so:
- A
PATCHon an article performs check-out, save, check-in internally. - An API client that reads an article does not lock it.
- An API client that opens an article for editing should call the explicit check-out endpoint, then save, and expect the auto check-in.
The lock is advisory. A raw PATCH that ignores check-out will succeed and may overwrite an in-progress backend edit. If you build a headless editor on top of the API, you are responsible for calling check-out before showing the form, surfacing the lock state in your UI, and calling check-in when the user navigates away.
11.3 CLI Scripts and Direct SQL
The lock is enforced by Joomla's PHP code, not by the database itself. This means:
- CLI scripts that call
Table::store()directly bypass the lock check unless they explicitly callisCheckedOut()first. - Maintenance scripts that run raw
UPDATEstatements bypass it entirely. - Third-party migrators may not respect locks. Be careful running them while editors are active.
If your CLI script writes to a table that supports check-in, do the courtesy of checking out and checking back in around the write.
12. Troubleshooting and Useful SQL
12.1 Who Has This Article Locked?
SELECT a.id, a.title, u.name AS locked_by, a.checked_out_time
FROM jos_content a
JOIN jos_users u ON u.id = a.checked_out
WHERE a.checked_out IS NOT NULL;
Replace jos_ with your actual database prefix.
12.2 Which Tables Support Locks at All?
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME LIKE 'jos_%'
AND COLUMN_NAME = 'checked_out';
This is exactly what the Check-in component does in PHP. The result is the full list of tables that participate in the lock system on your site.
12.3 Emergency: Clear All Locks in All Core Tables
UPDATE jos_content SET checked_out = NULL, checked_out_time = NULL WHERE checked_out IS NOT NULL;
UPDATE jos_categories SET checked_out = NULL, checked_out_time = NULL WHERE checked_out IS NOT NULL;
UPDATE jos_menu SET checked_out = NULL, checked_out_time = NULL WHERE checked_out IS NOT NULL;
UPDATE jos_modules SET checked_out = NULL, checked_out_time = NULL WHERE checked_out IS NOT NULL;
UPDATE jos_users SET checked_out = NULL, checked_out_time = NULL WHERE checked_out IS NOT NULL;
Prefer Global Check-in or the scheduled task to direct SQL. They fire the onAfterCheckin event, which other extensions may rely on for audit logging.
12.4 Common Problems
"Global Check-in is slow." The component runs SHOW TABLES and then DESCRIBE on every prefix-matching table. On a site with 200 or more tables, this can take a few seconds. Do not load the screen as a "health check" page. Enable the scheduled task so you rarely need to visit it.
"I checked in but the editor still sees the warning." The warning is rendered from the row's checked_out value at page load. The editor must refresh their edit form (or list view) to see the lock released.
"The task ran but locks are still there." Check the task's Delay parameter. With delay = 24, locks newer than 24 hours are deliberately preserved. Run the task once with delay = 0 to force-clear, then restore your normal value.
"A custom extension's locks never appear in Global Check-in." The table's name does not start with your dbprefix, or the table is missing one of the two required columns. Check with:
SHOW COLUMNS FROM <yourtable> LIKE 'checked_out%';
You should see exactly two rows.
13. Best Practices and Cheat Sheet
If you only remember a few things from this article, remember these:
- Check-in is not the primary unlock path - saving a record is. The component exists for when saving never happens.
- Pick the smallest tool that solves the problem: lock icon, then Global Check-in, then the scheduled task.
- Enable
plg_task_globalcheckinon multi-editor production sites with a sensible delay (2 hours is a good start). - The convention is two columns:
checked_outandchecked_out_time. Any table that has them participates automatically. - The lock is advisory. Raw SQL or careless CLI scripts can bypass it.
- With
session_metadata = true(the default), expired sessions already behave as unlocked in the UI - Global Check-in just tidies the columns. - Subscribe to
onAfterCheckinif you need an audit trail.
Cheat Sheet
UNLOCK ONE ROW Click the lock icon in the list view
UNLOCK MANY ROWS System → Maintenance → Global Check-in
AUTO-RELEASE Enable plg_task_globalcheckin, set delay = 2
SUPPORT IT IN MINE Add checked_out + checked_out_time columns
Extend Table and AdminModel
AUDIT WHO RELEASED Subscribe to onAfterCheckin
REFUSE A CHECK-IN Subscribe to onTableBeforeCheckin
FIND ALL LOCK TABLES INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'checked_out'
DIAGNOSE A LOCK SELECT a.*, u.name FROM <table> a JOIN #__users u ON u.id = a.checked_out
14. Summary
Joomla's check-in system is small, visible, and deliberately simple. Two columns hold the lock state. One backend screen releases the locks. One scheduled task automates the cleanup. There is no magic, no contrib module, no hidden state.
For administrators, the practical advice is this: enable the scheduled task with a sensible delay, train editors to click the lock icon for their own stuck rows, and treat Global Check-in as a break-glass tool you rarely need. For developers, the system is free for any extension that follows the two-column convention - no plugin registration, no XML.
Or, in one sentence: when Joomla did not get a chance to clean up after itself, Check-in acts as the cleanup mechanism.


Joomla en Linux specialist voor snelle, veilige en schaalbare websites.


