Developers
Direct database access
How to reach a hub's database from the command line, and the one convention
that will confuse you if nobody tells you about it: the #__ table prefix.
You will want a mysql prompt sooner than you expect — to see what a
migration actually did, or what a form saved. Read this before you write a
query into an extension, because the prefix rule is not optional and the
mistake it prevents does not show up until someone installs your work on a
hub configured differently from yours.
The credentials
A hub keeps its database credentials in app/config/database.php, which
returns a plain PHP array:
return array(
'dbtype' => 'mysql',
'host' => 'localhost',
'user' => 'hubuser',
'password' => '…',
'db' => 'hubdatabase',
'dbprefix' => 'jos_',
'port' => '',
);
The file is written by the installer and is not in the repository. Read it over SSH; it is only readable by the web server user and root.
mysql -u hubuser -p hubdatabase
The table prefix
Every Hubzero table name in the database carries a prefix — jos_ on a
default install, whatever dbprefix says on yours. Code never writes that
prefix. It writes #__, and the driver substitutes the configured value as
the statement goes out:
public function replacePrefix($sql, $prefix = '#__')
{
// As we replace strings of different lengths, subsequent prefix positions will become invalid.
// Thus, we track that differential here to account for the shifting locations
$differential = strlen($this->tablePrefix) - strlen($prefix);
$count = 0;
foreach (Str::findLiteral($prefix, $sql) as $prefixPosition)
{
$sql = substr_replace($sql, $this->tablePrefix, $prefixPosition + ($differential*$count), strlen($prefix));
$count++;
}
return $sql;
}
So #__users in a query is jos_users in the database, and a query with a
literal jos_ in it is a bug: it breaks on any hub whose prefix differs.
It will not break on yours, which is the problem — the failure arrives on
somebody else's hub as Table 'theirdb.jos_booking_instruments' doesn't exist, naming a prefix they have never used. Copying a working statement
out of a mysql prompt and into a model is how it happens; put the #__
back on the way in.
The same placeholder is the default table name a model derives for itself —
#__{namespace}_{plural model name} — so a Post model in the blog
namespace reads and writes #__blog_posts without being told to.
Translate in your head when you move between a mysql prompt and the code:
SELECT * FROM jos_users at the prompt is #__users in a query.
Dumping and loading
Muse wraps the two operations you actually need, using the hub's own credentials so you do not have to find them:
php core/bin/muse database dump
php core/bin/muse database load <file>
dump writes to your home directory. See the
muse database reference.
From code
Do not open your own connection. The hub's driver is App::get('db'), and
above it sit a query builder and an ORM that bind every value they are
given:
$rows = Post::all()
->whereEquals('state', 1)
->order('created', 'desc')
->rows();
The Database book covers all three layers: the
query builder, the
ORM, and
migrations for schema changes. Schema
changes belong in a migration, not in a mysql prompt — a change made by
hand is a change the next hub to run the migrations will not have.
Rewritten and checked against 2.4-main @ 348f0057c2 on 2026-09-10.