linkrobins / birdseye
Forum analytics for Flarum — server-side, zero JavaScript, no cookies. Your stats live in your own database.
Requires
- php: ^8.3
- flarum/core: ^2.0
- maxmind-db/reader: ^1.11
Requires (Dev)
- flarum/phpstan: ^2.0
- flarum/testing: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-04 01:47:22 UTC
README
Forum analytics for Flarum, captured server-side, with zero JavaScript, no cookies, and no external tracker. Your analytics history lives in your own database and stays there.
How it works
The extension records events (page and discussion views, searches, posts, registrations) on the server as requests happen. Visitors are counted with a daily-rotating salted hash: no IP address is ever stored, no cookie is ever set, and nothing runs in your visitors' browsers.
Events sit in a short-lived local buffer (72 hours max). Once a day, the buffer is rolled up into small per-day aggregate rows in your database. That is the permanent history, a few kilobytes per month.
Everything is computed on your own server: pageviews, visitors, bounce rate, session length, top pages and discussions, referrer sources, devices, countries (world map included). There is no account, no license key, and no external service. Nothing ever leaves your forum, and the full picture is free.
What the dashboard shows
- Traffic. Visitors, pageviews, bounce rate and average visit length, with a visitors-per-day chart over 7 or 30 days, plus history by month and by year.
- Content. Top pages, top discussions and top tags, and a "Needs a reply" list of discussions viewed this week that have no replies.
- Audience. Referrer sources, devices, and countries as bars, a pie chart or a zoomable full-screen world map.
- Community. Posts, signups (accounts created) and new members (accounts that confirmed their email), and the search terms people use.
- Today so far. A live strip from the buffer that becomes final when the day closes.
- Export. A CSV export of any card or of every card at once.
It appears on the Birdseye settings page in the admin panel, and in a modal opened from the user menu for groups you grant access.
Beyond the numbers
- Forum health, not just traffic. New members per day, discussions that get read but never answered, top tags, live search terms, and a "today so far" strip straight from the buffer.
- Share the dashboard. A
View forum analyticspermission lets any group you choose open the dashboard from an Analytics entry in their user menu, without admin access. Nobody holds it until you grant it, and administrators always have it. - Weekly digest. Every Monday morning (UTC), administrators get an email summary of last week against the week before: visitors, pageviews, posts, signups, top discussion and top search, sent by your forum's own mailer. It is skipped when the week has no data, and one switch turns it off.
- History by month and year, and a CSV export of every card.
Visitor countries
Birdseye finds a visitor's country on your own server, in one of three ways.
Behind Cloudflare or another proxy that sends a country header: nothing to do. Birdseye reads Cloudflare's CF-IPCountry header by default. For another proxy, enter its header name (for example X-Country from nginx-geoip) in the Trusted country header setting.
Everywhere else (most shared hosting): also nothing to do. Birdseye downloads DB-IP's free country database into Flarum's storage folder and refreshes it once a month. It needs no account or key, and every lookup happens on your server: only the database file is downloaded, and no visitor data is sent anywhere. This needs Flarum's scheduler running (a cron job calling php flarum schedule:run every minute), which Birdseye already uses for its stats. To fetch the database straight away instead of waiting for the next hourly check, run php flarum birdseye:country-database. When it is in use, the dashboard shows the "IP Geolocation by DB-IP" credit its licence (CC BY 4.0) asks for.
If your host blocks outgoing connections from PHP, the download fails and the world map says so, with the error. Ask your host to allow HTTPS to download.db-ip.com, or use your own database file as below. You can also turn the download off with Download a country database automatically.
Your own database file (optional): to use MaxMind's GeoLite2 Country database, or any other MaxMind-format country database, instead:
- Create a free account at maxmind.com and download GeoLite2 Country in the GZIP (
.mmdb) format. - Extract the download. It is a
.tar.gzarchive; the file you need inside it isGeoLite2-Country.mmdb. - Upload that file to your server, ideally outside your public web folder (outside
public_htmlor similar), so it can't be downloaded from your site. For example/home/you/geoip/GeoLite2-Country.mmdb. - Make sure PHP can read it. Permissions of
644usually work. - In Birdseye's settings, enter the full path to the file (starting with
/) in Country database file, keep Country lookup via anonymized IP prefix turned on, and leave Trusted country header empty.
A file set this way always takes the place of the automatic download. MaxMind updates its database regularly; replacing the file now and then keeps countries accurate.
Countries appear from the next day processed onwards. Days already summarised can't be filled in afterwards, because the raw events behind them are deleted once their totals are written.
If something is wrong, the world map says what: the download failing or not having run yet, a path that isn't a full path, a folder instead of the file, no file at that path, a file PHP can't read, the still-compressed download, or a file that isn't a MaxMind database.
Requirements
- Flarum 2.x and PHP 8.3 or newer. Flarum 1.8 forums install the 1.x line of the same package; Composer picks the right one for your forum.
- The scheduler must be running for rollups, the weekly digest and the country database (
php flarum schedule:runvia cron every minute).
Installation
composer require linkrobins/birdseye
Then toggle it on in the admin panel's Extensions page. Stats begin collecting immediately.
Updating
composer update linkrobins/birdseye php flarum cache:clear
Settings
Admin, then Extensions, then Birdseye.
| Setting | Default | What it does |
|---|---|---|
| Collect analytics | On | Pause collection without disabling the extension. Existing stats keep rendering. |
| Weekly email digest | On | The Monday summary email to administrators. |
| Download a country database automatically | On | Keeps DB-IP's free country database on your server, refreshed monthly. Not used when a database file is set. |
| Country database file (optional) | empty | Full path to your own MaxMind-format country database. Always takes the place of the automatic download. |
| Country lookup via anonymized IP prefix | On | Keeps a truncated IP prefix in the 72-hour buffer so countries can be resolved without a proxy header. |
| Trusted country header | CF-IPCountry |
The proxy header carrying each visitor's two-letter country code. Leave empty if you have no such proxy. |
Commands
Birdseye registers three commands on Flarum's scheduler. You never need to run them by hand, but you can:
php flarum birdseye:syncrolls up the buffered events into daily totals and prunes the buffer (hourly).php flarum birdseye:digestsends the weekly digest (Mondays).php flarum birdseye:country-databasedownloads the country database when a new edition is due (checked hourly).
What is counted
Page and discussion views, searches, posts and registrations. Requests from bots and crawlers (by user agent) are not counted. Capture is best-effort and never gets in the way of a request. If the scheduler stops, the dashboard says statistics have not updated since a given date, and whether the scheduler appears to be running; the buffer holds 72 hours, so a longer outage loses the days beyond that.
Privacy notes
- Visitor identity is
hash(secret + date + IP + user agent), truncated; the salt rotates daily so visitors cannot be tracked across days. - Country detection uses a trusted proxy country header when one is configured: Cloudflare's
CF-IPCountryby default, or e.g.X-Countryif nginx-geoip supplies it. The header is trusted as-is, so if your forum is not behind such a proxy, blank the setting (a client talking to your server directly could otherwise forge its country). - Without a header, an anonymized IP prefix (/24 for IPv4, /48 for IPv6, never the full address) is kept in the 72-hour buffer solely for country lookup during processing, then discarded. This can be turned off in settings, and no full IP is written to disk either way.
- The only outgoing request Birdseye makes is the monthly download of DB-IP's country database, which carries no visitor data. Turn it off in settings if you supply countries another way.
- Bounce rate and visit duration are measured log-style (server-side), which runs slightly conservative compared to script-based trackers. That is the cost of shipping no JavaScript at all.