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
-
You write an extension, the file lints clean, and installing the application dies: Fatal error: Declaration of IPS\myapp\extensions\core\FileStorage\Audio::move(?int $offset = null): void must be compatible with IPS\Extensions\FileStorageAbstract::move(int $offset, int $storageConfiguration, ?int $oldConfiguration = null): void Extension methods are declared by an abstract class, and PHP enforces the signature exactly. This is not a warning you can ship past — the application will not install
-
If your application needs to react to content being posted anywhere — forum topics, gallery images, calendar events, downloads, blog entries, and every record in every Pages database — you do not need a listener per class. You need one, declared against the right base class. Why the obvious approach does not scale A content listener is registered in data/listeners.json with the class it watches: { "Post": { "type": "ContentListenerType", "classname": "IPS\\myapp\\listen
-
If your application connects members to an outside service — Discord, Telegram, anything with OAuth — the temptation is to add a table: myapp_links, member id, remote id, done. Do not. Invision Community already stores that relationship, and keeping a second copy creates a class of bug you cannot fix later. core_login_links When a member signs in with, or connects, an OAuth login handler, the link is recorded in core_login_links: token_login_method — the id of the login handler token_mem
-
Achievements are the points, badges and ranks a member collects for taking part. A core/AchievementAction extension is one thing a member can do that an administrator is then allowed to attach a rule to. "Member starts a topic", "member votes on a poll", "member's answer is marked as the best answer" and "member downloads a file" are all AchievementAction extensions; each one appears as an option in the drop-down at ACP → Members → Achievements → Rules → Add Rule, brings its own filter fields to
-
The core/AdminNotifications extension is how something on the site gets an administrator's attention. It defines a type of ACP notification. Once one has been raised, the bell in the AdminCP header gains a count, the notification appears in the dropdown behind that bell and on the notification centre at app=core&module=overview&controller=notifications, and — if you declared it high or critical — it is also rendered as a coloured banner across the top of every AdminCP page. Critical ones
-
An administrator opening AdminCP → Site Features → Integrations sees a grid of cards, one per third-party service: Google Analytics, Mailchimp, Giphy, Zapier, Mapbox, Postmark, MaxMind and so on. Each card carries a title, a short description, a logo, an Enable or Disable button, and — once the service is on — a Configure Service button that opens a settings form. The core/CommunityEnhancements extension is what puts one of those cards on that page. That is the whole of its job. It does not make
-
A core/EditorMedia extension adds a tab to the Insert existing attachment dialog — the folder icon in the editor's attachment bar. A member composing a post clicks it, and gets a grid of files they already own on the site, with a search box and pagination, and a button to insert one or several into the post they are writing. Core ships one source (their previous post attachments), Gallery adds their gallery images, and Downloads adds the files they have uploaded to Downloads. Your extension adds
-
An administrator types an IP address into AdminCP → Members → IP Address Tools and gets a grid of tiles: "Posts 14", "Registrations 1", "Device Logins 3", "Transactions 0". Clicking a tile opens a table of the matching records. The same screen exists for moderators in the ModCP under "IP Tools", and the reverse view — every IP a given member has ever used, with a first-seen and last-seen date — appears on a member's ACP profile, in the ban form, and in the GDPR personal-information export. Every
-
When an administrator opens a member in the AdminCP — app=core&module=members&controller=members&do=view&id=1 — the page they see is not a template. It is a two-column grid assembled at runtime from a list of small classes, one per panel: the header with the 30-day posting sparkline, the groups panel, the warnings and restrictions panel, the known devices panel, the quota bars, the Commerce customer panel. Each of those is a core/MemberACPProfileBlocks extension. The extension po
-
Invision Community keeps an audit trail of things that happen to a member's account — group changes, display name changes, password changes, warnings, purchases, and so on. Each entry is one row in core_member_history, and the row itself is nothing but an application key, a type string and a blob of JSON. A core/MemberHistory extension is what turns that JSON back into a readable sentence. Without one, an administrator looking at ACP → Members → (a member) → History sees a blank cell where the d
-
In the AdminCP, every member's profile page carries a box headed "Warnings & Restrictions". It lists, in red, the things this particular account is currently not allowed to do — "Restricted from submitting files", "Restricted from using tags", "Restricted from accessing Gallery" — and it has an Edit link that opens a dialog where an administrator can turn those restrictions on and off for that one member. A core/MemberRestrictions extension is how an application adds its own rows to that lis
-
The core/MetaData extension point backs three things a member sees on a content item: the coloured staff notice pinned above the first post ("Item Message"), the box of recommended replies shown above the comment list ("Featured Comments"), and the moderator toggle that makes every new reply to one particular topic require approval ("Item Moderation"). All three store their state as rows in a single shared table, core_content_meta, rather than in the content item's own table, and the three exten
-
A core/ModCp extension adds one section to the front-end Moderator Control Panel — the page at /modcp that moderators use to work through reports, the approval queue, hidden and deleted content, warnings, alerts, announcements and IP lookups. Each extension owns one entry in the left-hand menu, owns the whole content pane when that entry is selected, and may optionally contribute a counter badge to the strip along the top of the page. There is no other way to add a section to that screen; the me
-
A moderator who opens the Moderator Control Panel and clicks "Member Management" gets a member search box and, under it, a strip of tabs: Banned, Restricted, Queued. Each tab is a table of members who are in some administrative state the moderator may need to act on — members serving a ban or suspension, members whose posts are being held for approval, members whose content is queued. A core/ModCpMemberManagement extension is one of those tabs. It supplies the tab key and the HTML of the table b
-
The core/Notifications extension is what puts an entry behind the bell in the site header. It does two separate jobs for one type of member-facing notification. First, it declares the switches a member sees under Account Settings → Notification Settings — the rows that let somebody choose whether "somebody replied to content you follow" arrives in the notifications list, as a web push notification, as an email, or not at all. Second, it turns a stored notification row back into something readabl
-
A core/OverviewStatistics extension adds a tile to the statistics dashboards an administrator sees under ACP → Statistics → Key Statistics. There are two such dashboards — "User Activity" (app=core&module=stats&controller=overview) and "Content Activity" (app=core&module=activitystats&controller=overview) — and a third page, "Saved Reports" (app=core&module=overview&controller=mycharts), where an admin can pin individual tiles into a named report and download the whole re
-
When a member with an unfinished profile loads any front-end page, Invision Community shows a small panel above the content: a progress bar, "Next Step: Birthday", and a "Complete my profile" button. Pressing it opens a wizard at app=core&module=system&controller=settings&do=completion that walks the member through the outstanding items one form at a time. An administrator decides what those items are under AdminCP → Members → Profiles → Profile Completion, where each row is a "step"
-
A core/Statistics extension is one graph on the ACP Statistics tab. It is the thing an administrator sees when they open Statistics → Registrations or Statistics → Posts: a chart with a timescale selector, a date range, optional filter tabs, a data table underneath, and a "save this chart" action. Saving is the part that makes the extension worth writing rather than just building a chart inline in your controller. A saved chart is pinned to Statistics → Key Statistics → Saved Reports (app=core&a
-
A core/Build extension is a hook into the developer's "build application" step in the AdminCP. Nobody browsing the community ever triggers it. What they experience is its output: files that are generated at build time and shipped inside the application tar, rather than committed to the repository. Core's only implementation is the reason the ACP code editor has syntax highlighting at all — the CodeMirror sources live under applications/core/dev/codemirror/, the build filter deliberately excludes
-
The core/ContactUs extension controls what happens on the site's Contact Us page. A visitor opens app=core&module=contact&controller=contact, types a message, and presses send. What that page looks like, and what actually happens to the message afterwards, is decided entirely by these extensions. Each one contributes one or more choices to the "Contact Us behavior" radio in the ACP (Settings → Contact Us), contributes the configuration fields that go with those choices, may add fields to
-
A member in Invision Community has one primary group and any number of secondary groups. Every per-group setting therefore has several candidate values at once, and something has to decide which one the member actually gets: five attachments or fifty, a signature or no signature, a thirty-second flood control or none at all. A core/GroupLimits extension is how an application declares that decision for the group columns it owns. It does not add settings and it does not enforce anything — it is a
-
Every AdminCP page has a search box in the header. An administrator types three or more characters into it, an overlay drops down, and down the left of that overlay is a list of areas — Settings, Members, Groups, Clubs, Forums, Downloads Categories, and so on — each with its own result count. Clicking an area shows that area's results as a list of links. A core/LiveSearch extension is one of those areas. It decides whether the area is visible to this administrator, whether it should be the pre-s
-
When an administrator opens a member in the AdminCP — app=core&module=members&controller=members&do=view&id=1 — they see a two-column page of blocks: basic information, groups, warnings, content statistics, and so on. The core/MemberACPProfileTabs extension adds a second (third, fourth) whole page of blocks to that screen, reached by a tab bar across the top. On a stock install with Commerce installed there are exactly two: "Member View" and "Customer View". Your extension is wha
-
The core/MemberExportPersonalInformation extension decides what goes into the XML file a community produces when someone asks for a copy of their personal data. There are two places on a site where that file is produced: a member can request their own copy from Account Settings → Privacy, an administrator approves the request, and the member then downloads it; or an administrator with the member_export_pi restriction can go to a member in the ACP and export the file directly. In both cases the b
-
The core/MFAArea extension adds one tick box to a single ACP form. That form is the ACP screen registered in applications/core/data/acpmenu.json as settings → mfa and labelled "Two Factor Authentication" — app=core&module=settings&controller=mfa — and the field is "Require two factor authentication when". Each MFAArea extension in the suite contributes one option to that list — "Logging into AdminCP", "Changing email address", "Managing stored cards", and so on. When an administrator tic