Skip to main content
RapidDev - Software Development Agency
bolt-ai-integrationsBolt Chat + API Route

How to Integrate Bolt.new with Ghost

Integrate Bolt.new with Ghost CMS using Ghost's Content API for reading posts and the Admin API for creating content. Get a Content API key from Ghost Settings → Integrations, then fetch posts, pages, tags, and authors directly — Ghost's API supports CORS and may work from the client side. Build a headless blog frontend or CMS-powered site in Bolt. Ghost is the cleanest CMS integration available, purpose-built for API-first content delivery.

What you'll learn

  • How to create a Ghost integration and obtain Content API and Admin API keys
  • How to fetch posts, pages, tags, and authors from Ghost's Content API via a Next.js API route
  • How to build a headless blog frontend in Bolt consuming Ghost CMS content
  • How to implement pagination, tag filtering, and search in your Ghost-powered Bolt app
  • How to use Ghost's Admin API to create and update posts programmatically
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner19 min read20 minutesCMSApril 2026RapidDev Engineering Team
TL;DR

Integrate Bolt.new with Ghost CMS using Ghost's Content API for reading posts and the Admin API for creating content. Get a Content API key from Ghost Settings → Integrations, then fetch posts, pages, tags, and authors directly — Ghost's API supports CORS and may work from the client side. Build a headless blog frontend or CMS-powered site in Bolt. Ghost is the cleanest CMS integration available, purpose-built for API-first content delivery.

Build a Headless Ghost CMS Blog Frontend in Bolt.new

Ghost is arguably the best CMS integration available for Bolt-built apps. Unlike WordPress (which was designed as a monolith and retrofitted with a REST API) or Contentful (which requires complex nested queries), Ghost was built API-first from the ground up. Every Ghost instance exposes a clean REST API with predictable response shapes, comprehensive pagination, flexible filtering, and built-in support for tags, authors, tiers, and newsletters. The API is documented thoroughly and the response format is consistent across all endpoint types.

For Bolt developers, Ghost stands out for one more reason: its Content API intentionally allows CORS requests from any origin. This means you can call Ghost's Content API directly from client-side JavaScript in your Bolt app without needing a Next.js API route proxy — a significant simplification compared to most external APIs that require server-side proxying to avoid CORS errors. You still benefit from using an API route (better security, server-side caching, consistent abstraction), but it is optional rather than required for Ghost's read-only Content API.

Ghost can be self-hosted on any Node.js-compatible server (including Railway, Render, or a VPS) or hosted as Ghost(Pro) at ghost.org. Ghost(Pro) plans start at $9/month and include managed hosting, SSL, automatic backups, and a custom domain. For Bolt developers who want a CMS without managing infrastructure, Ghost(Pro) is the simplest path to a working headless CMS. The free tier allows up to one publication with standard features — sufficient for evaluating the integration.

Integration method

Bolt Chat + API Route

Ghost's Content API is deliberately CORS-friendly and may work directly from client-side JavaScript in Bolt's WebContainer, making it one of the simplest CMS integrations available. For the Ghost Admin API (creating and editing content), server-side API routes are required to keep the Admin API key private. Both approaches work in Bolt's WebContainer during development since all communication is standard HTTPS.

Prerequisites

  • A Bolt.new account with a Next.js project
  • A Ghost instance running on Ghost(Pro) at ghost.org or self-hosted on a server with a public URL
  • A Custom Integration created in Ghost Admin → Settings → Integrations
  • The Content API Key (for reading) and optionally the Admin API Key (for writing) from your integration
  • Your Ghost instance URL (e.g., https://your-publication.ghost.io or your custom domain)

Step-by-step guide

1

Create a Ghost Integration and Get API Keys

Ghost's API keys are created through Custom Integrations in Ghost Admin. This is different from most platforms — there is no developer portal or OAuth flow. You create an integration once, and Ghost generates both a Content API key and an Admin API key for that integration. Log in to your Ghost Admin panel at `your-ghost-url/ghost`. Navigate to Settings (gear icon in the bottom-left sidebar) → Integrations → scroll to the 'Custom Integrations' section at the bottom → click 'Add custom integration.' Give it a name (e.g., 'Bolt Dashboard' or 'BoltApp Frontend') and click 'Create.' Ghost immediately generates two keys: The Content API Key is a hex string (like `a9b8c7d6e5f43210abcdef12`). It provides read-only access to your published content. This key is safe to use in client-side JavaScript — it can only read public content, not modify anything or access private drafts. The Admin API Key is formatted as `{keyId}:{secret}` (like `5abb6d...abcdef:3ef56a...`). It provides full read-write access including drafts, members, and administrative operations. This key must remain server-side and should never appear in client-side code or be committed to your repository. Also note your Ghost URL — the base domain of your Ghost instance. For Ghost(Pro), this is typically `https://yoursite.ghost.io` or your custom domain if configured. For self-hosted Ghost, it is whatever domain you pointed at the server. Add three values to your Bolt project's .env file: `GHOST_URL=https://your-ghost-url.ghost.io`, `GHOST_CONTENT_API_KEY=your-content-api-key`, and `GHOST_ADMIN_API_KEY=your-admin-api-key` (if you need write access). The GHOST_URL is also needed as a public variable for client components: `NEXT_PUBLIC_GHOST_URL=https://your-ghost-url.ghost.io`.

Bolt.new Prompt

Add to .env: GHOST_URL=https://your-ghost-url.ghost.io, GHOST_CONTENT_API_KEY=your-content-api-key, GHOST_ADMIN_API_KEY=your-admin-key. Create lib/ghost.ts that exports a ghostFetch helper that calls the Ghost Content API at {GHOST_URL}/ghost/api/content/{endpoint}?key={GHOST_CONTENT_API_KEY}&{params}. Export a ghostPosts function that fetches posts with include=tags,authors, a ghostPost function that fetches a single post by slug, and a ghostTags function that fetches all tags with count.posts. Handle Ghost's error format where errors are in an array under response.errors.

Paste this in Bolt.new chat

lib/ghost.ts
1// lib/ghost.ts
2const GHOST_URL = process.env.GHOST_URL;
3const GHOST_CONTENT_API_KEY = process.env.GHOST_CONTENT_API_KEY;
4
5interface GhostFetchOptions {
6 params?: Record<string, string>;
7}
8
9export async function ghostFetch<T = unknown>(
10 endpoint: string,
11 options: GhostFetchOptions = {}
12): Promise<T> {
13 if (!GHOST_URL || !GHOST_CONTENT_API_KEY) {
14 throw new Error('GHOST_URL and GHOST_CONTENT_API_KEY must be set in .env');
15 }
16
17 const url = new URL(`${GHOST_URL}/ghost/api/content/${endpoint}/`);
18 url.searchParams.set('key', GHOST_CONTENT_API_KEY);
19
20 if (options.params) {
21 Object.entries(options.params).forEach(([k, v]) => url.searchParams.set(k, v));
22 }
23
24 const response = await fetch(url.toString());
25 const data = await response.json() as { errors?: Array<{ message: string; context?: string }> } & T;
26
27 if (!response.ok || data.errors) {
28 const message = data.errors?.[0]?.message || `Ghost API error: ${response.status}`;
29 throw new Error(message);
30 }
31
32 return data;
33}
34
35export interface GhostPost {
36 id: string;
37 uuid: string;
38 title: string;
39 slug: string;
40 html: string;
41 excerpt: string;
42 custom_excerpt: string | null;
43 feature_image: string | null;
44 feature_image_alt: string | null;
45 reading_time: number;
46 published_at: string;
47 updated_at: string;
48 tags: Array<{ id: string; name: string; slug: string; description: string | null }>;
49 authors: Array<{ id: string; name: string; slug: string; profile_image: string | null }>;
50 meta_title: string | null;
51 meta_description: string | null;
52 og_image: string | null;
53 twitter_image: string | null;
54 primary_tag: { id: string; name: string; slug: string } | null;
55 primary_author: { id: string; name: string; slug: string; profile_image: string | null };
56}
57
58export async function ghostPosts(
59 params?: Record<string, string>
60): Promise<{ posts: GhostPost[]; meta: { pagination: { page: number; pages: number; limit: number; total: number } } }> {
61 return ghostFetch('posts', {
62 params: {
63 include: 'tags,authors',
64 fields: 'id,title,slug,html,excerpt,custom_excerpt,feature_image,feature_image_alt,reading_time,published_at,updated_at,primary_tag,primary_author,meta_title,meta_description',
65 ...params,
66 },
67 });
68}
69
70export async function ghostPost(slug: string): Promise<{ posts: GhostPost[] }> {
71 return ghostFetch('posts', {
72 params: {
73 filter: `slug:${slug}`,
74 include: 'tags,authors',
75 limit: '1',
76 },
77 });
78}
79
80export async function ghostTags(): Promise<{ tags: Array<{ id: string; name: string; slug: string; description: string | null; count: { posts: number } }> }> {
81 return ghostFetch('tags', {
82 params: { include: 'count.posts', limit: 'all' },
83 });
84}

Pro tip: Ghost's Content API key is intentionally client-side safe — it can only read published content. If you need to keep your Ghost URL private (for a staging instance or private content), use a Next.js API route as a proxy. Otherwise, you can call Ghost's Content API directly from client components for simpler data fetching.

Expected result: The Ghost API helper is configured. A test call to ghostPosts() should return your published posts with their tags and authors. The ghost URL and Content API key are in .env.

2

Build the Posts API Route

Ghost's Content API is one of the most developer-friendly CMS APIs available. Posts are returned with full HTML content, semantic metadata, author information, and tag relationships in a single request. The API supports powerful filtering, ordering, and pagination built into the query parameters. The posts endpoint is `GET /ghost/api/content/posts/`. Key parameters: `key` (your Content API key, required), `include` (related data to embed: tags, authors, tiers), `fields` (specific fields to return — use this to reduce response size), `limit` (number of posts, default 15, max 15 for Ghost API key tier, 'all' for unlimited), `page` (page number for pagination), `filter` (NQL-based filtering), and `order` (sorting). Ghost uses Nested Query Language (NQL) for filtering, which is more powerful than simple equality matching. Examples: `filter=tag:technology` (posts with a specific tag), `filter=featured:true` (featured posts only), `filter=published_at:>2024-01-01` (posts after a date), `filter=tag:technology+featured:true` (AND condition), `filter=tag:technology,tag:design` (OR condition). This filtering capability enables building dynamic tag pages, featured sections, and date-range archives without additional client-side filtering. For a blog listing page, a good default request includes: `include=tags,authors` (to display tag badges and author names without separate API calls), `fields` limited to non-body fields (exclude the `html` field on listing pages since it can be large), `limit=12` (a page-appropriate number for grid layouts), and `order=published_at desc` (newest first). For individual post pages, fetch the full post with `html` included and render it inside a prose-styled container. Ghost generates clean semantic HTML with proper heading hierarchy, code blocks with language classes, and responsive images. Apply Tailwind's `prose` class from `@tailwindcss/typography` to the container for attractive default text styling without writing custom post CSS.

Bolt.new Prompt

Create a Next.js API route at app/api/ghost/posts/route.ts that fetches Ghost posts using ghostPosts from lib/ghost.ts. Accept query params: page (default 1), limit (default 12), tag (optional slug for filtering), fields (optional comma-separated field list). If tag is provided, add filter=tag:{tag} to the query. Return the posts array and pagination metadata from Ghost's response. Create a second route at app/api/ghost/posts/[slug]/route.ts that fetches a single post by slug using ghostPost and returns it or 404 if not found.

Paste this in Bolt.new chat

app/api/ghost/posts/route.ts
1// app/api/ghost/posts/route.ts
2import { NextResponse } from 'next/server';
3import { ghostPosts } from '@/lib/ghost';
4
5export async function GET(request: Request) {
6 const { searchParams } = new URL(request.url);
7 const page = searchParams.get('page') || '1';
8 const limit = searchParams.get('limit') || '12';
9 const tag = searchParams.get('tag');
10
11 const params: Record<string, string> = {
12 page,
13 limit,
14 order: 'published_at desc',
15 fields: 'id,title,slug,excerpt,custom_excerpt,feature_image,feature_image_alt,reading_time,published_at,primary_tag,primary_author',
16 };
17
18 if (tag) {
19 params.filter = `tag:${tag}`;
20 }
21
22 try {
23 const data = await ghostPosts(params);
24 return NextResponse.json({
25 posts: data.posts,
26 pagination: data.meta.pagination,
27 });
28 } catch (err) {
29 const message = err instanceof Error ? err.message : 'Failed to fetch posts';
30 return NextResponse.json({ error: message }, { status: 500 });
31 }
32}
33
34// app/api/ghost/posts/[slug]/route.ts
35// import { NextResponse } from 'next/server';
36// import { ghostPost } from '@/lib/ghost';
37// export async function GET(_req: Request, { params }: { params: { slug: string } }) {
38// try {
39// const data = await ghostPost(params.slug);
40// if (!data.posts?.[0]) return NextResponse.json({ error: 'Post not found' }, { status: 404 });
41// return NextResponse.json({ post: data.posts[0] });
42// } catch (err) {
43// return NextResponse.json({ error: 'Failed to fetch post' }, { status: 500 });
44// }
45// }

Pro tip: Exclude the 'html' field on listing API calls by specifying explicit fields — a page of 12 posts with full HTML content can be several hundred KB of response data. Fetch the full HTML only for individual post pages where you render the content.

Expected result: The posts API route returns a paginated list of Ghost posts with metadata. Tag filtering works with the ?tag=slug query parameter. Verify with /api/ghost/posts and /api/ghost/posts?tag=your-tag-slug in the browser.

3

Build the Blog Frontend Components

With the Ghost API routes returning structured post data, build the React components that render your blog. A well-designed headless Ghost frontend typically has three main views: a post listing page (home page or /blog), individual post pages, and tag/category pages. For the post listing page, create a PostCard component that renders a post's feature image, title, excerpt, reading time, publish date, primary tag badge, and author avatar. Ghost provides `reading_time` as a pre-calculated integer (minutes), `primary_tag` as the first tag, and `primary_author` as the first author — convenient for card layouts without complex data manipulation. Format dates using JavaScript's `Intl.DateTimeFormat` for locale-aware display. For individual post pages, the most important consideration is rendering Ghost's HTML output safely. Ghost generates clean, well-structured HTML, but Next.js's `dangerouslySetInnerHTML` prop is required to render it. The risk is minimal since the HTML comes from your own Ghost instance (not user input), but follow the practice of wrapping the content in a styled container. Apply Tailwind's prose classes from the `@tailwindcss/typography` plugin for beautiful typographic defaults: `<div className="prose prose-lg max-w-none" dangerouslySetInnerHTML={{ __html: post.html }} />`. Ghost's HTML includes images with responsive `srcset` attributes, code blocks with `language-*` class names for syntax highlighters, blockquotes, and properly nested heading hierarchies. The typography plugin handles all of these correctly. For tag pages, create a `/tag/[slug]` route that fetches the tag details from Ghost's tags endpoint and the filtered posts list. Ghost's tag objects include a `description` (the tag's bio) and `feature_image` which you can use as the tag page header image.

Bolt.new Prompt

Build a Ghost blog frontend: (1) A blog listing page at /blog that fetches from /api/ghost/posts with pagination. Show 12 posts in a responsive grid using PostCard components displaying feature image, title, excerpt (max 150 chars), reading time, publish date, primary tag badge, and author name. Add 'Load more' pagination. (2) A post page at /blog/[slug] that fetches from /api/ghost/posts/[slug] and renders post.html with Tailwind prose classes. Show the post title, feature image, author info, publish date, and tags above the content. (3) A tag page at /tag/[slug] that shows filtered posts. Add next/image optimization for feature images with proper width/height.

Paste this in Bolt.new chat

Pro tip: Install @tailwindcss/typography and add it to your Tailwind config plugins array: require('@tailwindcss/typography'). This single addition makes Ghost's HTML content render beautifully with proper font sizes, line heights, link colors, and code block styling without writing any custom CSS.

Expected result: The blog listing page renders a grid of post cards with feature images, titles, and excerpts. Individual post pages display full HTML content with proper typography. Tag pages show filtered posts for each category.

4

Add Ghost Admin API for Content Creation

Ghost's Admin API allows creating, updating, and deleting posts, pages, tags, and members programmatically. This is useful for building custom editorial workflows, importing content from other sources, or creating a simplified editing interface for non-technical team members. The Admin API uses JWT authentication rather than a simple API key. The Admin API key (format `{keyId}:{secret}`) must be split at the colon, and a JWT token must be constructed with the key ID in the header and the secret used to sign the payload. Ghost's official `@tryghost/admin-api` npm package handles this JWT complexity automatically. For creating a post via the Admin API, send a POST request to `/ghost/api/admin/posts/` with a JSON body containing at minimum `title` and `status` (draft or published). The `html` field accepts HTML content, and the `mobiledoc` field accepts Ghost's native editor format. Posts in draft status are accessible via the Admin API but not through the public Content API. Key considerations for Admin API use: all Admin API requests must be server-side since the API key grants full administrative access to your Ghost instance including member data, billing information, and settings. Never expose the Admin API key to client-side code. Also note that Admin API rate limits are more restrictive than Content API limits — Ghost throttles admin API calls to prevent abuse. For a Bolt app that only needs to display blog content, the Admin API is optional. Include it only if you are building content management features. The vast majority of Ghost integrations only need the Content API.

Bolt.new Prompt

Install @tryghost/admin-api. Create a Next.js API route at app/api/ghost/admin/posts/route.ts with POST handler that accepts { title, html, status, tags } and creates a post in Ghost using the Ghost Admin API client initialized with GHOST_URL and GHOST_ADMIN_API_KEY from process.env. Return the created post's id and url. Also add PATCH handler that accepts { id, ...updateFields } to update an existing post. Validate that status is either 'draft' or 'published'. Never use NEXT_PUBLIC_ prefix for the Admin API key.

Paste this in Bolt.new chat

app/api/ghost/admin/posts/route.ts
1// app/api/ghost/admin/posts/route.ts
2import { NextResponse } from 'next/server';
3// Note: @tryghost/admin-api is a CommonJS package — import with require
4import GhostAdminAPI from '@tryghost/admin-api';
5
6function getAdminClient() {
7 const url = process.env.GHOST_URL;
8 const key = process.env.GHOST_ADMIN_API_KEY;
9
10 if (!url || !key) {
11 throw new Error('GHOST_URL and GHOST_ADMIN_API_KEY must be configured');
12 }
13
14 return new GhostAdminAPI({ url, key, version: 'v5.0' });
15}
16
17export async function POST(request: Request) {
18 const body = await request.json() as {
19 title: string;
20 html?: string;
21 status?: 'draft' | 'published';
22 tags?: Array<{ name: string } | { slug: string }>;
23 };
24
25 const { title, html, status = 'draft', tags } = body;
26
27 if (!title) {
28 return NextResponse.json({ error: 'title is required' }, { status: 400 });
29 }
30
31 if (status !== 'draft' && status !== 'published') {
32 return NextResponse.json({ error: 'status must be draft or published' }, { status: 400 });
33 }
34
35 try {
36 const api = getAdminClient();
37 const post = await api.posts.add(
38 { title, html: html || '', status, tags: tags || [] },
39 { source: 'html' } // tell Ghost the content is HTML, not Mobiledoc
40 );
41
42 return NextResponse.json({
43 id: post.id,
44 url: post.url,
45 title: post.title,
46 status: post.status,
47 slug: post.slug,
48 }, { status: 201 });
49 } catch (err) {
50 const message = err instanceof Error ? err.message : 'Failed to create post';
51 return NextResponse.json({ error: message }, { status: 500 });
52 }
53}

Pro tip: The @tryghost/admin-api package requires the source option when adding posts with HTML content: api.posts.add({ html: content }, { source: 'html' }). Without this option, Ghost treats the content as Mobiledoc (Ghost's native editor format) and the HTML may not render correctly.

Expected result: The Admin API route creates draft posts in Ghost when called with valid title and HTML content. Verify by checking Ghost Admin → Posts after calling the endpoint and seeing the new draft appear with correct content.

5

Deploy and Optimize Ghost Content Delivery

Ghost's Content API works perfectly in Bolt's WebContainer preview since all communication is outbound HTTPS to your Ghost instance. The WebContainer limitation does not affect Ghost integration — there are no incoming webhooks in the basic read-only integration. You can build, test, and verify the entire blog frontend in Bolt's preview before deploying. The Admin API also works in the WebContainer since it is also outbound HTTPS. Test post creation, updating, and deletion in the preview. The only reason to deploy early is if you want to test Ghost's webhook notifications (Ghost sends POST requests to your URL when content is published), which require a publicly accessible server. For deployment, connect Netlify via Settings → Applications → Connect Netlify or click Publish to deploy to Bolt Cloud. Add server-side environment variables: `GHOST_URL` and `GHOST_CONTENT_API_KEY` (both safe without NEXT_PUBLIC_ since they are accessed from API routes, though Content API key is not sensitive), and `GHOST_ADMIN_API_KEY` (server-side only, no NEXT_PUBLIC_). For performance optimization in production, consider Next.js ISR (Incremental Static Regeneration) for blog pages. Ghost webhooks can trigger ISR revalidation when content is published. Configure a revalidation route that Ghost calls via its webhook settings on content update — this approach means pages are statically served until content changes, providing maximum performance without stale content. For a production blog, enable Next.js image optimization by using `next/image` with your Ghost URL in the `domains` config array in `next.config.js`. This enables automatic WebP conversion, responsive sizing, and lazy loading for Ghost's feature images.

Bolt.new Prompt

Add GHOST_URL and GHOST_CONTENT_API_KEY to .env. Create an /api/ghost/revalidate route that accepts POST with a secret token in the header, validates it against GHOST_REVALIDATION_SECRET env var, then calls revalidatePath('/blog') and revalidatePath('/blog/[slug]') to trigger ISR refresh. This route will be called by Ghost webhooks on content updates. Add the ghost URL to next.config.js images domains array for next/image optimization.

Paste this in Bolt.new chat

next.config.js
1// next.config.js addition for Ghost image optimization:
2/** @type {import('next').NextConfig} */
3const nextConfig = {
4 images: {
5 domains: [
6 // Add your Ghost domain(s) here:
7 'your-publication.ghost.io',
8 // Also include Ghost's CDN if using Ghost(Pro):
9 'images.unsplash.com', // Ghost(Pro) uses Unsplash for stock images
10 ],
11 // Alternative for Next.js 12+: use remotePatterns for more control
12 remotePatterns: [
13 {
14 protocol: 'https',
15 hostname: '**.ghost.io',
16 pathname: '/content/images/**',
17 },
18 ],
19 },
20};
21
22module.exports = nextConfig;

Pro tip: Configure Ghost webhooks (Ghost Admin → Settings → Integrations → your integration → Webhooks) to call your /api/ghost/revalidate endpoint when posts are published or updated. This enables instant content refresh in your Bolt app after editing in Ghost's admin panel without a full redeploy.

Expected result: The deployed blog loads Ghost content with optimized images. ISR revalidation works when content is updated in Ghost Admin. The revalidate webhook endpoint responds to Ghost's webhook calls and triggers page refresh within seconds of content updates.

Common use cases

Headless Blog Frontend

Build a fully custom blog frontend in Bolt that reads all content from Ghost CMS. Display a post listing page with pagination, individual post pages with rich HTML content, tag pages for category browsing, and an author pages section. Ghost's API delivers structured data including feature images, custom excerpts, reading time estimates, and SEO metadata.

Bolt.new Prompt

Build a headless Ghost CMS blog. Create a Next.js API route at /api/ghost/posts that fetches posts from Ghost Content API at {GHOST_URL}/ghost/api/content/posts/ with key=GHOST_CONTENT_API_KEY from process.env and fields=id,title,slug,excerpt,feature_image,reading_time,published_at,tags,authors&limit=10&include=tags,authors. Build a React blog listing page at / showing post cards with title, excerpt, feature image, author name, publish date, and tag badges. Create a /posts/[slug] page that fetches a single post and renders the html field. Add pagination with previous/next page links.

Copy this prompt to try it in Bolt.new

Documentation or Knowledge Base Site

Use Ghost as a documentation CMS by organizing content with tags as categories and pages for static documentation sections. Build a Bolt-powered documentation site with a sidebar navigation driven by Ghost tags, full-text search using Ghost's filter API, and code syntax highlighting for technical content.

Bolt.new Prompt

Build a documentation site using Ghost CMS. Create API routes for fetching Ghost pages (for documentation articles) and tags (for navigation categories). Build a layout with a sidebar showing all tags as navigation sections. Each tag links to a filtered post list. Implement search by calling /api/ghost/posts with filter=tag:{tag-slug} and title:~{search-term} params. Fetch content with GHOST_URL and GHOST_CONTENT_API_KEY from process.env. Include syntax highlighting for code blocks in the HTML content using a highlight.js component.

Copy this prompt to try it in Bolt.new

Content Management Admin Panel

Build a custom content editing interface using Ghost's Admin API for creating, updating, and publishing posts. Allow team members to write and publish content through a Bolt-built interface without accessing Ghost's default admin UI, with a custom approval workflow or additional metadata fields.

Bolt.new Prompt

Build a Ghost CMS admin panel. Create Next.js API routes at /api/ghost/admin/posts with POST (create post), PATCH (update post), and DELETE (delete post) methods. Use Ghost Admin API at {GHOST_URL}/ghost/api/admin/posts/ with Authorization header 'Ghost {JWT}' generated from GHOST_ADMIN_API_KEY using the @tryghost/admin-api package. Build a React editor page with title input, a simple textarea for markdown content, tags selector, and Publish / Save Draft buttons. Show a list of recent posts with edit and delete actions.

Copy this prompt to try it in Bolt.new

Troubleshooting

Ghost API returns 401 'Incorrect key credentials' error

Cause: The Content API key is incorrect, expired, or was copied with extra spaces. Ghost also returns this error if the key was generated for a different Ghost instance than the GHOST_URL being called.

Solution: Regenerate the Content API key in Ghost Admin → Settings → Integrations → your integration → click regenerate Content API Key. Verify the GHOST_URL exactly matches your Ghost instance URL including protocol (https://) and without a trailing slash. Copy and paste the key carefully to avoid whitespace.

Posts return but feature_image URLs return 404 when loaded as next/image

Cause: Ghost's feature image URLs point to your Ghost instance's CDN or storage. If next/image is not configured with the Ghost domain in the domains or remotePatterns config, Next.js will block the image optimization and return errors.

Solution: Add your Ghost URL's hostname to the images.remotePatterns array in next.config.js. For Ghost(Pro) sites, include '**.ghost.io' as a hostname pattern. Rebuild the Next.js app after updating next.config.js since this is a build-time configuration change.

typescript
1// next.config.js:
2images: {
3 remotePatterns: [
4 { protocol: 'https', hostname: '**.ghost.io' },
5 { protocol: 'https', hostname: 'your-custom-domain.com' },
6 ],
7},

Ghost HTML content renders without styling — plain text with no typography

Cause: Ghost's HTML output requires CSS to display properly. Without the Tailwind typography plugin (or custom CSS), headings, lists, code blocks, and blockquotes will render unstyled.

Solution: Install @tailwindcss/typography and add require('@tailwindcss/typography') to your tailwind.config.js plugins array. Wrap the dangerouslySetInnerHTML content in a div with className='prose prose-lg max-w-none'. The typography plugin adds all necessary styles for Ghost's HTML output.

typescript
1// In your post page component:
2<div
3 className="prose prose-lg prose-slate max-w-none"
4 dangerouslySetInnerHTML={{ __html: post.html || '' }}
5/>
6// tailwind.config.js:
7plugins: [require('@tailwindcss/typography')],

Best practices

  • Use Ghost's Content API key for all read-only operations — it is intentionally designed to be used client-side and cannot modify content, making it safe to include in browser requests if needed.
  • Exclude the 'html' field from post listing API calls by specifying explicit fields — full post HTML content significantly increases response size for listing pages where only metadata is displayed.
  • Apply Tailwind's typography plugin (@tailwindcss/typography) to the container rendering Ghost's HTML output — it handles all typographic styles for headings, lists, code blocks, and blockquotes without custom CSS.
  • Use Next.js ISR with Ghost webhooks for production blogs — statically generate pages at build time, then revalidate on content updates via Ghost's webhook system for instant refresh without stale content.
  • Store GHOST_ADMIN_API_KEY as a server-side environment variable without NEXT_PUBLIC_ prefix — the Admin API key grants full access to your Ghost instance including member data and billing settings.
  • Implement pagination using Ghost's built-in pagination metadata (meta.pagination.page, pages, total) rather than client-side slicing — Ghost's API handles server-side pagination efficiently.
  • Use Ghost's NQL filter syntax for content queries (filter=tag:technology+featured:true) rather than fetching all posts and filtering client-side — server-side filtering is more efficient and reduces data transfer.
  • Configure Ghost webhooks on content events (post.published, post.updated) to call your ISR revalidation endpoint — this enables instant content updates in your Next.js app without manual cache clearing or redeployment.

Alternatives

Frequently asked questions

Does Bolt.new have a native Ghost CMS integration?

No — Bolt.new does not have a native Ghost connector. However, Ghost is one of the easiest CMS integrations to build since its Content API is intentionally simple and CORS-friendly. A working blog frontend can be built in Bolt from a single descriptive prompt, with the Ghost Content API key being the only credential required for read-only access.

Can I call Ghost's Content API directly from client-side React components without a Next.js API route?

Yes — Ghost's Content API deliberately allows CORS requests from any origin, unlike most external APIs that require server-side proxying. You can use the @tryghost/content-api package or a direct fetch() call with your Ghost URL and Content API key directly in a React client component. For consistency and caching control, using a Next.js API route is still recommended, but the direct approach works for simpler implementations.

Can I use Ghost as a CMS for an existing website that is not a blog?

Yes — Ghost supports 'Pages' (static pages with their own slugs) separate from 'Posts' (blog entries), and custom taxonomies via tags. You can structure any content hierarchy using Ghost's tag system as categories, with posts as content items under each category. Ghost is particularly well-suited for documentation sites, knowledge bases, and portfolio sites in addition to traditional blogs.

What is the difference between Ghost's Content API and Admin API?

The Content API provides read-only access to published content (posts, pages, tags, authors) and is safe to use in browser-side code. The Admin API provides full read-write access including drafts, member data, billing, settings, and the ability to create, update, and delete any content. The Admin API key must be kept server-side and never exposed to browser code.

How do I keep my Bolt app in sync when I update content in Ghost Admin?

Ghost supports webhooks that send POST requests to a URL of your choice when content events occur (post.published, post.updated, post.deleted). Configure a Ghost webhook pointing to your Bolt app's /api/ghost/revalidate endpoint. This endpoint calls Next.js revalidatePath() to invalidate the cached page, triggering a regeneration with fresh content. This approach ensures your Bolt app shows updated content within seconds of publishing in Ghost Admin.

Can the Ghost integration work in Bolt's WebContainer preview without deploying?

Yes — Ghost's API calls are outbound HTTPS requests to your Ghost instance, which work perfectly in Bolt's WebContainer. You can fetch all your blog content, test pagination, tag filtering, and individual post rendering entirely in the WebContainer preview. The only scenario requiring deployment is if you want Ghost webhooks to notify your app of content updates, since webhooks require a publicly accessible URL.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.