Customizing BinktermPHP

May 14, 2026 · View on GitHub

BinktermPHP is designed to be customized without touching core files, so your changes survive upgrades. This document covers the full customization system, from the point-and-click Appearance admin panel to low-level template overrides and custom shells.


Table of Contents


Appearance System

The Appearance system is the primary way to customize your BBS. All settings are managed through Admin → Appearance in the web interface and are stored in data/appearance.json. Changes take effect immediately — no restart required.

Shells

A shell is the overall UI chrome that wraps every page. BinktermPHP ships with two shells:

ShellDescription
webModern Bootstrap 5 responsive interface. The default.
bbs-menuRetro bulletin board menu interface with three display variants.

Shell selection priority (highest to lowest):

  1. User's personal preference — stored in the UserMeta table under the key shell, only honoured when the shell is not locked.
  2. Sysop default — the shell.active value in data/appearance.json.
  3. Built-in fallback — web.

To lock all users to a single shell, enable Lock shell in Admin → Appearance → Shell. When locked, the per-user preference is ignored.

Shell templates live in templates/shells/<shell-name>/. The active shell's directory is added to the Twig loader search path ahead of the core templates/ directory, so any template placed there overrides its core equivalent.


BBS Menu Shell

The bbs-menu shell replaces the standard navbar with a classic bulletin board main menu. Three variants are available:

cards (default)

A Bootstrap card grid. Each menu item shows an icon, label, and keyboard shortcut. Good for mouse and touch users.

text

A terminal-style numbered/lettered text menu rendered in a monospace font with cyan, yellow, and green colours. Feels like a traditional BBS.

ansi

Displays an ANSI art image (uploaded via Admin → Appearance → ANSI Art) above or instead of the menu. Uses the Perfect DOS VGA 437 web font (public_html/fonts/PerfectDOSVGA437.ttf) and the public_html/css/ansisys.css stylesheet for authentic CP437 rendering.

The BBS menu shell now includes an ANSI Art Size setting in Admin → Appearance → Shell with these presets:

  • 80x25 - Traditional BBS screen size. This is the authentic baseline presentation, so it can look smaller than the larger presets on modern displays.
  • 132x24 - Wide-screen ANSI mode using a fixed 132-column canvas.
  • 132x43 - Wide-screen ANSI mode with a taller fixed canvas.
  • 132x50 - Wide-screen ANSI mode with the tallest fixed canvas.
  • Full Screen - Scales the rendered ANSI art to fit the available browser viewport below the shell header.

These presets apply to the BBS menu shell only. Other ANSI views on the site keep their normal renderer behaviour.

Customising menu items

The menu items shown in all three BBS menu variants are configurable through the Appearance admin panel. Each item has:

FieldDescription
keySingle letter or number shown as the keyboard shortcut
labelDisplay name
iconFont Awesome icon name (without the fa- prefix)
urlDestination URL

Items can be reordered via drag-and-drop in the admin UI. The list is stored in data/appearance.json under shell.bbs_menu.menu_items.


ANSI Art Display

When the bbs-menu shell is set to the ansi variant, BinktermPHP displays an ANSI art file on the dashboard.

Uploading art files:

  1. Go to Admin → Appearance → ANSI Art and upload a .ans, .asc, or .txt file (CP437 encoded DOS ANSI art).
  2. Go to Admin → Appearance → Shell.
  3. Set the BBS menu variant to ansi.
  4. Select the uploaded art file.
  5. Choose an ANSI Art Size preset.

Art files are stored in data/shell_art/. BinktermPHP strips the SAUCE record (EOF marker \x1A) and converts CP437 bytes to UTF-8 before rendering, so standard ANSI editors (TheDraw, PabloDraw, etc.) produce compatible files.

The public_html/css/ansisys.css stylesheet provides:

  • Fixed-width character cells (.ansi-c) to prevent glyph misalignment
  • Full CGA/EGA colour class set (.ansi-red, .ansi-bg-cyan, .ansi-bright-yellow, etc.)
  • Text attribute classes (.ansi-bold, .ansi-blink, .ansi-reverse)
  • Responsive scaling (8 px on mobile, 6 px on very small screens)

For the BBS menu shell specifically, the size presets use a fixed ANSI grid so 80x25, 132x24, 132x43, and 132x50 render as distinct terminal layouts instead of all collapsing to the same trimmed output.


Branding

SettingDescription
Accent colourCSS hex colour applied as the primary button and header colour via a --bbs-accent CSS variable. Leave blank to use the theme default.
Logo URLURL of an image to replace the default system name text in the navbar.
Default themeForce a specific theme stylesheet for all users.
Lock themeWhen enabled, users cannot change their theme in Settings.
Footer textCustom HTML or plain text shown in the page footer.

Announcements

Announcements appear as a dismissible banner at the top of every page (below the navbar). Settings:

FieldDescription
EnabledShow or hide the banner.
TextThe announcement message. HTML is allowed.
TypeBootstrap alert type: info, warning, or danger.
Expires atOptional date after which the banner stops showing automatically.
DismissibleWhen enabled, users can close the banner. It stays closed for their session.

Announcement state is stored in data/appearance.json under content.announcement.


System News

System news is the content shown on the dashboard after login. Priority order:

  1. data/systemnews.md — Managed via Admin → Appearance → System News. Write in Markdown; BinktermPHP renders it to HTML automatically.
  2. templates/custom/systemnews.twig — Legacy Twig template override. See templates/custom/systemnews.twig.example.
  3. Built-in default — A generic welcome message with the sysop's name and BinktermPHP version.

Using data/systemnews.md is recommended. It is upgrade-safe and editable through the admin UI.


House Rules

House rules are displayed on the /houserules page when enabled. Write them in Markdown and save to data/houserules.md via Admin → Appearance → House Rules. If the file does not exist, the /houserules route is not shown.


Custom links can be added to the navigation bar under Admin → Appearance → Navigation. Each link has:

FieldDescription
LabelLink text
URLDestination (absolute or relative)
Open in new tabWhen enabled, opens in a new browser tab

Links are appended after the built-in navigation items and stored in data/appearance.json under navigation.custom_links.


SEO Settings

SettingDescription
Site descriptionMeta description tag used by search engines.
OG image URLOpen Graph image for social sharing previews.
About page enabledEnable the /about page, which displays the site description publicly.

Message Reader

The Admin → Appearance → Message Reader tab controls a small set of message-reading and messaging-menu behaviours.

SettingDescription
Scrollable message bodyKeeps the message header fixed while the message body scrolls independently.
E-mail link URLOptional webmail/external mail URL added to the Messaging dropdown directly under Echomail. Leave blank to hide the link.

To add a webmail link:

  1. Go to Admin → Appearance → Message Reader.
  2. Enter the destination in E-mail link URL. This can be an absolute URL such as https://mail.example.com/ or another valid destination you want exposed from the Messaging menu.
  3. Click Save Message Reader Settings.

When set, logged-in users will see an E-mail item in the Messaging menu, directly below Echomail.


File Areas

The Admin → Appearance → File Areas tab lets you add custom content to the file area pages — useful for upload ratio policies, DMCA contact information, or any other notices you want visible to users browsing files.

SettingDescription
Panel titleOptional heading displayed at the top of the sidebar info panel. Leave blank for no heading.
Sidebar content (Markdown)Markdown rendered in a card on the right-hand sidebar of the file area. Leave blank to hide the panel entirely.
Footer content (Markdown)Markdown rendered in a card below the file list. Useful for DMCA notices, ratio policies, or other notices. Leave blank to hide the footer entirely.

Example sidebar content:

**Upload ratio:** 1:3 — for every 1 MB uploaded you may download 3 MB.

Questions? Contact <sysop@example.com>.

Example footer content:

**DMCA Notice:** To file a takedown request, e-mail <dmca@example.com>.

Both fields accept Markdown. Changes take effect immediately after saving.


Themes

Themes are CSS files that control the colour palette and overall visual style. Available themes are defined in config/themes.json:

{
    "Amber":      "/css/amber.css",
    "Cyberpunk":  "/css/cyberpunk.css",
    "Dark":       "/css/dark.css",
    "Green Term": "/css/greenterm.css",
    "Regular":    "/css/style.css"
}

To add a custom theme:

  1. Create a CSS file in public_html/css/.
  2. Add an entry to config/themes.json.
  3. The theme will appear in user Settings and in Admin → Appearance → Branding.

Theme selection priority:

  1. If theme is locked by sysop → sysop default theme always wins.
  2. User's theme preference (stored in user_settings table).
  3. STYLESHEET environment variable in .env.
  4. /css/style.css (built-in fallback).

You can also set the default stylesheet via .env:

STYLESHEET=/css/dark.css

Template Overrides

Any core template can be overridden by placing a file with the same name in templates/custom/. The custom directory is checked first, so your file wins without modifying anything in templates/.

Example — override the dashboard:

cp templates/dashboard.twig templates/custom/dashboard.twig
# edit templates/custom/dashboard.twig

Files in templates/custom/ are never touched by BinktermPHP updates.


Template Resolution Order

When Twig resolves a template name, it searches these paths in order:

  1. templates/custom/ — Local overrides (highest priority)
  2. templates/shells/<active-shell>/ — Shell-specific templates
  3. templates/ — Core templates (lowest priority)

The first matching file wins. This means:

  • A file in templates/custom/ overrides both the shell and the core.
  • A file in the active shell directory overrides only the core.
  • Core templates are the fallback and should never be edited directly.

Custom Shells

You can create a completely new shell without modifying any core file.

  1. Create the directory templates/shells/<your-shell-name>/.
  2. Add a base.twig that defines the page layout. Use the existing templates/shells/web/base.twig as a starting point.
  3. Place any additional shell-specific templates in the same directory.
  4. Set shell.active to "<your-shell-name>" in data/appearance.json, or select it via Admin → Appearance.

Shell templates have access to all Twig global variables. The active_shell global contains the current shell name, and appearance contains the full appearance configuration array.


Custom Routes

To add new pages or API endpoints without touching core files, create the file routes/web-routes.local.php. It is loaded automatically if it exists and is never overwritten by updates.

Example:

<?php
// routes/web-routes.local.php

$router->get('/hello', function() use ($template) {
    echo $template->render('custom/hello.twig', ['message' => 'Hello, world!']);
});

Place the corresponding template in templates/custom/hello.twig.


Header Insertions

To inject HTML into the <head> of every page (analytics, custom CSS, external fonts, etc.), create:

templates/custom/header.insert.twig

See templates/custom/header.insert.twig.example for a reference with Google Analytics and other examples.


Welcome Messages

Several plain-text files in config/ control messages shown to users:

FileWhere it appears
config/welcome.txtMain page or login screen general welcome
config/terminal_welcome.txtReplaces the default "SSH Connection to host:port" message on the terminal login page
config/newuser_welcome.txtEmail body sent to newly approved users

Create the file if it does not exist. If it does not exist, the built-in default is shown.


Twig Global Variables

These variables are available in every template:

VariableTypeDescription
current_userarray|nullAuthenticated user record (password_hash removed)
system_namestringBBS name from config/binkp.json
sysop_namestringSysop name from config/binkp.json
fidonet_originstringPrimary FTN address
network_addressesarrayAll configured FTN addresses with domains
csrf_tokenstringPer-user CSRF token for form submissions
active_shellstringCurrently active shell (web or bbs-menu)
appearancearrayFull data/appearance.json contents (merged with defaults)
appearance_houserules_htmlstring|nullRendered house rules HTML, or null if not set
available_themesarrayMap of theme name → CSS path from config/themes.json
stylesheetstringActive theme CSS path
app_versionstringBinktermPHP version string
app_namestringApplication name
app_full_versionstringFull version string including name
terminal_enabledboolWhether the web terminal door is enabled
webdoors_activeboolWhether the WebDoors game system is enabled
credits_enabledboolWhether the credits system is enabled
credits_symbolstringCredits currency symbol
credit_balanceintCurrent user's credit balance
referral_enabledboolWhether referrals are enabled
default_echo_liststringUser's preferred echo interface (echolist or echoarea)
csrf_tokenstringCSRF token for POST forms
favicon_svgstringFavicon SVG path
favicon_icostringFavicon ICO path
favicon_pngstringFavicon PNG path

Configuration File Reference

data/appearance.json

Full schema with defaults:

{
  "shell": {
    "active": "web",
    "lock_shell": false,
    "bbs_menu": {
      "variant": "cards",
      "menu_items": [
        { "key": "M", "label": "Messages",    "icon": "envelope", "url": "/echomail" },
        { "key": "N", "label": "Netmail",     "icon": "at",       "url": "/netmail"  },
        { "key": "F", "label": "Files",       "icon": "folder",   "url": "/files"    },
        { "key": "G", "label": "Games & Doors","icon": "gamepad", "url": "/games"    },
        { "key": "S", "label": "Settings",    "icon": "cog",      "url": "/settings" }
      ],
      "ansi_file": ""
    }
  },
  "branding": {
    "accent_color": "",
    "default_theme": "",
    "lock_theme": false,
    "logo_url": "",
    "footer_text": ""
  },
  "content": {
    "announcement": {
      "enabled": false,
      "text": "",
      "type": "info",
      "expires_at": null,
      "dismissible": true
    }
  },
  "navigation": {
    "custom_links": [
      { "label": "Example", "url": "https://example.com", "new_tab": false }
    ]
  },
  "seo": {
    "description": "",
    "og_image_url": "",
    "about_page_enabled": false
  },
  "message_reader": {
    "scrollable_body": true,
    "email_link_url": ""
  }
}

All fields are optional. Missing fields fall back to the defaults shown above. Edit this file directly or use the Admin → Appearance UI — changes take effect immediately.