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 must be installed first. The child-app does not work standalone — it depends on the admin core for authentication, sessions, database helpers, shard routing, email queue, notifications, and all shared services. You must have /admin/ fully installed and configured before deploying any child app. See the Setup Guide for admin installation steps.

Quick Start

  1. 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.git

    Keep child-app/ intact as a reference. Build your app in the copy.

  2. Rename database references

    Edit docker-compose.yml and .env.example to 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 in docker-compose.yml with your app prefix and choose a unique host port. See Setup Guide → Running Multiple Apps.
  3. Build the theme
    npm install && npm run build:css

    The template ships SCSS source only. Without this step, the custom glassmorphism theme won't be available — you'll see plain Bootstrap.

  4. 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)
  5. Build your features

    Add views, actions, migrations, and your own theme. The original child-app/ has working examples of every pattern — reference it anytime.

Don't build directly in child-app/. Always copy the template to a new folder first. This keeps the original intact so you can diff against it, pull template updates, or spin up additional child apps later from a clean starting point.
Don't delete demo content prematurely. Build your new features alongside the existing items, examples, and game code. They provide tested, working reference implementations of: CRUD action files, live search, AJAX modals with inline validation, DataTables with server-side pagination, D3.js dashboard charts, toast notifications, and canvas backgrounds. Remove demo code only after your replacement features are working.

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); }
Do NOT set $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 / FunctionPurpose
$dbCore main DB connection (READ ONLY from child apps)
$child_dbChild 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
Child app versions start at 1.0 and are completely independent of admin's version numbers. Never edit existing migration files — always create a new version.

Deployment Layout

your-app/ auth/ ← branded auth pages (login, register, etc.) login.php register.php template.php ← auth page HTML shell app/ index.php ← main controller (routes ?page= to views/) api/ index.php ← single JSON API endpoint include/ common.php ← authenticated bootstrap (see above) common_auth.php ← auth page bootstrap (no session guard) common_api.php ← API bootstrap definition.php ← app metadata / variable pre-declarations common/ ← helper functions (glob-included) childDBFunctions.php ← $child_db + shard wrappers appMigrationFunctions.php appFunctions.php ← your custom helpers actions/ memberActions/ ← authenticated form handlers apiActions/ ← AJAX handlers views/ ← UI pages (loaded by app/index.php) template.php ← HTML shell (sidebar, navbar, footer) dashboard.php snippets/ ← modal content, reusable partials config/ config.php ← gitignored — child DB credentials db_migrations/ main/ ← child main DB migrations shard/ ← child shard DB migrations assets/ scss/ ← SCSS source (custom Bootstrap theme) css/ ← compiled CSS output js/ ← app-specific JavaScript .htaccess ← denies include/, config/, views/, snippets/

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