Analytics Ops
Google Analytics 4 configuration as code and reporting for Ruby and Rails.
Analytics Ops gives you a review-first way to inspect GA4, detect drift, run useful reports, and apply a small set of safe configuration changes. The core is plain Ruby; Rails support is optional.
Three-command start
You need Ruby 3.2 or newer, access to a GA4 property, and a Google service-account JSON key kept outside the repository.
gem install analytics_ops
analytics-ops setup --service-account /absolute/path/to/service-account.json
analytics-ops overview
setup lists the properties the service account can access, lets you choose
one, and creates the smallest valid config/analytics_ops.yml for the default
production profile. It securely remembers the connection, so every later
command works without another authentication option. You do not need
gcloud, browser authorization, a property ID, or hand-written YAML.
overview makes one bounded batch request and shows totals, a daily trend,
traffic acquisition, landing pages, and devices for the previous 28 complete
days. Both commands are read-only in Google Analytics.
After setup, everyday use stays short:
analytics-ops report traffic --last 7
analytics-ops overview --compare
analytics-ops portfolio
analytics-ops health
--last, --from, --to, and --compare make date changes easy.
portfolio shows users, sessions, and key events across every configured
property. health checks traffic, expected events, attribution quality,
quota, and configuration drift without changing GA4.
Google Analytics setup (first time only)
The setup is easier when you keep these four separate things in mind:
| Item | What it does |
|---|---|
| Human Google Account | The account you use to sign in to Google Analytics and Google Cloud |
| GA4 account and property | Holds your website's analytics configuration and collected data |
| Google Cloud project | Owns API access, quota, and the service account; it does not hold your GA4 report data |
| Service account | The non-human identity Analytics Ops uses to call Google's APIs |
A. Create GA4 only if you do not already have it
Already have a working GA4 property collecting website data? Skip to Create the Google Cloud project.
- Sign in at Google Analytics.
- Click Start Measuring, or open Admin → Create → Account.
- Create an Analytics account.
- Create a GA4 property and choose its reporting time zone and currency.
- Create a Web data stream using your website URL.
- Install the resulting Google tag or Measurement ID on your website.
Analytics Ops reads and manages an existing GA4 property. It does not install the website tracking tag or send browser events.
B. Create the Google Cloud project
- Sign in at Google Cloud.
- Open the project selector and choose New Project.
- Use a clear name such as Analytics Ops.
- Choose No organization if you use a personal account. That is fine.
- Create the project and make sure it is selected before continuing.
Google requires this separate project for API access, quota, and the service account identity. Creating it does not create another GA4 account or move your analytics data.
C. Enable the two required APIs
In the selected Cloud project, open APIs & Services → Library and enable exactly:
- Google Analytics Admin API
- Google Analytics Data API
You do not need the Gemini API.
D. Create the service account
- Open IAM & Admin → Service Accounts.
- Click Create service account.
- Use a name such as Analytics Ops Local.
- Continue past the optional Cloud permissions step without assigning Owner, Editor, or another broad Cloud IAM role.
- Finish creating the service account.
- Copy its email address. It resembles:
analytics-ops@example-project.iam.gserviceaccount.com.
Google Cloud IAM Editor and GA4 Editor are different roles. The service account does not need the Cloud IAM Editor role.
E. Download and protect the JSON key
- Open the service account you just created.
- Open Keys.
- Choose Add key → Create new key → JSON.
- Download the key.
- Move it to a stable location outside every application repository.
- On macOS or Linux, protect it with mode
0600:
mkdir -p ~/.config/analytics_ops
chmod 700 ~/.config/analytics_ops
mv /path/to/downloaded-key.json \
~/.config/analytics_ops/service-account.json
chmod 600 ~/.config/analytics_ops/service-account.json
Protect this file like a password:
- Never commit it.
- Never paste its contents into chat, logs, screenshots, issues, or environment variables.
- Do not put it inside
config/analytics_ops.yml. - If it is exposed, revoke the key in Google Cloud and create a replacement.
F. Give the service account GA4 access
- Return to Google Analytics.
- Select the correct GA4 account and property.
- Open Admin.
- Choose Property access management for one property, or Account access management for every property in that GA4 account.
- Click + → Add users.
- Paste the service-account email.
- Disable Notify new users by email. A service account has no human inbox.
- Select Viewer for reports, discovery, snapshots, doctor, and audit.
Select Editor only if you plan to use reviewed
applyoperations later. - Do not grant Administrator. Analytics Ops does not manage GA4 users.
- Click Add.
Your human Google Account must already have permission to manage users in that GA4 account or property.
G. Install and connect Analytics Ops
For a standalone installation:
gem install analytics_ops
analytics-ops setup \
--service-account ~/.config/analytics_ops/service-account.json
For an application that uses Bundler, add:
# Gemfile
gem "analytics_ops", "~> 0.3", group: :development
Then run:
bundle install
bundle exec analytics-ops setup \
--service-account ~/.config/analytics_ops/service-account.json
Setup:
- validates the key
- verifies both Analytics APIs
- lists the GA4 properties the service account can access
- lets you choose a property
- creates
config/analytics_ops.yml - remembers named connections and profile choices, using only key paths, in
~/.config/analytics_ops/connection.json
H. Verify the connection with read-only commands
bundle exec analytics-ops doctor
bundle exec analytics-ops properties
bundle exec analytics-ops profiles
bundle exec analytics-ops overview
bundle exec analytics-ops portfolio
bundle exec analytics-ops report traffic --json
bundle exec analytics-ops realtime
bundle exec analytics-ops audit
Empty reports are normal for a new property. These checks are read-only in GA4. Setup also makes no GA4 changes; it writes only the local configuration and connection pointer.
audit exits with status 0 when configuration matches and status 2 when
it finds drift. Both are successful read-only results.
You do not need gcloud, a browser OAuth login, an OAuth consent screen, an
OAuth test-user list, or Gemini. An API key cannot authorize access to private
GA4 data.
apply is the mutating command. Do not run it until you have created and
reviewed a saved plan.
I. Quick troubleshooting
- No properties listed: Add the exact service-account email to the correct GA4 account or property.
- API disabled: Select the correct Cloud project and confirm both Analytics APIs are enabled.
- Permission denied: Grant Viewer or Editor inside GA4. A Google Cloud IAM role does not grant access to GA4 data.
- “This app is blocked” opens in a browser: That is an obsolete OAuth flow. Analytics Ops uses a service-account JSON key and does not launch browser authorization.
- Key unavailable: Run setup again with the key's new absolute path.
- Key creation disabled: An organization policy may prohibit downloaded service-account keys. Ask the Google Workspace or Cloud administrator.
Google's official guides cover GA4 account and property creation, Cloud project creation, API enablement, service-account creation, JSON key creation, and GA4 access management.
See the live read-only smoke-test guide for a safe real-app release test.
Already connected? List properties before creating configuration:
analytics-ops properties
analytics-ops discover # includes data streams
More than one property or client
Setup can safely add another profile to the same configuration:
analytics-ops setup \
--profile client_b \
--connection client_b \
--service-account /secure/client-b/service-account.json
analytics-ops profiles
analytics-ops connections
analytics-ops use client_b
analytics-ops overview
The selected profile and its named Google connection are remembered for this
configuration. Nothing secret enters the YAML. Use analytics-ops portfolio
for one read-only totals table across all configured properties, or:
analytics-ops portfolio report traffic
analytics-ops portfolio health
Those commands isolate per-property failures and use bounded concurrency. Optional profile labels make client and brand results easier to identify.
Custom reports without Ruby
Discover valid property-specific fields, then run a report:
analytics-ops reports
analytics-ops fields event
analytics-ops report custom \
--dimension eventName \
--metric eventCount \
--where eventName=purchase \
--last 7
Saved report recipes can include nested filters and ordering in
config/analytics_ops.yml. Large exports use explicit bounded pagination:
analytics-ops report traffic \
--all \
--max-rows 100000 \
--output tmp/traffic.csv
See Reports and Analytics health.
Ask ChatGPT, Codex, or Claude
Analytics Ops includes a local MCP server with read-only tools only:
analytics-ops mcp --config /absolute/path/to/config/analytics_ops.yml
It can list properties and report fields, audit configuration and governance,
check health, and read overviews, portfolios, saved or custom reports,
realtime events, and explicitly configured BigQuery/funnel results. It has no
plan, apply, create, update, or delete tool. See
ChatGPT, Codex, and Claude for the copy-and-paste
connection commands and the data-sharing boundary.
The safe change workflow
configuration → audit → saved plan → review → apply → verify
# Read-only: save a deterministic plan with mode 0600
bundle exec analytics-ops plan --output tmp/ga4-plan.json
# Review the JSON before doing anything
less tmp/ga4-plan.json
# Mutating: prints the exact saved operations and asks you to type yes
bundle exec analytics-ops apply tmp/ga4-plan.json
# Read-only: confirm managed settings now match
bundle exec analytics-ops verify
apply refreshes the remote snapshot, rejects stale plans, and executes only
the operations in the saved file. Ordinary plans never delete or archive
anything. If one operation fails, execution stops and reports what succeeded,
what failed, and what remains.
Common commands
| Command | Purpose | Remote writes? |
|---|---|---|
analytics-ops setup --service-account PATH |
Connect, choose a property, and create minimal configuration | No |
analytics-ops connections |
List saved connection names without exposing key paths | No |
analytics-ops profiles |
List configured properties and their connections | No |
analytics-ops use NAME |
Select a profile and connection locally | No |
analytics-ops properties |
List accessible accounts and properties without configuration | No |
analytics-ops doctor |
Check configuration, credentials, APIs, property access, clients, and clock | No |
analytics-ops discover |
List accessible accounts, properties, and streams without configuration | No |
analytics-ops overview |
Show five useful reports in one bounded batch | No |
analytics-ops portfolio |
Compare totals across every configured property | No |
analytics-ops portfolio report NAME |
Run one report across configured properties | No |
analytics-ops portfolio health |
Check every configured property with isolated failures | No |
analytics-ops health |
Check traffic, events, attribution quality, quota, and drift | No |
analytics-ops snapshot |
Print normalized managed remote state | No |
analytics-ops audit |
Show drift without writing a plan file | No |
analytics-ops plan --output FILE |
Review and save deterministic changes | No |
analytics-ops apply FILE |
Apply one reviewed, non-stale plan | Yes |
analytics-ops verify |
Check convergence | No |
analytics-ops report NAME |
Run a built-in standard report | No |
analytics-ops reports |
List built-in and saved report recipes | No |
analytics-ops fields SEARCH |
Find property-aware dimensions and metrics | No |
analytics-ops compatibility NAME |
Check whether a report's fields work together | No |
analytics-ops realtime |
Run the realtime-events report | No |
analytics-ops governance |
Audit broader Alpha GA4 settings | No |
analytics-ops changes |
Read de-identified recent configuration changes | No |
analytics-ops access |
Read aggregate Data API access-quota usage | No |
analytics-ops raw NAME |
Run an optional fixed, cost-capped BigQuery recipe | No |
analytics-ops funnel NAME |
Run an optional aggregate ordered funnel | No |
analytics-ops mcp |
Start the strictly read-only local AI connection | No |
analytics-ops schema --format json |
Print the configuration schema | No |
Use --json or --format json for automation. CSV is available for report
results, portfolio reports, and optional BigQuery/funnel results:
bundle exec analytics-ops report landing-pages --csv
Friendly traffic and landing-pages aliases leave the original
traffic_acquisition and landing_pages names fully supported.
Choose report dates without editing Ruby:
analytics-ops report traffic --last 7
analytics-ops report traffic --from 2026-07-01 --to 2026-07-07
analytics-ops overview --last 30 --compare
See Commands for every option and exit status.
IDs: the quick distinction
Use fake values like these in examples and tests:
| Value | Example | Where it belongs |
|---|---|---|
| Account ID | 100000001 |
Discovery output only |
| Property ID | 123456789 |
property_id in configuration and API requests |
| Stream ID | 987654321 |
stream_id in configuration |
| Measurement ID | G-EXAMPLE1 |
Browser tagging; never use it as a stream ID |
| Cloud project | example-analytics-project |
Owns enabled APIs, quota, and the service account |
| Service-account identity | ga-reader@example-project.iam.gserviceaccount.com |
Granted GA4 access; its key is never this YAML |
Ruby API
workspace = AnalyticsOps::Workspace.load(
"config/analytics_ops.yml",
profile: "production"
)
plan = workspace.plan
plan.write("tmp/ga4-plan.json")
report = workspace.report("calculator_completions")
report.rows.each { |row| puts row.fetch("eventCount") }
ranges = AnalyticsOps::Reports::Period.resolve(last_days: 7, compare: true)
comparison = workspace.report("traffic", date_ranges: ranges)
overview = workspace.overview
puts overview.report("overview_totals").rows
health = workspace.health
puts health.findings.map(&:message)
Loading the gem, loading YAML, and booting Rails do not contact Google. Network calls happen only when a workspace operation is invoked.
Rails
# Gemfile
gem "analytics_ops", require: "analytics_ops/rails", group: :development
bin/rails generate analytics_ops:install
bundle exec analytics-ops setup \
--service-account /absolute/path/to/service-account.json
bin/rake analytics:doctor
bin/rake analytics:overview
bin/rake analytics:portfolio
bin/rake analytics:health
bin/rake analytics:governance
bin/rake 'analytics:report[traffic_acquisition]'
The integration is a Railtie, not an Engine. It adds a generator and operator Rake tasks—no models, migrations, routes, controllers, views, JavaScript, or boot-time network calls. See Rails integration.
What Analytics Ops does not do
- It does not inject browser analytics or manage consent banners.
- It stores only named service-account key paths in a mode-
0600user file; it never copies keys, tokens, or report rows. - It does not delete accounts, properties, streams, or unmanaged resources.
- It does not claim to manage settings that Google exposes only in the UI.
- Experimental governance features are read-only and never enter ordinary plans or apply.
- Optional BigQuery support runs only fixed
SELECTrecipes behind a required dry run and byte cap; it does not accept arbitrary SQL.
Documentation
| Guide | Topic |
|---|---|
| Authentication | One-time service-account setup and key safety |
| Live smoke test | Real-app read-only verification and release gate |
| Configuration | Complete strict YAML contract |
| Commands | CLI syntax, formats, and exit statuses |
| Reports | Built-in, saved, custom, and exported reports |
| Analytics health | Deterministic traffic and data-quality checks |
| Governance | Experimental read-only Admin checks |
| BigQuery and funnels | Optional cost-capped raw-data recipes |
| ChatGPT, Codex, and Claude | Strictly read-only local AI connection |
| Rails | Generator and Rake tasks |
| Safety | Plans, stale-state protection, rollback, and credentials |
| Plan format | Version-1 JSON contract |
| API support | Exactly what is managed, manual, or unsupported |
| Client compatibility | Tested official Google gem versions |
| Troubleshooting | Common failures and fixes |
| Architecture | Boundaries and data flow |
For development, run:
bin/setup
bin/check
Security issues belong in private vulnerability reporting; see SECURITY.md. Contributions follow CONTRIBUTING.md.
Analytics Ops is MIT licensed and is not affiliated with, sponsored by, or endorsed by Google LLC. Google Analytics is a trademark of Google LLC and is named only to describe API compatibility.