What does a content performance dashboard show in Metabase?
A content performance dashboard joins what you published to what it earned: publishing cadence from the CMS, organic clicks and sessions per post from Google Search Console and GA4, top and declining content, engagement, and the conversions content actually drove. The CMS side can come from WordPress, Ghost, Strapi, or Webflow — it is the centerpiece of website analytics in the warehouse.
Which cards belong on a content performance dashboard?
- Posts published by week, by author or category (bar)
- Organic clicks per post, trailing 28 days (table)
- Sessions per post from site analytics (table)
- Top content by clicks and conversions (table)
- Declining content — clicks down vs. the prior period, refresh candidates (table)
- Engagement — comments per post, newsletter clicks per send (table)
- Conversions or member signups attributed to content (bar)
- Clicks by time since publish — cohort curves per publish month (line)
What data does the dashboard need?
- A CMS content table:
wp_posts(WordPress, direct MySQL),webflow_cms_items(Data API sync), or Ghost's posts table — id, title, publish date, status, and slug/URL. gsc_performance_dailyat page grain, and a sessions-per-page daily rollup from GA4 or Plausible.- Engagement and conversion sources as available:
wp_comments, Ghost newsletter stats (ghost_email_stats) and member attribution, or form submissions.
How do you build it?
- Connect the CMS: point Metabase at the WordPress/Ghost/Strapi database directly, or schedule a sync for hosted platforms like Webflow.
- Sync Search Console and analytics rollups to the same warehouse, and build the shared
url_keynormalization view over all three sources. - Model per-post daily performance plus a time-since-publish column, and period-over-period deltas for the declining-content table.
- Assemble the cards with author, category, and publish-cohort filters, and a drill-through from each post to its daily trend.
Example card SQL
WITH content AS (
SELECT
ID AS post_id,
post_title,
post_date::date AS published_on,
lower('yourdomain.com/blog/' || post_name) AS url_key
FROM wp_posts
WHERE post_type = 'post'
AND post_status = 'publish'
),
search AS (
SELECT
lower(
regexp_replace(page, '^https?://|/+$', '', 'g')
) AS url_key,
SUM(clicks) AS organic_clicks_28d
FROM gsc_performance_daily
WHERE stat_date >= CURRENT_DATE - 28
GROUP BY 1
)
SELECT
c.post_title,
c.published_on,
COALESCE(s.organic_clicks_28d, 0) AS organic_clicks_28d
FROM content c
LEFT JOIN search s ON s.url_key = c.url_key
ORDER BY organic_clicks_28d DESC
LIMIT 25;SELECT
date_trunc('week', post_date) AS week,
COUNT(*) AS posts_published
FROM wp_posts
WHERE post_type = 'post'
AND post_status = 'publish'
GROUP BY 1
ORDER BY 1;Related
Metrics
Integrations
Dashboards
FAQ
How do CMS posts get joined to search and analytics data?
url_key expression and apply it everywhere: lowercase, strip the protocol and trailing slash, and drop query strings and fragments (UTM-tagged rows otherwise become phantom pages). The CMS side comes from the post slug or permalink, the search side from Search Console page URLs, the analytics side from GA4 or Plausible page paths. Put the normalization in one warehouse view — three slightly different regexes is the classic way this dashboard silently loses rows.Why do last month's posts look like they underperform?
Which CMS rows should be excluded?
wp_posts holds revisions, drafts, autosaves, attachments, and pages alongside published posts — filter to post_type = 'post' AND post_status = 'publish' or every count on the dashboard inflates. The equivalents elsewhere: is_draft and is_archived flags on Webflow CMS items, post status in Ghost, and published_at IS NOT NULL in Strapi (drafts have no publish date).