KeystoneJS is an open-source headless CMS and application framework built on Node.js, Prisma ORM, and GraphQL. It provides a GraphQL API for content delivery and an auto-generated Admin UI. Since KeystoneJS is a backend framework (not a frontend), analytics integration happens in whatever frontend you build to consume its GraphQL API.
Integration Architecture
KeystoneJS is a backend API server:
- Frontend Framework -- You build the frontend (typically Next.js, Remix, or Astro). All tracking scripts load in your frontend code, not in KeystoneJS.
- GraphQL API -- KeystoneJS auto-generates a GraphQL schema from your Keystone schema definition. Query content lists and items to build your data layer.
- Session & Auth -- KeystoneJS handles authentication. You can pass auth context to your data layer (logged-in state, user role) via the GraphQL API or session cookies.
- Hooks & Access Control -- Keystone's
resolveInput,afterOperation, andvalidateInputhooks can trigger server-side analytics events (e.g., logging content creation events to GA4 Measurement Protocol).
Available Integrations
Analytics Platforms
- Frontend gtag.js integration
- GTM-based GA4 with KeystoneJS GraphQL data layer
- Server-side Measurement Protocol via Keystone hooks
Tag Management
- Frontend app shell integration
- SPA route-change tracking
Marketing Pixels
- Via GTM container (recommended)
- Server-side Conversions API via Keystone afterOperation hooks
Next.js + KeystoneJS Example
For a Next.js frontend consuming KeystoneJS's GraphQL API:
// app/posts/[slug]/page.tsx
import { keystoneClient } from '@/lib/keystone';
import { gql } from 'graphql-request';
const QUERY = gql`
query Post($slug: String!) {
post(where: { slug: $slug }) {
title
slug
status
author { name }
tags { name }
category { name }
publishDate
}
}
`;
export default async function Post({ params }) {
const { post } = await keystoneClient.request(QUERY, { slug: params.slug });
return (
<>
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'virtual_pageview',
content_type: 'post',
page_title: ${JSON.stringify(post.title)},
author: ${JSON.stringify(post.author?.name || '')},
category: ${JSON.stringify(post.category?.name || '')},
tags: ${JSON.stringify(post.tags?.map(t => t.name).join(',') || '')},
status: '${post.status}',
publish_date: '${post.publishDate || ''}'
});
`,
}}
/>
<article>{/* render post */}</article>
</>
);
}
Server-Side Analytics via Keystone Hooks
KeystoneJS's hook system enables server-side event tracking. For example, send a GA4 event when content is published:
// keystone.ts (schema definition)
import { list } from '@keystone-6/core';
import { text, timestamp, select } from '@keystone-6/core/fields';
export default config({
lists: {
Post: list({
fields: {
title: text({ validation: { isRequired: true } }),
status: select({ options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Published', value: 'published' },
]}),
},
hooks: {
afterOperation: async ({ operation, item, originalItem }) => {
if (operation === 'update'
&& item.status === 'published'
&& originalItem?.status === 'draft') {
// Send server-side event via GA4 Measurement Protocol
await fetch(`https://www.google-analytics.com/mp/collect?measurement_id=G-XXXX&api_secret=SECRET`, {
method: 'POST',
body: JSON.stringify({
client_id: 'server',
events: [{ name: 'content_published', params: { content_id: item.id, title: item.title } }],
}),
});
}
},
},
}),
},
});
Platform Limitations
No built-in frontend. KeystoneJS is a backend framework. The Admin UI is for content management only and is not a public-facing website. All frontend analytics are your responsibility.
GraphQL schema changes. Modifying your Keystone schema (adding/removing fields, renaming lists) changes the GraphQL API. Data layer queries in your frontend must be updated when the schema changes. Use TypeScript codegen (@graphql-codegen) to catch mismatches at build time.
Session-based auth complexity. KeystoneJS uses session cookies for authentication. If your data layer includes user role or authentication state, ensure you're reading session data correctly across SSR and client-side rendering.
Prisma ORM performance. KeystoneJS uses Prisma for database access. Complex GraphQL queries with many relations can generate slow SQL. Keep data layer queries simple -- request only the fields needed for analytics.
Performance Considerations
- GraphQL efficiency. Request only analytics-relevant fields in your data layer queries. KeystoneJS's auto-generated GraphQL schema includes all fields, but you should project only what you need.
- SSG/ISR. Pre-render pages at build time or via ISR to eliminate runtime GraphQL queries. Data layer values are baked into the HTML.
- Admin UI isolation. The Keystone Admin UI at
/adminshould be excluded from analytics tracking. It is a separate React application not intended for public traffic.
Recommended Integration Priority
- Add GTM to frontend app shell -- Single container for all tracking
- Build GraphQL data layer queries -- Map KeystoneJS lists and fields to data layer variables
- Implement SPA route tracking -- Virtual pageview events on navigation
- Configure GA4 via GTM -- Content groups from KeystoneJS list types
- Add server-side hooks -- GA4 Measurement Protocol for content lifecycle events
Next Steps
For general integration concepts, see the integrations overview.