Monthly: From an Internal Reporting Tool to a GA4 Dashboard

When I first wrote about the Monthly Analytics Report Generator, it was mostly a design document. I wanted a way to collect numbers from my projects and turn them into a monthly update without gathering everything manually.

It eventually became an internal tool for that purpose. Now I’ve taken another pass at it, with a different question in mind: what would make this useful to someone other than me?

The result is Monthly, a free, self-hosted GA4 dashboard. You bring your own Google service-account key, add your properties, and compare their performance side by side.

Get Monthly on GitHub.

Less configuration, more dashboard

The original idea focused on generating a report. The latest version also gives you somewhere to explore the numbers before exporting them.

You can manage up to 12 GA4 properties in one workspace. Each project shows pageviews, active users, sessions, and engagement rate, with comparisons against the previous period. There are daily traffic graphs, acquisition-channel charts, and expandable top-page tables.

Choose a 7-, 30-, or 90-day window and an end date. Monthly compares it with the immediately preceding window of the same length, so you can see both the current totals and what changed.

Monthly showing traffic, metrics, and acquisition channels for two fictional projects

This screenshot uses the built-in synthetic demo. These are fictional projects, not real analytics.

Adding properties should not mean filling out forms

One of the more useful changes is property discovery.

After loading your service-account credentials, Monthly can list the GA4 properties that account can access. Select one or several and add them immediately. It retrieves their names and looks up their website URLs where available.

There’s no second review form or extra save step. If a name or URL needs changing, you can edit it afterward. You can still add a property manually using its numeric property ID, and properties without a website URL work too.

Once you’ve added your properties, the discovery list collapses. Connection settings also collapse after successful discovery or reporting. They’re still available when needed, but they don’t need to occupy the screen while you’re reading a report.

Two views, depending on how much you want to see

I spent a fair amount of time making the dashboard smaller.

That sounds like an odd improvement, but large graphs become less useful when they force you to scroll through one project at a time. I wanted to compare projects, not give each chart its own page.

Standard view shows two project cards per row on wide screens. Each includes the main metrics, a daily traffic graph, an acquisition-channel chart, and a short traffic summary. Daily data and top pages stay collapsed until you open them.

Mini view fits four projects per row on wide screens. Each card shows pageviews, the change from the previous period, and one small traffic graph.

Switching views doesn’t fetch the data again. Mini is for scanning across projects; Standard is for looking more closely at one. Both adapt to smaller screens.

The reporting details matter

A dashboard can look convincing while giving misleading answers. Some of the work here was about avoiding that.

Active users are counted across the full reporting period, rather than calculated by adding daily user counts. Someone visiting on several days should not become several users in the period total. Across separate properties, though, users are not deduplicated.

A zero comparison baseline also needs special handling. If a project has activity now but none in the previous period, Monthly shows New instead of inventing a growth percentage.

And a failed request is an error, not zero traffic. One inaccessible property doesn’t hide successful reports for the others, and live failures never get replaced with demo data.

The visual details received attention too: growth is green, declines are red, and unchanged traffic is neutral. A “pageviews are down” message should not look like good news.

Still useful for writing project updates

The dashboard hasn’t replaced the original reporting goal.

You can copy a report as Markdown or download it as Markdown or JSON. Exports contain the full report even when you’re using Mini view, so choosing a compact layout doesn’t remove information from the output.

That keeps the workflow useful for project updates: check the graphs, look into anything unusual, then take the numbers into a post or your own analysis.

Bring your own key – and understand where it goes

Monthly uses a Google service-account key, not a GA4 Measurement Protocol API secret. The latter is for sending events, not reading reports.

You can upload or paste the service-account JSON, or enter its email and private key manually. Project settings are saved in browser local storage. Credential drafts are kept in the tab’s session storage so refreshing doesn’t force you to start over.

There are important limits to that convenience. Session storage is not encrypted storage, and browser restore or duplicated tabs may retain a copy. There’s a Clear & forget credentials action for explicitly removing the saved entry.

Monthly is also server-backed, not browser-only. When you request a report, credentials go to the server hosting the application, which calls Google’s APIs. Run it locally or on a deployment you trust.

This is a self-hosted tool, not a public multi-tenant service with accounts and access controls. If you host it beyond localhost, restrict access and use HTTPS.

A narrower focus than the original plan

The first post described a broader combination of GA4, Google Search Console, and Ahrefs data.

The new dashboard focuses on GA4. You don’t need Search Console access or an Ahrefs subscription to use it. The older reporting API remains available for existing integrations, but it’s separate from this UI.

That narrower scope makes the current tool easier to explain: connect your properties, compare traffic, and export a report.

The bring-your-own-key experience also takes inspiration from Polypost, although Monthly’s Google API requests run on its server.

Try it without connecting an account

You’ll need Node.js 22.14 or later and npm:

git clone https://github.com/saguiitay/monthly-analytics-report.git
cd monthly-analytics-report
npm ci
npm run dev

Open http://localhost:3000 and choose Explore demo. It uses synthetic data and requires no credentials or Google API requests.

For real reports, the README walks through enabling the Google APIs and granting a service account Viewer access to your GA4 properties.

Monthly is now licensed under 0BSD, so you can use, modify, and redistribute the project code – including commercially – without an attribution requirement. Third-party dependencies retain their own licenses.

It’s still a small tool for checking how your projects are doing. It just no longer assumes you’re the person who wrote it.