Connect your Lovable app to Ghost's Content API by creating a Supabase Edge Function that authenticates with a Ghost API key and fetches posts, pages, tags, and authors. Ghost is a focused publishing platform optimized for blogs and newsletters, unlike WordPress which is a general-purpose CMS. Ghost's Content API is simpler and better documented than WordPress REST API for pure publishing use cases.
Build a Ghost-powered blog or newsletter site with Lovable
Ghost is purpose-built for publishing. Where WordPress is a general-purpose CMS that can do almost anything with enough plugins, Ghost is specifically designed for blogs, newsletters, and content memberships. This focus produces a significantly cleaner API: Ghost's Content API has fewer endpoints, consistent response formats, and built-in support for the publishing primitives that matter most — authors, tags, primary authors, reading time, feature images, membership tiers, and newsletter subscription status.
Ghost's Content API is the read-only public API for fetching published content. It accepts a key as a query parameter rather than requiring request signing or OAuth flows, making it straightforward to call from a Supabase Edge Function. The key is a Content API key tied to your Ghost installation — it only grants read access to public content and cannot modify anything. For writing content (creating posts, updating members, sending newsletters), the Ghost Admin API requires more complex JWT authentication.
Compared to WordPress as a headless CMS, Ghost is cleaner and faster to integrate but more limited in flexibility. WordPress supports arbitrary custom post types, complex taxonomies, ACF custom fields, and thousands of plugins that extend the API. Ghost supports posts, pages, tags, and authors — that is the entire content model. For a blog, newsletter, or content publication where that model is sufficient, Ghost's simplicity is a significant advantage. For a complex application requiring custom data types, WordPress or Contentful is more appropriate.
Integration method
Ghost integrates with Lovable through a Supabase Edge Function that calls Ghost's Content API with a Content API key as a query parameter. The Ghost site URL and Content API key are stored in Cloud → Secrets. The Edge Function fetches posts, pages, tags, or authors and returns the structured JSON. Ghost's Admin API (for creating or updating content) also follows this pattern but uses a different key and requires HMAC JWT signing.
Prerequisites
- A Ghost installation — self-hosted (ghost.org/docs/install/) or Ghost Pro managed hosting
- A Ghost Content API key from Ghost Admin → Settings → Integrations → Add custom integration
- Your Ghost site URL (e.g., https://yourblog.ghost.io or https://blog.yoursite.com)
- A Lovable account with an active Lovable Cloud project
Step-by-step guide
Create a Ghost integration and get the Content API key
Create a Ghost integration and get the Content API key
Ghost uses integration-specific API keys rather than user account keys. Creating a custom integration in Ghost Admin generates a pair of keys — a Content API key (read-only, public content) and an Admin API key (read-write, requires JWT signing). To get a Content API key: Log in to your Ghost Admin panel (at yourdomain.com/ghost). Go to Settings (gear icon) → Integrations. Scroll to the bottom of the Integrations page and click 'Add custom integration'. Give it a name like 'Lovable App'. Ghost creates the integration immediately and displays: 1. Content API Key — a hex string like 9fcfdb476936c3e8d74b0f6b8 (what you need for reading content) 2. Admin API Key — for writing content (not needed for a read-only integration) 3. API URL — your Ghost site URL in the format needed for API calls In Lovable, click '+' next to Preview to open the Cloud panel, then click 'Secrets'. Add: - GHOST_URL — your Ghost site URL (e.g., https://yourblog.ghost.io) without trailing slash - GHOST_CONTENT_API_KEY — your Content API key from the integration Ghost Content API keys are safe to use in server-side code and do not provide write access. However, they must still go in Cloud → Secrets (not frontend code) because Lovable routes all external API calls through Edge Functions to prevent CORS issues.
Pro tip: Test your Ghost Content API key directly in a browser: visit {GHOST_URL}/ghost/api/content/posts/?key={CONTENT_API_KEY}&limit=3 — you should see a JSON array of your three most recent posts. This confirms the key is valid before writing any Edge Function code.
Expected result: GHOST_URL and GHOST_CONTENT_API_KEY are stored in Cloud → Secrets. A direct browser test of the Ghost Content API URL returns published posts as JSON.
Create the Ghost Content API proxy Edge Function
Create the Ghost Content API proxy Edge Function
Ghost's Content API is simpler than most CMS APIs to call — authentication is just a key query parameter appended to every request. The Edge Function builds the Ghost API URL by combining the GHOST_URL, the content type path, and the API key, then returns the response. Ghost Content API endpoints: - Posts: /ghost/api/content/posts/ - Pages: /ghost/api/content/pages/ - Tags: /ghost/api/content/tags/ - Authors: /ghost/api/content/authors/ - Tiers: /ghost/api/content/tiers/ - Single by slug: /ghost/api/content/posts/slug/{slug}/ Key query parameters: - key={api_key} — required for all requests - include=authors,tags — embed related resources in response - filter=primary_tag:{slug} — filter by tag - filter=authors.slug:{slug} — filter by author - limit={n} — max items (default 15, max 100) - page={n} — pagination page number - fields=id,title,slug,excerpt — select only specific fields - formats=html,plaintext — include HTML or plaintext content fields The Edge Function below accepts a 'path' parameter (just the query string parameters, without the base URL and key) and constructs the full authenticated URL, keeping the API key server-side.
Create a Supabase Edge Function at supabase/functions/ghost-proxy/index.ts that calls Ghost's Content API. Read GHOST_URL and GHOST_CONTENT_API_KEY from Deno.env.get(). Accept a POST request with 'resource' string (posts, pages, tags, authors), optional 'slug' string, and optional 'params' object of query parameters. Build the Ghost API URL with the resource path and API key. If slug is provided, fetch /ghost/api/content/{resource}/slug/{slug}/. Return the Ghost response data array (or single item for slug requests) with pagination meta. Include CORS headers.
Paste this in Lovable chat
1// supabase/functions/ghost-proxy/index.ts2const corsHeaders = {3 'Access-Control-Allow-Origin': '*',4 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',5};67Deno.serve(async (req) => {8 if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders });910 try {11 const { resource, slug, params = {} } = await req.json() as {12 resource: string; slug?: string; params?: Record<string, string>;13 };1415 const allowedResources = ['posts', 'pages', 'tags', 'authors', 'tiers'];16 if (!allowedResources.includes(resource)) {17 return new Response(JSON.stringify({ error: `resource must be one of: ${allowedResources.join(', ')}` }), {18 status: 400,19 headers: { ...corsHeaders, 'Content-Type': 'application/json' },20 });21 }2223 const ghostUrl = Deno.env.get('GHOST_URL')!.replace(/\/$/, '');24 const apiKey = Deno.env.get('GHOST_CONTENT_API_KEY')!;2526 let apiPath = `/ghost/api/content/${resource}/`;27 if (slug) apiPath += `slug/${encodeURIComponent(slug)}/`;2829 const queryParams = new URLSearchParams({ ...params, key: apiKey });30 const url = `${ghostUrl}${apiPath}?${queryParams.toString()}`;3132 const ghostResponse = await fetch(url);3334 if (!ghostResponse.ok) {35 console.error(`Ghost API error ${ghostResponse.status} for ${resource}${slug ? '/' + slug : ''}`);36 return new Response(JSON.stringify({ error: `Ghost API returned ${ghostResponse.status}` }), {37 status: ghostResponse.status,38 headers: { ...corsHeaders, 'Content-Type': 'application/json' },39 });40 }4142 const result = await ghostResponse.json();43 return new Response(JSON.stringify({44 data: result[resource] ?? result,45 meta: result.meta ?? null,46 }), {47 headers: { ...corsHeaders, 'Content-Type': 'application/json' },48 });49 } catch (error) {50 console.error('ghost-proxy error:', error);51 return new Response(JSON.stringify({ error: String(error) }), {52 status: 500,53 headers: { ...corsHeaders, 'Content-Type': 'application/json' },54 });55 }56});Pro tip: Include 'formats=html' in your params when fetching posts for detail pages — Ghost returns post.html with the full rendered HTML content when this format is requested. Without it, the html field is null and only the excerpt is available.
Expected result: The ghost-proxy Edge Function deploys. A test request with resource='posts' and params={include: 'authors,tags', limit: '5'} returns a data array of posts with embedded author and tag objects.
Build the Ghost blog frontend
Build the Ghost blog frontend
With the Edge Function deployed, build the Ghost-powered blog frontend in Lovable. Ghost post objects have a clean, well-documented structure: - post.title — post title string - post.slug — URL-friendly slug - post.excerpt — short auto-generated preview text - post.feature_image — URL of the featured image (can be null) - post.published_at — ISO 8601 publish date string - post.reading_time — estimated reading time in minutes (integer) - post.html — full rendered HTML content (requires formats=html param) - post.primary_author — first author object (with name, profile_image, slug) - post.primary_tag — first tag object (with name, slug, accent_color) - post.tags — array of all tag objects - post.visibility — 'public', 'members', or 'paid' For the listing page, use title, excerpt, feature_image, primary_author, primary_tag, reading_time, and published_at. For the detail page, additionally include html (rendered HTML content) and the full tags array. Ghost's HTML content is generated from its Lexical editor and is clean, semantic HTML. Use dangerouslySetInnerHTML with DOMPurify sanitization to render it. Ghost also provides post.custom_excerpt (editor-written excerpt) and post.plaintext for non-HTML contexts. Ask Lovable to generate the blog listing and single post components with these Ghost-specific field names:
Build a Ghost blog. Create two routes: /blog (post listing) and /blog/:slug (post detail). On /blog: call ghost-proxy Edge Function with resource='posts', params={include:'authors,tags', limit:'12', filter:'visibility:public'}. Display posts as cards: post.feature_image as card image (fallback to a placeholder), post.title, post.excerpt, post.primary_author.name with avatar, post.reading_time + ' min read', and post.primary_tag.name as a badge. On /blog/:slug: call ghost-proxy with slug and params={include:'authors,tags', formats:'html'}. Show feature image hero, title, author bio with profile image, and post.html rendered as sanitized HTML.
Paste this in Lovable chat
Pro tip: Ghost includes an accent_color field on tags (e.g., '#ff5733') which you can use as the tag badge background color for visual differentiation. Map it directly as a CSS style on the tag chip component.
Expected result: The blog listing page shows Ghost posts with featured images, author names, reading times, and tag badges. The detail page renders the full post HTML. Navigation between listing and detail pages works correctly.
Common use cases
Build a blog with Ghost posts and tag filtering
Your Ghost publication manages a blog with dozens of posts organized by tags. The Lovable app fetches published posts with their featured images, authors, and tags, displays them in a filterable grid, and renders individual post pages with Ghost's HTML content. Editors continue publishing in Ghost's clean writing interface while users enjoy a custom-designed Lovable frontend.
Build a blog page that fetches posts from the ghost-proxy Edge Function using path '/posts?include=authors,tags&limit=12&fields=id,title,slug,excerpt,feature_image,published_at,reading_time,primary_author,tags'. Display posts in a 3-column grid with: featured image, post title, excerpt, primary author avatar and name, reading time, and primary tag as a colored badge. Add a tag filter row at the top that refetches posts filtered by tag slug. Clicking a post navigates to /posts/:slug.
Copy this prompt to try it in Lovable
Display Ghost members-only content with access gates
Ghost supports tiered membership content where some posts require a free account or paid subscription. The Lovable app fetches post metadata (including visibility field: 'public', 'members', or 'paid') and shows a blurred preview with a signup/upgrade prompt for gated content. Full content is only delivered to users who authenticate through Ghost's Members API.
Build a content gating system for Ghost member content. Fetch posts and check post.visibility: 'public' = show full content, 'members' = show excerpt with a 'Sign in to read' button, 'paid' = show excerpt with an 'Upgrade to read' button. For public posts, display post.html content sanitized with DOMPurify. For gated posts, show a blurred content preview with an overlay card showing the access level required and a CTA linking to your Ghost subscription page.
Copy this prompt to try it in Lovable
Show author profiles and their post archives
Ghost stores rich author profiles with bio, profile image, website, and social links. The Lovable app renders an authors directory page and individual author archive pages showing all posts by that author. Ghost's API supports filtering posts by author slug, making it easy to build author-specific content views.
Build an authors page that fetches all Ghost authors via the ghost-proxy Edge Function. Display each author as a card with their profile image, name, bio (truncated to 150 chars), and post count. Clicking an author navigates to /authors/:slug. The author detail page fetches posts filtered by author slug and displays them in a list with post title, date, and featured image. Link back to the full post from each item.
Copy this prompt to try it in Lovable
Troubleshooting
Ghost API returns { errors: [{ message: 'Unknown Content API Key' }] }
Cause: The Content API key stored in GHOST_CONTENT_API_KEY is incorrect, has been regenerated, or the integration was deleted in Ghost Admin.
Solution: Open Ghost Admin → Settings → Integrations and find your custom integration. If the integration still exists, click on it and copy the Content API key again. If it was deleted, create a new integration and generate a new key. Update GHOST_CONTENT_API_KEY in Cloud → Secrets with the correct key value.
Post html field is null even though the post has content
Cause: The formats=html parameter was not included in the API request. Ghost does not include the html field by default — it must be explicitly requested.
Solution: Add formats=html to the params object when calling ghost-proxy for post detail pages. For listing pages where full HTML is not needed, continue excluding it to reduce response payload size. Example: params={include:'authors,tags', formats:'html'} for detail pages.
Feature images show as broken links
Cause: Ghost stores feature images as full absolute URLs when hosted on Ghost Pro or when Ghost is configured with its base URL. If the URL in the response starts with http:// instead of https://, or uses a different domain, the image may not load in a HTTPS context.
Solution: Check the feature_image URL value in your API response. Ghost Pro image URLs are absolute (https://your-site.ghost.io/content/images/...). Self-hosted Ghost may return URLs relative to the Ghost installation. Ensure your GHOST_URL secret matches the Ghost installation URL exactly, including protocol.
Best practices
- Always request specific fields using the fields parameter rather than fetching all fields — Ghost posts contain many fields, and listing pages only need a subset (title, slug, excerpt, feature_image, published_at, primary_tag, primary_author). Reducing payload size improves performance.
- Use include=authors,tags for listing pages and include=authors,tags for detail pages — Ghost requires explicit inclusion of related resources; they are not embedded by default.
- Cache Ghost post lists in Supabase or React state between route navigations — Ghost content changes infrequently and the same posts page should not make a new API call every time the user returns to the listing.
- Handle null feature_image gracefully — not all Ghost posts have a featured image. Always provide a fallback placeholder image URL in the feature_image img src attribute.
- Use Ghost's filter parameter for dynamic content filtering — filter=primary_tag:{slug} for tag archives, filter=authors.slug:{slug} for author archives, filter=visibility:public to exclude member-only posts from public feeds.
- Sanitize Ghost's html field content with DOMPurify before rendering with dangerouslySetInnerHTML — while Ghost generates cleaner HTML than WordPress, users with admin access can create HTML cards with arbitrary content.
- Use Ghost's accent_color tag field for visual design consistency — Ghost tags have a built-in accent color configured by the editor. Using this color for tag badges creates visual cohesion between your Lovable frontend and the Ghost editorial experience.
Alternatives
Choose WordPress if you need a general-purpose CMS with custom post types, extensive plugin support, and ACF custom fields rather than Ghost's focused publishing model.
Choose Contentful if you need a purpose-built headless CMS with structured content modeling — Contentful has a native Lovable connector and supports complex content structures beyond Ghost's publishing primitives.
Choose TYPO3 if you need enterprise European CMS capabilities with complex user groups, multilingual workflows, and granular permission management for large organizational websites.
Frequently asked questions
Do I need a self-hosted Ghost or can I use Ghost Pro?
Both work. Ghost Pro (hosted by Ghost at ghost.org/pricing) includes Content API access on all plans starting from $9/month. Self-hosted Ghost (free, open-source) also exposes the Content API. The API key and endpoint URL format are identical for both. Ghost Pro is simpler to set up and maintain; self-hosted gives you full control and no monthly fees if you have server infrastructure.
What is the difference between Ghost's Content API and Admin API?
The Content API is read-only and public — it fetches published content using a Content API key as a query parameter. It is safe to use in server-side Edge Functions for reading posts, pages, tags, and authors. The Admin API has read-write access and requires HMAC JWT token signing for authentication. Use the Admin API if you need to create posts, manage members, trigger newsletter sends, or update content from your Lovable app. For most frontend applications, only the Content API is needed.
Can I integrate Ghost newsletter subscriptions with my Lovable app?
Yes, via the Ghost Admin API. To add a subscriber, POST to /ghost/admin/api/members/ with name, email, and optional tier/newsletter data using JWT authentication. Ghost also provides a hosted subscription form (your-site.com/#/portal/signup) that handles the subscription UX directly — you can embed this as a popup or redirect users to it without building custom subscription logic in Lovable.
How does Ghost compare to WordPress for SEO?
Ghost includes built-in SEO fields (meta_title, meta_description, og_image, twitter_image) accessible via the Content API. Ghost also generates a canonical URL and a clean URL structure by default. WordPress with Yoast SEO provides more granular control but requires separate plugin configuration. For a Lovable headless frontend, you need to manually implement meta tags in your React components using Ghost's SEO fields — Ghost does not automatically inject them since the traditional theme rendering is bypassed.
What happens to existing Ghost themes when I go headless with Lovable?
The Ghost backend continues running with its active theme, which means your existing Ghost-rendered site and your new Lovable frontend can coexist. Ghost themes handle the traditional server-rendered view at your main domain, while the Lovable app is deployed separately (typically on a subdomain like app.yoursite.com or a different path). This allows gradual migration — run both in parallel until you are confident in the Lovable frontend, then switch the main domain.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation