Creating a Child App
Child apps are separate git repos deployed as siblings of /admin/ in the webroot. Clone the template, rename it, and start building your features.
/admin/ fully installed and configured before deploying any child app. See the Setup Guide for admin installation steps.
Quick Start
-
Copy the template into a new folder
# If you haven't already cloned the template: git clone git@github.com:WaveNetworks/child-app.git child-app # Copy it to your own app folder: cp -r child-app my-app cd my-app rm -rf .git git init git remote add origin git@github.com:YourOrg/my-app.gitKeep
child-app/intact as a reference. Build your app in the copy. -
Rename database references
Edit
docker-compose.ymland.env.exampleto use your app's database names (e.g.,myapp_main,myapp_shard_1).Multi-app Docker: If running other apps on the same machine, rename ALL volume names indocker-compose.ymlwith your app prefix and choose a unique host port. See Setup Guide → Running Multiple Apps. -
Build the theme
npm install && npm run build:cssThe template ships SCSS source only. Without this step, the custom glassmorphism theme won't be available — you'll see plain Bootstrap.
-
Start developing
docker compose up -d # Visit http://localhost/admin/install/index.php (set up admin first) # Visit http://localhost/my-app/install/index.php (then child app) -
Build your features
Add views, actions, migrations, and your own theme. The original
child-app/has working examples of every pattern — reference it anytime.
Bootstrap Pattern
Your child app's include/common.php is the central bootstrap file. Here's the complete pattern:
<?php
// 1. Include admin core — provides: autoload, config, $db, helpers,
// admin migrations, session guard, admin actions.
include(__DIR__ . '/../../admin/include/common.php');
// 2. Load child app's own config (only child DB credentials)
$childConfigFile = __DIR__ . '/../config/config.php';
if (file_exists($childConfigFile)) {
include($childConfigFile);
} else {
// Docker: read from environment
$child_db_host = getenv('CHILD_DB_HOST') ?: 'db_child';
$child_db_name = getenv('CHILD_DB_NAME') ?: 'childapp_main';
$child_db_user = getenv('CHILD_DB_USER') ?: getenv('DB_USER') ?: 'root';
$child_db_pass = getenv('CHILD_DB_PASS') ?: getenv('DB_PASSWORD') ?: '';
// ... build $childShardConfigs from env vars
}
// 3. Open child app's own main database
$child_db = new PDO(
"mysql:host=$child_db_host;dbname=$child_db_name;charset=utf8mb4",
$child_db_user, $child_db_pass
);
$child_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 4. Include child DB helpers and migration functions
include_once(__DIR__ . '/common/childDBFunctions.php');
include_once(__DIR__ . '/common/appMigrationFunctions.php');
// 5. Run child app migrations (independent of admin's versions).
// Targets are declared once in include/migration_versions.php.
include(__DIR__ . '/migration_versions.php');
child_check_and_migrate($child_db, 'main', $child_db_version, __DIR__ . '/../db_migrations/');
child_check_and_migrate_shards($child_shard_version, __DIR__ . '/../db_migrations/');
// 6. Child app definitions, helpers, and actions
include(__DIR__ . '/definition.php');
foreach (glob(__DIR__ . '/common/*.php') as $f) { include_once($f); }
foreach (glob(__DIR__ . '/actions/memberActions/*.php') as $f) { include_once($f); }
foreach (glob(__DIR__ . '/actions/apiActions/*.php') as $f) { include_once($f); }
$db_version or $shard_version before including admin's common.php. Admin must run its own migrations first against its own database.What You Get After Bootstrap
| Variable / Function | Purpose |
|---|---|
$db | Core main DB connection (READ ONLY from child apps) |
$child_db | Child app's own main DB — all your tables live here |
child_db_query($sql) | Query child main DB |
child_db_query_prepared($sql, $params) | Prepared query on child main DB |
child_db_insert_id() | Last insert ID on child main DB |
child_prime_shard($shard_id) | Open/cache child shard connection |
child_db_query_shard($shard, $sql) | Query child shard DB |
child_db_query_shard_prepared($shard, $sql, $params) | Prepared shard query |
$_SESSION['user_id'] | Authenticated user (set by core login) |
$_SESSION['shard_id'] | Which shard this user lives on |
queue_email($to, $subject, $body, $opts) | Send email via core's queue |
send_notification($uid, $shard, $slug, $title, $body) | Push a notification to a user |
h($string) | HTML-escape output |
sanitize($val, SQL) | SQL-escape input |
db_query($sql) / db_fetch($result) | Core DB helpers |
record_consent($uid, $type, $action, $vid) | Log consent decision (immutable audit trail) |
get_consent_status($uid, $type) | Check current consent status |
request_data_export($uid, $format) | GDPR data export (JSON/CSV) |
get_login_history($uid, $limit, $offset) | User's login history |
get_user_devices($uid) | Active device sessions |
revoke_device($device_id, $uid) | Remote session logout |
Child App Shard Queries
$shard = $_SESSION['shard_id'];
// Read from ADMIN's shard (user_profile — read only)
prime_shard($shard);
$profile = db_fetch(db_query_shard($shard,
"SELECT * FROM user_profile WHERE user_id = '" . $_SESSION['user_id'] . "'"));
// Read/write CHILD APP's own shard
child_prime_shard($shard);
$history = child_db_query_shard_prepared($shard,
"SELECT * FROM user_history WHERE user_id = ?",
[$_SESSION['user_id']]);
Child App Migrations
Same two-step rule as admin. Create an SQL file, then bump the version.
// File: db_migrations/main/1.1.sql
-- Migration 1.1 for Child App Main Database
-- Add categories table
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
CREATE TABLE IF NOT EXISTS `categories` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
COMMIT;
// In include/migration_versions.php, update:
$child_db_version = 1.1; // matches the filename
1.0 and are completely independent of admin's version numbers. Never edit existing migration files — always create a new version.Deployment Layout
Public-Facing Website
Need a marketing or documentation site for your child app? Clone the subtheme-website repo:
git clone git@github.com:WaveNetworks/subtheme-website.git your-site
It's a standalone PHP + HTML + CSS site that uses common_auth.php (no session guard) for branding. See the Public Website docs for the full pattern and setup steps.
What NOT to Do
- Name your DB variable
$db— use$child_db - Write to core tables (
user,device,api_key,email_queue) - Copy
admin/assets/— reference via../../admin/assets/ - Define
$db_versionbefore including admin'scommon.php - Put child app credentials in admin's
config.php - Create new API endpoint files — use action files instead
- Use
<form action="api/index.php">— post to the current page - Alter or drop core tables in child app migrations
- Reuse core's database names for your app DB