Skip to content
View in the app

A better way to browse. Learn more.

ernestdefoe.online

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
ernestdefoe.online

Extensions, themes & support for Flarum and Invision Community

Vibe coding for the community web. Report a bug, request a feature, or dig into the source — this is where the tools you use get built, in the open.

We do custom Bespoke Invision Community apps. If you have an idea for something you want then use the contact form to get in touch with us.
Knowledge base

Things that cost me a day, so they cost you none

Working notes from building Invision Community and Flarum applications. Mostly the failures that give no error at all — the ones where everything installs cleanly and quietly does the wrong thing.

92 articles

Invision Community 5

86 articles

Extensions and contracts

39

What each extension point is for, what it must declare, and what happens when it is wrong — which is usually nothing visible.

Languages and text

5

The string table, translation, and the places where text does not appear where you expected it to.

Theming, templates and forms

9

Theme hooks, CSS that survives both colour schemes, and building forms that do not throw on render.

Background work and scheduled tasks

5

The queue system, work that has to happen after the response, and jobs that finish without doing anything.

Data, settings and storage

11

The database layer, settings, tags, file storage, and backing up a live site.

AI features and expectations

5

What these features do, what they cost, and what buyers reasonably but wrongly assume they do.

Application structure and releases

11

The JSON files an application is made of, versioning and upgrade steps, and testing from the command line.

Realtime, chat and calls

1

WebSocket gateways, relays and the server-side pieces live features depend on — where "it works when I test it" and "it works for your members" are different claims.

Nothing matches that.

Extensions and contracts

What each extension point is for, what it must declare, and what happens when it is wrong — which is usually nothing visible.

39 Articles in this category

  1. Ernest Defoe ·
    A content listener must declare public static string $class, matching the extends value in your application's data/listeners.json. Omit it and the front page of the entire community returns a 500 — not just your application's pages. class Topic extends ContentListenerType { public static string $class = 'IPS\forums\Topic'; // REQUIRED } Why it is fatal rather than local ListenerType::getExtendedClass() reads that typed static with no isset() guard, and Event::loadListeners() walks eve
    • 0 comments
    • 28 views
  2. Ernest Defoe ·
    The core/ContentRouter extension is how an application tells the rest of Invision Community that a class is a content item. It is not a feature in itself — it is the list that roughly thirty other subsystems read to discover what content exists on the site: search, activity streams, the leaderboard, the member profile "Content" tab, moderator permissions, the Report Center, sitemaps, IP address lookup, achievement rules, follow records, warnings, advertisements, embeds and the REST/GraphQL conte
    • 0 comments
    • 38 views
  3. Ernest Defoe ·
    The core/Dashboard extension point adds a block (widget) to the AdminCP dashboard at app=core&module=overview&controller=dashboard. Core calls it in two completely separate places: once on the full page render in IPS\core\modules\admin\overview\dashboard::manage(), and again — through a different code path with different arguments — on the AJAX endpoint do=getBlock that the dashboard's JavaScript hits every time a block is added, dragged or refreshed. The contract system/Extensions/D
    • 0 comments
    • 42 views
  4. Ernest Defoe ·
    A core/FrontNavigation extension defines one type of item that an administrator can place in the front-end navigation bar via AdminCP → System → Menu Manager. The extension class is not the menu item itself: each row in the core_menu table names an app and an extension key, and core instantiates your class once per row, passing that row's stored configuration. Core builds the menu in IPS\core\FrontNavigation::roots() and ::subBars() (applications/core/sources/FrontNavigation/FrontNavigation.ph
    • 0 comments
    • 39 views
  5. Ernest Defoe ·
    A core/GroupForm extension adds a tab of fields to the member group form in the ACP (Members → Groups → edit), writes those values back onto the group when it is saved, and gets a say in what happens when a group is copied or deleted. It is the supported way for an application to attach its own per-group settings. Core calls it from exactly four places: IPS\core\modules\admin\members\groups::form() (build and save), and three methods on IPS\Member\Group — __clone(), delete() and canDelete(). E
    • 0 comments
    • 39 views
  6. Ernest Defoe ·
    The core/Permissions extension is how an application tells the suite two different things: which of its node classes own rows in core_permission_index and can be edited from the group permission matrix, and — separately — how the application wants to override normal permission checks at runtime. Core calls the first half exactly once, when the AdminCP renders Members → Groups → (a group) → Permissions; it calls the second half from IPS\Content\Permissions, which sits in front of nearly every can
    • 0 comments
    • 32 views
  7. Ernest Defoe ·
    A core/Sitemap extension contributes URLs to the XML sitemap that sitemap.php serves, and adds the per-app fields to ACP → Promotion → Search Engine Optimisation → Sitemap. Core calls it from three places: the hourly core/sitemapgenerator task (via IPS\Sitemap::buildNextSitemap()), the core/RebuildSitemap background queue task fired by the ACP "Rebuild Sitemap" button, and the ACP SEO controller itself when it builds and saves the settings form. The contract All four methods in IPS\Extension
    • 0 comments
    • 40 views
  8. Ernest Defoe ·
    The core/AccountSettings extension adds a tab to the member-facing account settings screen — the page at /settings, served by IPS\core\modules\front\system\settings. Core calls it in exactly one place, that controller's manage() method, and it calls it twice per page load: once to find which extension owns the requested area and render its body, and once more (inside _wrapOutputInTemplate()) to build the sidebar list of every tab. Core ships exactly one implementation of this extension, IPS\co
    • 0 comments
    • 34 views
  9. Ernest Defoe ·
    The core/ModeratorPermissions extension is how an application adds fields to AdminCP → Members → Staff → Moderators → [edit], and how it gets told when a moderator record is created, changed or deleted. Every field you declare becomes one key in a single flat JSON blob stored in core_moderators.perms, which is read back at runtime by IPS\Member::modPermission( 'your_key' ). The only consumer is applications/core/modules/admin/staff/moderators.php. It calls Application::allExtensions( 'core', '
    • 0 comments
    • 38 views
  10. Ernest Defoe ·
    The core/OutputPlugins extension registers a new {tag="value"} that can be used in theme templates, CSS, email templates, Pages blocks and Pages content. It is unlike almost every other extension in Invision Community 5 in one crucial respect: it does not run when the page is rendered. It runs when the template is compiled, and what it returns is not output — it is a fragment of PHP source code that gets written into the compiled template function and later eval()'d. The single consumer is IPS
    • 0 comments
    • 38 views
  11. Ernest Defoe ·
    The core/Uninstall extension is the only hook an application gets into its own removal, and the only way one application can react to a different application being removed. Core calls it from IPS\Application::delete() (system/Application/Application.php) — preUninstall() before any data is touched, onOtherUninstall() on every enabled application immediately after, and postUninstall() near the end, after your database tables have already been dropped. Despite the docblock wording, there is no p
    • 0 comments
    • 44 views
  12. Ernest Defoe ·
    Invision Community lets an application react to things that happen elsewhere in the suite — a member registering, a topic being posted, an invoice being paid — by registering a listener. There are 58 hooks across eight listener types. They are not documented anywhere. The only record that a hook exists is an @method annotation in the abstract class, so this page is a transcription of those, checked against the code that fires them. Registering a listener data/listeners.json in your applicati
    • 0 comments
    • 29 views
  13. Ernest Defoe ·
    Hideable, Taggable, Lockable, FuturePublishing and the rest are traits, not interfaces. Testing for them with instanceof compiles, runs, throws nothing — and is false for every object on the site. Wrong if ( $item instanceof \IPS\Content\Taggable ) // always FALSE { $item->setTags( $tags ); } The feature simply never runs. Nothing in a log, nothing in a test that only asserts "no exception was thrown". Right if ( \IPS\IPS::classUsesTrait( $item, 'IPS\Content\Taggable' ) ) { $ite
    • 0 comments
    • 30 views
  14. Ernest Defoe ·
    Invision Community has a rich set of member filters behind the core/MemberFilter extension point: group, join date, last visit, content count, reputation, achievements, profile fields, and from Commerce, purchases, subscriptions, total spend and donations. Any application can reuse them instead of writing its own. There is one trap, and it fails silently. The trap Every filter decides for itself which "areas" it is available in, and the list is hardcoded: // core/MemberFilter/Lastvisit.php r
    • 0 comments
    • 25 views

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.