Search Console: SEO for all your domains
The Search Console module of MuseDock CMS brings the Google Search Console data of all your sites into a single panel: clicks, impressions, CTR and average position for every domain, with its history, queries, pages, countries and devices. You no longer have to open each property in Google one by one. It is built for platforms with many sites: review the SEO of all your domains at once, filter and compare them, while your customers' domains stay private.
What you can do with the module
- See all your domains together, with filters by domain, colour and status, and the total clicks and impressions of whatever you filtered.
- Open the report of each domain: clicks, impressions, CTR and position against the previous period, a daily chart and tables of queries, pages, countries and devices.
- See SEO opportunities: which pages and searches would earn the most clicks with a small push, and what has dropped or is rising, with a sentence on what to do.
- Download Google's data automatically, with up to 16 months of history the first time and a refresh every few hours afterwards.
- Add domains to Search Console automatically when their DNS is in one of your DNS accounts (Cloudflare), without touching DNS by hand, and retry on its own if Google is slow.
- See the domains in your Search Console that are not CMS sites (WordPress, other servers…) in a separate section called "Other domains".
- Keep customer domains private: each customer connects their own Google account and only they see their data, unless you decide to register them with your account (then they see it without doing anything).
- Give each tenant with a subdomain its own filtered SEO: they only see their own subdomain's, never the main domain's or anyone else's.
- Give each tenant access to their own site's data from their own panel, with specific permissions.
How it works
The module connects to Google with a service account. It is a Google Cloud "robot user" with its own email, like name@project.iam.gserviceaccount.com. It is not your personal Google user, even if you create it from your own account.
We chose a service account instead of OAuth for two reasons:
- It works unattended: it does not depend on someone signing in or on permissions being renewed, so it is suitable for automatic jobs such as the nightly download.
- It does not require verifying an app with Google: unverified OAuth apps have permissions that expire after a few days.
The robot only sees the Search Console properties it has been given access to. The module does three things with that access:
- Reads the properties the robot sees and assigns each one to the matching CMS site, looking at the tenant's main domain and aliases.
- Picks one property per domain. If there are several, a domain property (
sc-domain:example.com) wins over URL-prefix properties (https://www.example.com/), so the same traffic is never counted twice. - Stores metrics per day and per dimension (site, page, query, country and device) exactly as Google provides them. It never adds different levels together: site totals come from the daily total Google publishes, not from summing queries.
Requirements
- A Google account with access to Google Cloud Console (free for this use).
- The Search Console module enabled in the CMS. For tenants, it must also be enabled in each tenant that will use it.
- An encryption key in the CMS
.env(APP_ENCRYPTION_KEYorAPP_KEY). Without it, the module does not store keys. - For automatic domain setup: the domain's DNS must be in one of the accounts configured in Settings → DNS accounts (the CMS's single registry of DNS accounts, shared by Domain Manager, Cloud and this module).
- For automatic downloads: the server cron (
cli/cron-search-console.php, every 15 minutes).
Step-by-step setup
1. Create the service account in Google Cloud
- Open Google Cloud Console and create a project, for example
musedock-gsc. - Enable these two APIs in the project:
- Google Search Console API, to read properties and their metrics.
- Site Verification API, for automatic domain setup.
- Go to IAM & Admin → Service accounts → Create service account. Give it a name and do not assign any role: it needs no permissions on the project.
- Open the account you created, go to Keys → Add key → Create new key → JSON and download the file.
Important: that JSON file is the robot's credential. Do not share it, do not paste it into chats or emails, and delete it from your computer once uploaded.
2. Upload the key to the CMS
- In the superadmin panel open Settings → Search Console (also available under Modules → Search Console and, for each domain, from the "SEO of my domains" button in Tenants).
- Select the JSON file and click Connect.
- The CMS tests the key against Google before saving it. If it is valid, it stores it encrypted and reads the properties the robot can already access. The key is never shown again: the panel only displays the robot's email.
Right after connecting, the robot normally sees no domains yet. The next step is to give it access.
3. Give the robot access to your domains
There are two ways, and you can combine them:
| Method | When to use it | What it does |
|---|---|---|
| Automatic ("Add" or "Add all" button) | The domain's DNS is in one of your DNS accounts | Asks Google for a verification token, publishes a TXT record in the domain's zone, confirms the verification and adds the domain property in Search Console. The robot becomes one more verified owner. |
| Manual | The DNS is outside your DNS accounts, or you prefer not to create records | In Search Console, open the property → Settings → Users and permissions → Add user, paste the robot's email and choose the Full permission. |
Once access is granted, click "Re-read properties" and the domains will appear in the table.
If Google does not yet see the TXT record you just published, the domain stays as "Pending Google". That state is saved: it is not lost if you reload or close the page. The server retries on its own every 5 minutes for 48 hours, and you can also click "Retry now". Google usually takes a few seconds.
For a domain that is not a CMS tenant (for example, a WordPress site), use the "Add a domain that is not in the CMS" form. If its DNS is in one of your accounts, the TXT record is published for you; otherwise the panel shows you the TXT record to put in its DNS, and you click "Add" again.
Automatic data download
Once a property is active, its data is downloaded on its own; you do not have to connect it again or do anything else.
| What | How |
|---|---|
| First download | Up to 16 months of history (the most Google keeps). |
| Afterwards | Each property refreshes about every 12 hours (every 3 hours if the last attempt failed) and the last 4 days are downloaded again, because Google adjusts them. |
| Google's delay | Final data arrives about 2 days late: the report ends on the latest available day, not "today". |
| What is stored | Per day: site totals, countries, devices, pages and queries (clicks, impressions, CTR and position). |
| Who does it | A server cron (cli/cron-search-console.php, every 15 minutes). It also retries pending verifications. |
| By hand | The "Download data now" button (on the overview and on each domain's page) forces the download immediately. A domain's first download can take a minute or two. |
If a download fails, the CMS keeps everything it had: it never deletes data outside the range it is re-downloading. If Google says the robot no longer has access, the property becomes "No access".
The superadmin panel
The main screen shows a table with your domains. For each one you will see:
- the tenant it belongs to, with its colour;
- the property's status;
- clicks, impressions, CTR and average position for the last 28 days;
- the change in clicks against the previous 28 days;
- the date of the last sync.
You can filter by text, tenant colour and status, and the table header sums whatever is filtered. For example, filter by the green colour to see all your film sites together. Click a domain's name to open its report.
Further sections appear below:
- Other Search Console domains: properties the robot sees that are not any CMS site, such as an old domain or a website hosted elsewhere. Only you see them; they are not assigned to any tenant and are not mixed into the main table.
- Own domains without access yet: CMS sites the robot cannot see yet, each with its automatic setup button. Those waiting for Google say so and offer "Retry now".
- Add a domain that is not in the CMS, with the list of those pending verification.
Property statuses
| Status | Meaning |
|---|---|
| Active | The robot has access and the property is assigned to a site. |
| Pending | Setup is half done, usually waiting for Google to recognise the TXT record. The server retries on its own for 48 hours. |
| No access | The robot used to have access and no longer does, for example because someone removed it as a user in Search Console. |
| Error | Google returned an error. The message explains the cause. |
A domain's report
Every active domain has its own SEO page, at /musedock/search-console/domain/ID (and, for each tenant, in their panel). It shows:
- Four indicators: clicks, impressions, CTR and average position, each compared with the previous period of the same length.
- A daily chart of clicks and impressions.
- Queries: what people searched on Google to reach you (the top 50 by clicks).
- Pages that get the most traffic from Google.
- Countries and devices (desktop, mobile, tablet).
You can choose 7, 28, 90, 180 or 365 days. Average position is weighted by impressions, as Google does. The data comes from what has already been downloaded, so the page opens instantly.
SEO opportunities
In each domain's report, the "SEO opportunities" card tells you where there is most to gain. It does not use artificial intelligence: it applies fixed rules to the data Google has already given you, to both queries and pages, ordered by what you could gain.
| Opportunity | When it triggers | What to do |
|---|---|---|
| Low CTR for its position | Position 1–10, but with less than half the clicks that would be normal for that position | Rewrite the title and meta description to make them more appealing. |
| About to take off | Position 4–20 with impressions: on the first two pages, outside the top 3 | Strengthen the page (content, internal links, title) to climb. |
| There is demand, but you are far | Position above 20 with impressions | Create or improve a page designed for that search. |
| Drop | Less than half the clicks (or impressions) of the previous period, or something that used to earn clicks has disappeared | Check whether you changed the page or competitors have overtaken you. |
| Rising | More than double the impressions of before, or it appears for the first time within the top 20 positions | Take advantage by strengthening that page while it climbs. |
How the potential gain is calculated. Your real CTR is compared with the CTR each position usually gets in the industry (28% in 1st, 15% in 2nd, 11% in 3rd… 2% in 10th). It is an approximate reference, not Google data. The "≈ +N clicks" figure is an estimate.
The minimum number of impressions adapts to the size of the site (between 3 and 50), so a small site is not left empty and a large one does not fill up with noise. With as little data as a new site has, few opportunities will appear; they will show up as it receives more searches. Drops and rises are only shown when there is already downloaded history for the previous period to compare with.
A real example: on a small site, the /domain page accumulated 34 impressions at position 60 (there is demand and you are far), and the home page was at position 7.9 with 39 impressions and 3 clicks (about to take off). A legal page at position 5.5 with 13 impressions and no clicks showed up as "Low CTR".
What it cannot detect: whether two of your pages compete for the same search (cannibalization). Google gives queries without their page, so the CMS cannot know.
What Google data it includes… and what it does not
The module downloads the Performance report of Search Console (what Google calls Search Analytics): clicks, impressions, CTR and position, by site, query, page, country and device. The numbers match what you see in Search Console.
These other reports from the Search Console website are not included, because Google does not offer them in its public API (or only for a single URL):
- Links (sites linking to you, most linked pages, anchor text);
- Page indexing, Insights and bulk URL inspection;
- Core Web Vitals, HTTPS and the Experience reports;
- Product snippets, merchant listings and other rich results.
For those reports, keep using the Search Console website. What the CMS gives you is the most consulted part (performance) for all your domains together.
Why queries add up to less than the total: anonymized queries
Google does not reveal the text of searches made by very few people, to protect their privacy. Those searches do count in the click and impression totals, but they do not appear by name in the query list. Google calls them anonymized queries, and you see exactly the same in the Search Console website and in the CMS.
A real example from a small site: in three months it had 100 impressions in total, but the named queries added up to 38. The rest (62%) were anonymized searches. On low-traffic sites this is normal, because almost everything is rare searches (the "long tail"). It is not a bug: the totals are correct; you just cannot know what exactly those people typed.
The same happens with pages: impressions per page can add up to more or less than the site total, because Google counts differently when one search shows several of your pages.
Search Console and the CMS Web Analytics
The CMS has its own analytics (Web Analytics, in the superadmin panel). It is not the same as Search Console and the two complement each other:
| CMS Web Analytics | Search Console | |
|---|---|---|
| What it measures | The visits reaching your site, wherever they come from | How you are found on Google, before the click |
| Data | Visitors, page views, countries, devices, browsers, where they come from | Impressions, clicks, average position and what people searched |
| Who measures it | Your own CMS (needs cookie consent) | |
| What you see | Real traffic, including traffic not coming from Google | Google search only (not Bing or other engines) |
They meet at one point: the "from Google" visits in Web Analytics should be close to Search Console's clicks, but they will not match exactly (rejected cookies, bots, ad blockers, Google's delay…). Use Web Analytics to know how many people arrive and what they do, and Search Console to know what they search and how you appear on Google.
Web Analytics keeps its results for 10 minutes so it loads fast; an "Updated X min ago" button recalculates them immediately.
Own domains and customer domains
In MuseDock CMS every tenant can be marked as a customer domain from the tenant list. The module applies strict rules based on that mark, and only you, as superadmin, can decide to share a customer's data:
| Own domain | Customer, not authorized | Customer authorized by you | |
|---|---|---|---|
| Google account that reads it | The platform's | The customer's own, uploaded from their panel | The platform's (yours) |
| Superadmin can see it | Yes | No, on no screen | Yes, marked as "authorized customer" |
| Tenant can see it | Yes, in their panel | Yes, only with their own account | Yes, without doing anything, and only their own |
| In Tenants and Domain Manager | Green SEO button | "Customer's private SEO" padlock | SEO button |
Authorizing a customer's domain
In Settings → Search Console, the "Customer domains" section lists your customer tenants. For each one you can click "Authorize and add". Your administrator password is required, because you are sharing a customer's data:
- If their domain is a subdomain of one you already added (for example
customer.musedock.com), there is nothing to verify: its filtered property is created (see "Subdomains") and downloaded on the next cycle. - If it is the customer's own domain (
customer.com), it is verified with a TXT record, like yours (its DNS must be in your DNS accounts, or you publish the TXT yourself).
From then on the customer sees their SEO without configuring anything and that data is not deleted. "Remove authorization" (also with your password) deletes that customer's data from the platform: they can connect their own account if they want. If you do not authorize them, the customer keeps the usual path: connecting their own Google account and verifying their domain.
There are also these guarantees:
- The platform account never reads a customer domain you have not authorized. Not even if Google shows it because you are still an owner in Search Console.
- If you mark a tenant as a customer, the data the platform had read from it is deleted, unless you had authorized it. The same happens if a domain from "Other domains" is later added as a customer tenant or as a customer's alias.
- Customer keys are encrypted with a different subkey for each tenant. They cannot be decrypted from the platform's scope or from another tenant's.
- A customer only sees their own domain or subdomain: never another customer's or the main domain's.
- The superadmin cannot enter a tenant's panel by impersonating them. Each panel has its own login.
To be transparent: anyone with root access to the server can technically read the database. What the CMS guarantees is that no superadmin screen, route or query shows the data of a customer who has not authorized it.
Subdomains: each tenant sees only their own
If you sell subdomains of your domains (for example customer.musedock.com), Google lumps them all into the main domain's property. So that each tenant sees only the SEO of their own subdomain, the CMS creates a virtual property for each one:
- Its data is that of the property of the domain covering it, but it is downloaded from Google with a page filter (only that subdomain's URLs). That way clicks, impressions, queries, countries and devices are exact, not an estimate.
- The cron creates it only for your own tenants whose domain is a subdomain of an active property. For a customer it is only created if you authorize them.
- Each tenant only sees their own virtual property; never the main domain's or another tenant's.
- In your table they appear with the subdomain label and are not added twice to the totals, because they are already inside their domain's.
Each tenant's panel
Every tenant with the module enabled has a Search Console section in their menu, at /admin/search-console (or whatever admin path they have configured).
- Own tenant whose domain is already added in the platform account, or that has a subdomain of one that is: sees their data and report without configuring anything (for a subdomain, filtered to their subdomain). If their domain has not been added yet, they see a notice that it is not connected yet.
- Customer tenant authorized by you: sees their SEO without doing anything, only their own, and a notice that the platform manages it.
- Customer tenant not authorized: sees a privacy notice and the form to connect their own service account, with the same steps as this guide. They can also add their domain automatically if the platform manages their DNS. They only see what their own account reads.
Each tenant sees only their own domain: never another tenant's or a customer's.
There are two permissions, assigned by role or by user:
- View Search Console (
search_console.view): see the metrics. - Manage Search Console (
search_console.manage): connect or change the site's Google account and request a download. It includes the view permission.
Activation: the module appears on each tenant's Modules screen and their administrator (with the permission to manage modules and their password) can enable it themselves. Enabling it gives no access to other people's data: they only see their own domain.
Security
- Keys are stored encrypted with AES-256-GCM and are only decrypted in memory to talk to Google.
- The JSON file is validated before it is saved: it must be a service account key and Google must accept it.
- All actions require a session and CSRF protection. Superadmin actions additionally require the superadmin role.
- Disconnecting the account asks for your administrator password in a pop-up, because it deletes the credential and every metric stored with it.
- Customer domains and other tenants' domains are filtered on every query: knowing a property's number is not enough to see its data.
Coming soon
The module is developed in phases. Secure connection, property setup (with retries), automatic data download, the per-domain report, the opportunity detector, customer authorization by the master and per-subdomain properties are already available. Next:
- Letting own tenants also connect their own Google account.
- Downloading query and page together to detect pages competing for the same search.
- Filters by date, query and page, and exportable or emailed reports.
- Alerts when an important opportunity appears or a page drops.
Frequently asked questions
If I create the service account with my own Google user, does it already have access to all my domains?
No. The service account is an independent user and does not inherit your Search Console permissions. You have to give it access with the automatic setup or by adding its email as a user on each property. For example, if you manage mysite.com from your Gmail, the robot gsc@musedock-gsc.iam.gserviceaccount.com will not see it until you add it.
Do I lose my regular Search Console access?
No. You remain an owner of your properties exactly as before. The robot is just one more owner or user, and you can remove it any time from Users and permissions.
My domain already has a Google verification TXT in Cloudflare. What happens to it?
Nothing: it stays as it is. Google gives each owner their own token, so automatic setup adds another TXT record for the robot. A domain can have several verification TXT records at once. For example:
mysite.com. TXT "google-site-verification=AbC…your-token"
mysite.com. TXT "google-site-verification=XyZ…robot-token"
Will I see the traffic Search Console had already collected?
Yes. The data belongs to the property, not to the user: the CMS downloads up to 16 months of history the first time, even if the domain has been in Search Console for years.
If the domain is already in Search Console, why do I have to "add" it again?
Because "being in the CMS" and "having access in Google" are different things. The robot is a new user and starts with access to no domain, not even the ones you already have. "Add" gives it access to that domain (by placing a TXT record to prove it is yours). Your own access does not change.
Can I close or reload the page while a domain is being verified?
Yes. The "Pending Google" state is stored in the database and the server keeps retrying for 48 hours, even if you do not have the panel open.
How often is the data updated?
Each property, roughly every 12 hours. Google publishes final data about 2 days late, so the report ends on that latest available day. If you do not want to wait, use "Download data now".
Why don't I see the "Links" or "Core Web Vitals" reports?
Because Google does not offer them in its public API. Those reports exist only on the Search Console website. The module covers the Performance report (clicks, impressions, position, queries, pages, countries and devices).
Why do queries add up to less than total clicks and impressions?
Because of anonymized queries: Google hides the text of searches made by very few people, although it counts them in the total. The same happens on the Search Console website. More details in the "Anonymized queries" section.
I have domains in Search Console that are not in the CMS. Do they appear?
Yes, in the "Other Search Console domains" section. Only the superadmin sees them and they are not assigned to any tenant. If you later add one of those domains as an own tenant, it moves to the main table by itself. To add a new one, use the "Add a domain that is not in the CMS" form or add the robot's email as a user in Search Console.
What happens if I mark as a customer a tenant that already had data?
The data the platform account had read from that domain is deleted and it disappears from your panel. From then on only the customer, with their own account, can see their SEO. If you mark it as own again, click "Re-read properties" and "Download data now" to get it back, because the history is still in Google.
As superadmin, can I see a customer's SEO?
Not from the CMS. There is no screen, button or route that shows it, and their key is encrypted with a subkey that only works in their own scope.
Which property does it use if I have several for the same domain?
Just one, so traffic is not duplicated. The preference is: domain property (sc-domain:), then https://www., then https:// and lastly http://.
Does it work with CMS subdomains, like mysite.musedock.com?
Yes. A domain property of the main domain covers all its subdomains, and the CMS creates a filtered virtual property for each tenant, so each one sees only the SEO of their own subdomain. See the "Subdomains" section.
If I authorize a customer, do they see the SEO of other subdomains or of the main domain?
No. A tenant only sees their own property, and a subdomain's is filtered by page from the moment it is downloaded from Google: other subdomains' data never even reaches their property.
What happens if I remove a customer's authorization?
That customer's data is deleted from the platform and they stop seeing it from your account. They can connect their own Google account and verify their domain. You need your administrator password.
How does the CMS decide what an opportunity is?
With fixed rules, no AI: it compares each query's and page's position, clicks and impressions with what is usual for that position and with the previous period. The detail is in the "SEO opportunities" section.
What happens if I disconnect the service account?
The CMS deletes the key and all the metrics stored with it. Nothing changes in Google: your properties and their history stay as they are. If you connect a different service account, the previous account's properties are removed from the CMS and read again with the new one. Disconnecting asks for your administrator password.
Troubleshooting
| Message or symptom | Cause and fix |
|---|---|
| "The file is not a service account key" | You uploaded another type of credential, for example an OAuth client. Download the JSON key from Service accounts → Keys. |
| "Google rejected the key: Invalid grant: account not found" | The service account or its key was deleted in Google Cloud. Create a new key and upload it. |
| "Enable Site Verification API and Google Search Console API" | One of the two APIs is not enabled in the Google Cloud project. |
| "The DNS of … is not in any CMS DNS account" | The domain is not in the accounts of Settings → DNS accounts. Add that account there, or publish the TXT record the panel shows in the domain's DNS and click "Add" again. Alternatively, add the robot's email as a user in Search Console. |
| "TXT published in DNS; Google does not see it yet" | Google takes a few seconds to recognise the record. The domain stays as "Pending Google" and the server retries on its own for 48 hours; you can also click "Retry now". |
| A domain shows as "No access" | Someone removed the robot from that property. Add it again and click "Re-read properties". |
| An active domain shows "—" and "no data yet" | It has not been downloaded yet. Click "Download data now" or wait for the next cron cycle. If the domain is new in Search Console, Google may take a few days to have data. |
| Data is not updating | Check that the cli/cron-search-console.php cron is scheduled every 15 minutes and look at the property's last attempt: if it failed, the reason appears on the domain's page. |