Does PlanetScale Work With Contentful?
PlanetScale and Contentful work together, but they solve different problems—use Contentful for content management and PlanetScale for custom application data that needs relational structure.
Quick Facts
How PlanetScale Works With Contentful
PlanetScale and Contentful serve complementary roles in a modern stack rather than directly integrating. Contentful manages your content (blog posts, pages, product descriptions) via REST/GraphQL APIs, while PlanetScale stores application-specific data that requires relational structure—user profiles, orders, comments, or custom metadata. They don't conflict; they occupy different layers. Your frontend or backend queries Contentful for published content and PlanetScale for transactional or relational data. The integration happens at the application layer: your backend (Node.js, Python, etc.) fetches from both APIs and combines the data before sending it to the client. This approach avoids forcing structured relational data into Contentful's document-based model. The developer experience is straightforward—initialize both clients in your backend and compose queries as needed. One architecture consideration: if you're building a Jamstack site with Contentful, you might not need PlanetScale at all unless you have user interactions, forms, or real-time data requirements. For commerce or SaaS apps layering Contentful's marketing content on top of transactional data, this combo is natural.
Best Use Cases
Fetching Content and App Data
npm install contentful mysql2 dotenvimport { createClient } from 'contentful';
import mysql from 'mysql2/promise';
const contentful = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
});
const pool = mysql.createPool({
host: process.env.PLANETSCALE_HOST,
user: process.env.PLANETSCALE_USER,
password: process.env.PLANETSCALE_PASSWORD,
database: process.env.PLANETSCALE_DB,
waitForConnections: true,
});
export async function getProductPage(productId: string) {
const [content, userOrders] = await Promise.all([
contentful.getEntries({ content_type: 'product', 'fields.id': productId }),
pool.query('SELECT * FROM orders WHERE product_id = ?', [productId]),
]);
return {
content: content.items[0],
salesCount: userOrders[0].length,
};
}Known Issues & Gotchas
No native Contentful webhooks to PlanetScale sync—you must build this yourself
Fix: Use Contentful webhooks to trigger your backend endpoint, which then writes denormalized data to PlanetScale if you need fast reads without API calls
Contentful has rate limits (4 requests/second on free tier); querying both APIs sequentially can slow responses
Fix: Implement caching (Redis) for Contentful queries and batch your requests. Use GraphQL federation if combining both sources client-side
PlanetScale branching is powerful but irrelevant to Contentful workflows—confusion about where to version content
Fix: Remember: version content in Contentful's draft/publish system; use PlanetScale branches only for schema changes in your app data layer
Alternatives
- •Supabase + Contentful: PostgreSQL alternative with built-in auth, better for user-centric apps
- •Strapi + PlanetScale: Self-hosted headless CMS with PlanetScale backend for full relational control
- •Contentful + Firebase: Combine Contentful content with Firebase Realtime Database for simpler real-time interactions
Resources
Related Compatibility Guides
Explore more compatibility guides