These integration guides are not official documentation and the Strapi Support Team will not provide assistance with them.
What Is PlanetScale?
PlanetScale is a fully managed, MySQL-compatible serverless database platform built on Vitess, the open-source database clustering system originally developed at YouTube. It runs on locally-attached NVMe SSDs and uses 3-node clusters by default consisting of one primary and two replica nodes, with automated failovers.
Three capabilities set it apart from standard managed MySQL:
- Database branching: Create isolated development copies of your database using Git-like branches, with development branches copying schema only (not data unless using Data Branching®) and supporting safe testing before promoting to production.
- Non-blocking schema changes: ALTER TABLE operations execute without locking tables using Vitess's online schema change techniques and tablet throttler, so your application keeps reading and writing during migrations.
- Horizontal sharding: Scale reads and writes across multiple MySQL instances by explicitly defining sharding keys (Primary Vindexes) through Vitess, with VTGate nodes abstracting query routing. Note: Requires application awareness of sharding strategy and manual configuration of sharding keys.
Important limitation: PlanetScale's Vitess architecture does not support database-level foreign key constraints by default, requiring application-level referential integrity management for Strapi content relationships.
Sign up for the Logbook, Strapi's Monthly newsletter
Why Integrate PlanetScale with Strapi
Strapi v5 gives you a flexible, open-source headless CMS with full control over your content model, API layer, and admin panel. PlanetScale handles the database layer so you don't have to. Here's what the combination offers:
- Zero-downtime schema migrations: PlanetScale's non-blocking schema changes allow you to modify Strapi content structures without locking database tables. Schema changes are applied gradually while your API remains fully responsive, allowing content type modifications in production without downtime.
- Branch-based schema testing: Create a PlanetScale development branch, connect a Strapi instance to it, and test schema changes for content model modifications in complete isolation before deploying to production through PlanetScale's deploy request system.
- Serverless-friendly connection handling: PlanetScale manages connection pooling at the platform level. The @planetscale/database driver uses HTTP/Fetch API architecture, eliminating manual pool tuning that traditional MySQL deployments demand—especially valuable when deploying Strapi to containerized or serverless environments.
- Query performance monitoring: PlanetScale's Insights dashboard surfaces query execution time, slow queries, and connection wait times, which helps when debugging the JOIN-heavy SQL that Strapi generates for nested content relationships.
- Schema version control that complements Git workflows: PlanetScale's deploy request system provides reviewable schema diffs, giving your team the same review process for database changes used for code.
- Horizontal scaling for content-heavy applications: As your Strapi content grows, PlanetScale's Vitess-based architecture enables horizontal scaling through explicit sharding configuration without requiring manual database resharding.
How to Integrate PlanetScale with Strapi
Prerequisites
Before starting, confirm you have the following:
- Node.js 18+ installed locally.
- Strapi v5 project (or you'll create one in Step 1).
- PlanetScale account with an organization created at planetscale.com.
- PlanetScale CLI (
pscale) installed (installation guide). - MySQL 8.0+ compatibility (Strapi v5 dropped MySQL 5.x support).
Step 1: Create a Strapi v5 Project
If you already have a Strapi v5 project, skip to Step 2. Otherwise, scaffold a new one:
npx create-strapi@latest my-planetscale-appDuring the interactive setup, select your preferred language (JavaScript or TypeScript). You can choose the default SQLite database for now since you'll switch to PlanetScale's MySQL in the next steps.
cd my-planetscale-appStep 2: Create a PlanetScale Database
Log in to the PlanetScale CLI and create a database:
pscale auth login
pscale database create my-strapi-db --region us-eastThe --region flag sets your database's primary region. Pick one close to your application servers for lower latency.
Next, generate connection credentials for the main branch:
pscale password create my-strapi-db main production-credentialsThis outputs connection details you need for the next step:
According to PlanetScale's connection string documentation, connection credentials are formatted as:
Host: aws.connect.psdb.cloud
Username: [auto-generated-username]
Password: [generated-password]
Database: [your-database-name]Or as a complete connection string:
mysql://[USERNAME]:[PASSWORD]@aws.connect.psdb.cloud/[DATABASE_NAME]?ssl={"rejectUnauthorized":true}Credentials are created per database branch through the PlanetScale dashboard or CLI using pscale password create <DATABASE_NAME> <BRANCH_NAME> <PASSWORD_NAME>.
Save these values. You won't be able to retrieve the password again after this.
Step 3: Install the MySQL Client
Strapi v5 requires the mysql client library for database connections. You'll need the MySQL client library in your project:
npm install mysqlStep 4: Configure Environment Variables
Store your PlanetScale credentials in the .env file at your project root:
DATABASE_HOST=aws.connect.psdb.cloud
DATABASE_PORT=3306
DATABASE_NAME=my-strapi-db
DATABASE_USERNAME=your-planetscale-username
DATABASE_PASSWORD=your-planetscale-password
DATABASE_SSL=true
DATABASE_SSL_REJECT_UNAUTHORIZED=trueNever commit .env to version control. Verify it's listed in your .gitignore.
Step 5: Configure the Database Connection
Update config/database.js (or config/database.ts) with a PlanetScale-specific configuration:
// config/database.js
module.exports = ({ env }) => ({
connection: {
client: 'mysql',
connection: {
host: env('DATABASE_HOST'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME'),
user: env('DATABASE_USERNAME'),
password: env('DATABASE_PASSWORD'),
ssl: env.bool('DATABASE_SSL', false) && {
rejectUnauthorized: env.bool('DATABASE_SSL_REJECT_UNAUTHORIZED', true),
},
timezone: 'utc',
},
pool: {
min: 0,
max: 10,
acquireTimeoutMillis: 60000,
idleTimeoutMillis: 30000,
createTimeoutMillis: 30000,
},
debug: false,
},
settings: {
forceMigration: true,
runMigrations: true,
},
});A few configuration choices worth noting:
ssl.rejectUnauthorized: true: PlanetScale requires SSL/TLS for all connections. Their certificates are signed by commonly trusted system root CAs, so you can keep verification enabled.pool.min: 0: Essential for containerized and serverless deployments. Unlike Strapi's standard configuration (pool.min: 2), settingmin: 0prevents holding stale connections that triggerECONNRESETerrors when PlanetScale closes idle connections after inactivity.acquireTimeoutMillis: 60000: Generous timeout that accounts for cold starts and network variability when connecting to PlanetScale's distributed infrastructure.
Step 6: Start Strapi and Verify the Connection
Start the development server:
npm run developWhen you start Strapi with a configured database connection, Strapi uses its experimental migration system to initialize the database schema. According to Strapi's database migrations documentation, Strapi runs one-time migration scripts before performing automatic schema synchronization based on your content types. If the connection and migrations succeed, you'll access the Strapi admin panel at the configured application URL.
Open the admin panel and create your first administrator account. At this point, Strapi is reading from and writing to your PlanetScale database.
To confirm directly, open a PlanetScale shell:
pscale shell my-strapi-db mainSHOW TABLES;You should see Strapi's core tables: strapi_core_store_settings, admin_users, admin_permissions, and others.
Step 7: Handle Foreign Key Constraints
This is where PlanetScale's architecture introduces a notable constraint. PlanetScale's Vitess foundation does not enforce foreign key constraints at the database level by default due to its distributed database design. Since Strapi relies on relational content models with foreign key relationships, you have two options: either implement referential integrity at the application layer using Strapi lifecycle hooks or enable foreign key constraint support through PlanetScale's paid plans.
Option A: Enable foreign key support (available in paid PlanetScale plans, with referential integrity at application layer for free plans)
If you need database-level foreign key constraints, PlanetScale supports them on paid plans as an exception to its Vitess-based architecture. However, PlanetScale's default configuration disables foreign key constraints due to its distributed database design. Alternatively, implement referential integrity at the Strapi application layer using lifecycle hooks.
Option B: Manage integrity at the application layer
Since PlanetScale's Vitess architecture does not support foreign key constraints by default, Strapi's lifecycle hooks provide an effective way to enforce referential integrity at the application layer. Strapi already handles most relationship management internally, so for many content-driven applications, this application-level approach works well in practice when database-level foreign key constraints are unavailable.
Project Example: Travel Blog with Next.js and Strapi on PlanetScale
To see this integration in action, let's build a travel blog where Strapi manages the content, PlanetScale handles the database, and Next.js renders the frontend. This pattern works for any content-driven site: portfolios, documentation, editorial platforms, or e-commerce storefronts.
Set Up the Strapi Content Model
With Strapi connected to PlanetScale (from the integration steps above), open the admin panel and navigate to the Content-Type Builder.
Create a Blog Post Collection Type with these fields:
| Field Name | Type | Details |
|---|---|---|
| title | Text | Required, unique |
| slug | UID | Attached to title |
| content | Rich Text | Required |
| excerpt | Text | Short summary, 280 character limit |
| coverImage | Media | Single image |
| destination | Relation | Many-to-one with Destination |
| publishedAt | DateTime | For scheduling |
Create a Destination Collection Type:
| Field Name | Type | Details |
|---|---|---|
| name | Text | Required, unique |
| slug | UID | Attached to name |
| description | Rich Text | Overview of the destination |
| region | Enumeration | Asia, Europe, Americas, Africa, Oceania |
After saving these content types, Strapi automatically generates the corresponding MySQL tables in your PlanetScale database and exposes REST and GraphQL endpoints.
Add Sample Content
Use the Content Manager to create a few destinations and blog posts. Make sure to publish them so they're accessible through the API.
Before fetching content from your frontend, configure the Users & Permissions plugin in Strapi's admin panel. Navigate to Settings → Users & Permissions → Roles → Public, and enable find and findOne permissions for your Blog Post and Destination content types.
Fetch Content from Next.js
In your Next.js project, create a utility for fetching from Strapi's REST API:
// lib/strapi.js
const STRAPI_URL = process.env.NEXT_PUBLIC_STRAPI_URL || 'http://localhost:1337';
export async function fetchAPI(path, params = {}) {
const queryString = new URLSearchParams(params).toString();
const url = `${STRAPI_URL}/api${path}${queryString ? `?${queryString}` : ''}`;
const response = await fetch(url, {
headers: { 'Content-Type': 'application/json' },
next: { revalidate: 60 },
});
if (!response.ok) {
throw new Error(`Strapi API error: ${response.status}`);
}
return response.json();
}Build the blog listing page:
// app/blog/page.js
import { fetchAPI } from '@/lib/strapi';
import Link from 'next/link';
export default async function BlogPage() {
const { data: posts } = await fetchAPI('/blog-posts', {
'populate': 'destination,coverImage',
'sort': 'publishedAt:desc',
});
return (
<main>
<h1>Travel Blog</h1>
<div>
{posts.map((post) => (
<article key={post.id}>
<Link href={`/blog/${post.slug}`}>
<h2>{post.title}</h2>
</Link>
<p>{post.excerpt}</p>
{post.destination && <span>{post.destination.name}</span>}
</article>
))}
</div>
</main>
);
}Fetch individual posts with the filters API:
// app/blog/[slug]/page.js
import { fetchAPI } from '@/lib/strapi';
export default async function BlogPost({ params }) {
const { data: posts } = await fetchAPI('/blog-posts', {
'filters[slug][$eq]': params.slug,
'populate': '*',
});
const post = posts[0];
if (!post) return <div>Post not found</div>;
return (
<article>
<h1>{post.title}</h1>
{post.destination && <p>Destination: {post.destination.name}</p>}
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}Use PlanetScale Branching for Content Model Changes
Here's where PlanetScale's branching really pays off. Suppose you need to add a travelTips Dynamic Zone to your blog posts. Instead of modifying production directly, you can create a development branch, test your schema changes in complete isolation, and then deploy them to production through a deploy request with zero downtime.
According to PlanetScale's non-blocking schema changes, these alterations are applied gradually without locking tables, so your blog remains fully accessible to readers and editors throughout the deployment.
Create an isolated development branch:
pscale branch create my-strapi-db add-travel-tipsUpdate your local .env to point at the development branch:
DATABASE_HOST=127.0.0.1
DATABASE_PORT=3306
DATABASE_SSL=falseThen open a local tunnel to the development branch:
pscale connect my-strapi-db add-travel-tips --port 3306Start Strapi and make your content type changes through the admin panel. Strapi will automatically apply the schema changes to your isolated PlanetScale development branch.
For production deployments with safe migrations enabled, this workflow requires additional coordination: extract the schema changes Strapi applies, create a deploy request in PlanetScale containing those changes, review the deploy request to verify compatibility with non-blocking migrations, and deploy through PlanetScale's deployment system before promoting your Strapi application code.
Test everything on the development branch first, then follow the proper deployment workflow for production changes.
Once you're satisfied with the changes, create and deploy a deploy request:
pscale deploy-request create my-strapi-db add-travel-tipsReview the diff in the PlanetScale dashboard, then deploy:
pscale deploy-request deploy my-strapi-db 1Deploy your updated Strapi code alongside the schema change. PlanetScale's non-blocking schema changes enable deployment without downtime, and your production API continues processing requests throughout the migration.
Production Database Configuration
For production deployment, tighten your configuration:
// config/database.js
module.exports = ({ env }) => ({
connection: {
client: 'mysql',
connection: {
host: env('DATABASE_HOST'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME'),
user: env('DATABASE_USERNAME'),
password: env('DATABASE_PASSWORD'),
ssl: {
rejectUnauthorized: true,
},
timezone: 'utc',
},
pool: {
min: 0,
max: 10,
acquireTimeoutMillis: 60000,
idleTimeoutMillis: 30000,
createTimeoutMillis: 30000,
},
debug: false,
},
settings: {
forceMigration: true,
runMigrations: true,
},
});Use separate PlanetScale passwords for each environment (development, staging, production). According to PlanetScale's connection documentation, you can create multiple credentials per branch, eliminating any need to share passwords across environments.
Strapi Open Office Hours
If you have any questions about Strapi 5 or want to join us, visit Strapi's Discord Open Office Hours, Monday through Friday, from 12:30 pm to 1:30 pm CST (18:30–19:30 UTC): Strapi Discord Open Office Hours.
For more details, visit the Strapi documentation and PlanetScale documentation.
Get Started in Minutes
npx create-strapi-app@latest in your terminal and follow our Quick Start Guide to build your first Strapi project.