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.
Invision Community 5
86 articlesExtensions and contracts
39What each extension point is for, what it must declare, and what happens when it is wrong — which is usually nothing visible.
- A content listener without $class takes down your whole community
- Content behaviours are TRAITS, so instanceof is always false
- Event listeners: all 58 hooks, and the argument-order trap
- Extension method signatures are fixed, and a mismatch is a fatal on installNew
- MemberFilter extensions are gated by a hardcoded area whitelist
- One content listener for every content type, including Pages databasesNew
- The core/AccountSettings extension in Invision Community 5
- The core/AchievementAction extension in Invision Community 5
- The core/AdminNotifications extension in Invision Community 5
- The core/Build extension in Invision Community 5
- The core/CommunityEnhancements extension in Invision Community 5
- The core/ContactUs extension in Invision Community 5
- The core/ContentRouter extension in Invision Community 5
- The core/Dashboard extension: ACP dashboard blocks in Invision Community 5
- The core/EditorMedia extension in Invision Community 5
- The core/FrontNavigation extension in Invision Community 5
- The core/GroupForm extension: adding fields to the member group form
- The core/GroupLimits extension: merging secondary group settings in Invision Community 5
- The core/IpAddresses extension in Invision Community 5
- The core/LiveSearch extension in Invision Community 5
- The core/MemberACPProfileBlocks extension in Invision Community 5
- The core/MemberACPProfileTabs extension: AdminCP member view tabs in Invision Community 5
- The core/MemberExportPersonalInformation extension in Invision Community 5
- The core/MemberHistory extension in Invision Community 5
- The core/MemberRestrictions extension in Invision Community 5
- The core/MetaData extension in Invision Community 5
- The core/MFAArea extension in Invision Community 5
- The core/ModCp extension in Invision Community 5
- The core/ModCpMemberManagement extension: adding tabs to the ModCP member management page in Invision Community 5
- The core/ModeratorPermissions extension in Invision Community 5
- The core/Notifications extension in Invision Community 5
- The core/OutputPlugins extension: how a {tag} in a template is resolved
- The core/OverviewStatistics extension in Invision Community 5
- The core/Permissions extension and node permission integration in Invision Community 5
- The core/ProfileSteps extension in Invision Community 5
- The core/Sitemap extension in Invision Community 5
- The core/Statistics extension in Invision Community 5
- The core/Uninstall extension in Invision Community 5
- Where OAuth account links live, and why you should not keep your own copyNew
Languages and text
5The string table, translation, and the places where text does not appear where you expected it to.
- A core/Notifications extension needs a language key named after the CLASS
- A module shows as module__myapp_thing on the administrator restrictions screen
- addToStack() often returns a placeholder, not translated text
- Creating a language in code: set_short() does not store the locale
- Why your app shows menutab__content or module__myapp_thing to customersNew
Theming, templates and forms
9Theme hooks, CSS that survives both colour schemes, and building forms that do not throw on render.
- "This theme may be out of date" usually means your form field, not the theme
- A container query cannot style its own container
- core/Loader js() and css() must return an array of arrays
- Custom CSS that works in both colour schemes, and the var() name trap
- Form\Editor needs an EditorLocations extension that actually exists
- Setting a theme custom_css and calling save() silently does nothing
- The Invision Community 4 CSS class that silently does nothing in 5New
- Theme hook types are an enum of four, there is no "replace", and how to change the page title anyway
- Widget conventions: two language keys, ipsWidget markup, and cached blanks
Background work and scheduled tasks
5The queue system, work that has to happen after the response, and jobs that finish without doing anything.
- A realtime gateway will let any member join any channel unless you sign the channel into the tokenNew
- Running code on every request, after the page has been sent
- The core/Queue extension in Invision Community 5
- The report that shows nothing: only_full_group_by, and why catching the exception is the real bug
- Your background task is running inside a visitor's page loadNew
Data, settings and storage
11The database layer, settings, tags, file storage, and backing up a live site.
- A Pages category created in code sends every article into a redirect loop
- Building a podcast feed Apple will acceptNew
- Creating a Pages database and categories in code
- Db::insert() third and fourth arguments do very different things
- Dumping an Invision Community database from PHP: ANSI_QUOTES, multi-member gzip, and the 0x trap
- Pages page content is not a template: why template syntax prints itself, and why removing an element does not stick
- S3 and R2 storage break in any CLI script without HTTPS=on
- Taggable::setTags() deletes every existing tag first
- The ?: fallback silently breaks any setting an admin can set to zero
- The core/FileStorage extension in Invision Community 5
- There is no event when a tag is created, and what that means for your app
AI features and expectations
5What these features do, what they cost, and what buyers reasonably but wrongly assume they do.
- A chat model in an embeddings field fails silently, and looks like a broken app
- Why a similarity threshold is never enough for automatic classification
- Why your AI assistant's links arrive as plain textNew
- Your AI assistant is only as good as what you gave it to read
- Your classifier does not learn, and everyone will assume it does
Application structure and releases
11The JSON files an application is made of, versioning and upgrade steps, and testing from the command line.
- "You do not have permission for that" on your own AdminCP screen: the $csrfProtected property
- A malformed manifest silently disables your whole application
- A shared image vanishes the moment one member changes their profile photoNew
- A stray "menutab_system" in your AdminCP: acpmenu.json tabs are not validated
- Application versions: why re-uploading the same version does nothing
- Invision Community already runs an OAuth 2.1 server — do not write another oneNew
- Raw language keys in the AdminCP menu: acpmenu.json must be an object
- Six ways an Invision Community 5 application fails silentlyNew
- What you can and cannot test from the command line
- Your editor toolbar button never appears, and nothing is loggedNew
- Your friendly URLs have the app name in them twiceNew
Realtime, chat and calls
1WebSocket 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.
Flarum 2
6 articles- Flarum 2: AbstractModel mass assignment throws a 500
- Flarum 2: frontend model relations must use the hasOne/hasMany call pattern
- Flarum 2: json-api-server returns 403 for declared but unwritable fields
- Flarum 2: Laravel facades throw "facade root has not been set"
- Flarum 2: never redefine core CSS variables at :root
- Flarum 2: notification Blueprints need getFromUser()
Nothing matches that.
Extensions and contracts
39 Articles in this category
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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', '
-
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
-
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
-
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
-
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
-
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