Connect your Lovable app to a WordPress site as a headless CMS by calling the WordPress REST API from a Supabase Edge Function. Fetch posts, pages, categories, tags, and Advanced Custom Fields data with optional JWT or application password authentication. WordPress is a self-hosted CMS you control entirely, unlike Contentful which is a headless CMS-as-a-service with a cloud-hosted content management UI.
Use WordPress as a headless CMS for your Lovable app via the REST API
Headless WordPress means using WordPress purely as a content management system — editors create and manage content in the familiar WordPress admin interface — while a completely separate frontend (your Lovable React app) fetches and displays that content via the REST API. This architecture gives content teams the WordPress editing experience they know while giving developers the freedom to build any frontend without WordPress theme constraints.
WordPress has included a full REST API since version 4.7, covering posts, pages, categories, tags, media, users, comments, and custom post types. The API is available at yoursite.com/wp-json/wp/v2/ and returns JSON by default. For public content (published posts and pages), no authentication is required — the Lovable Edge Function simply calls the endpoint and returns the data. For draft content, private posts, or write operations (creating or updating posts from the frontend), WordPress Application Passwords provide a secure authentication mechanism built into WP 5.6+ without any plugin required.
The headless WordPress architecture is the right choice when your team already has significant WordPress content, when non-technical editors need the WordPress admin UI for content management, or when your SEO strategy requires maintaining existing WordPress URLs alongside a new Lovable frontend. For new projects without existing content, Contentful's native Lovable connector provides a smoother setup experience as a purpose-built headless CMS. But for the 43% of websites already running WordPress, headless REST API integration is the fastest path to a modern Lovable frontend without migrating content.
Integration method
WordPress REST API integrates with Lovable through a Supabase Edge Function that fetches posts, pages, and custom data from your WordPress installation. For public content, no authentication is required and the Edge Function simply proxies the WP REST API response. For private content or write operations, WordPress Application Passwords (built-in since WP 5.6) provide secure authentication. The WP site URL is stored in Cloud → Secrets and the Edge Function handles caching and error handling.
Prerequisites
- A live WordPress site (self-hosted at wordpress.org or managed hosting — not WordPress.com free plan which restricts API access)
- WordPress version 4.7 or later with the REST API enabled (enabled by default)
- For private content or write access: a WordPress user account with Application Password generated (Users → your profile → Application Passwords in WP admin)
- Your WordPress site's base URL (e.g., https://yourblog.com)
- A Lovable account with an active Lovable Cloud project
Step-by-step guide
Store your WordPress site URL in Cloud → Secrets
Store your WordPress site URL in Cloud → Secrets
The WordPress REST API base URL is the first piece of configuration your Edge Function needs. For public content (published posts and pages), no authentication credentials are needed — just the site URL. For private content or write operations, you also need an Application Password. To set up an Application Password (for private content access): In your WordPress admin dashboard, go to Users → Profile (or Users → All Users, then click your username). Scroll to the 'Application Passwords' section near the bottom of the profile page. Enter a name for the application (e.g., 'Lovable App') and click 'Add New Application Password'. WordPress generates a password like 'abcd 1234 efgh 5678 ijkl mnop' — copy it immediately, it is shown only once. Your Application Password credentials are the WordPress username and this generated password. In Lovable, click '+' next to Preview to open the Cloud panel, then click 'Secrets'. Click 'Add new secret'. Add: - WP_SITE_URL — your WordPress site base URL without trailing slash (e.g., https://yourblog.com) - WP_APP_USERNAME — your WordPress username (only if using authentication) - WP_APP_PASSWORD — the Application Password generated above (only if using authentication) For fully public WordPress blogs (only fetching published posts/pages), only WP_SITE_URL is needed — no authentication credentials are required and the REST API is openly accessible.
Pro tip: Test your WordPress REST API directly in a browser before building the Edge Function: visit yoursite.com/wp-json/wp/v2/posts to see the JSON response. If you see an error or empty response, the REST API may be disabled by a security plugin — check with your WordPress host.
Expected result: WP_SITE_URL is stored in Cloud → Secrets. Visiting {your_site_url}/wp-json/wp/v2/posts in a browser returns a JSON array of your recent posts.
Create the WordPress REST API proxy Edge Function
Create the WordPress REST API proxy Edge Function
The Edge Function proxies WordPress REST API requests, adding authentication headers when needed and handling CORS (the browser cannot call the WordPress REST API directly from a Lovable frontend because WordPress is on a different domain). The function accepts a 'path' parameter specifying the REST API path to fetch (e.g., '/wp/v2/posts?per_page=10&_embed=1'), builds the full URL using WP_SITE_URL, optionally adds Basic Auth headers using the Application Password credentials, and returns the WordPress JSON response. The _embed=1 query parameter is important — it makes WordPress include embedded related resources (featured images, author details, category objects) in a single response rather than requiring multiple API calls. Always include _embed=1 when fetching posts to get featured image URLs and author display names in one request. For performance, consider caching WordPress API responses in Supabase. WordPress content changes infrequently — caching post lists for 5-10 minutes in a supabase_cache table significantly reduces API calls and improves page load times. The Edge Function checks the cache first and only calls WordPress if the cache is stale. For WooCommerce product data (which requires WooCommerce REST API consumer key/secret), add WC_CONSUMER_KEY and WC_CONSUMER_SECRET to Cloud → Secrets and use them as query parameters: ?consumer_key={key}&consumer_secret={secret} on WooCommerce endpoints (/wp-json/wc/v3/products).
Create a Supabase Edge Function at supabase/functions/wordpress-proxy/index.ts that proxies WordPress REST API requests. Read WP_SITE_URL, WP_APP_USERNAME (optional), and WP_APP_PASSWORD (optional) from Deno.env.get(). Accept a POST request with a 'path' string (e.g., '/wp/v2/posts?per_page=10&_embed=1'). Build the full WP API URL. If auth credentials exist, add an Authorization: Basic header with base64-encoded username:password. Return the WordPress JSON response. Include CORS headers.
Paste this in Lovable chat
1// supabase/functions/wordpress-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 { path } = await req.json() as { path: string };1213 if (!path?.startsWith('/')) {14 return new Response(JSON.stringify({ error: 'path must start with / (e.g., /wp/v2/posts)' }), {15 status: 400,16 headers: { ...corsHeaders, 'Content-Type': 'application/json' },17 });18 }1920 const siteUrl = Deno.env.get('WP_SITE_URL')!.replace(/\/$/, '');21 const username = Deno.env.get('WP_APP_USERNAME');22 const password = Deno.env.get('WP_APP_PASSWORD');2324 const url = `${siteUrl}/wp-json${path}`;25 const headers: Record<string, string> = { 'Accept': 'application/json' };2627 if (username && password) {28 headers['Authorization'] = 'Basic ' + btoa(`${username}:${password}`);29 }3031 const wpResponse = await fetch(url, { headers });3233 if (!wpResponse.ok) {34 console.error(`WordPress API error ${wpResponse.status} for path: ${path}`);35 return new Response(JSON.stringify({ error: `WordPress API returned ${wpResponse.status}` }), {36 status: wpResponse.status,37 headers: { ...corsHeaders, 'Content-Type': 'application/json' },38 });39 }4041 const data = await wpResponse.json();42 const totalPages = wpResponse.headers.get('X-WP-TotalPages');43 const total = wpResponse.headers.get('X-WP-Total');4445 return new Response(JSON.stringify({46 data,47 pagination: totalPages ? {48 total: parseInt(total || '0'),49 totalPages: parseInt(totalPages),50 } : null,51 }), {52 headers: { ...corsHeaders, 'Content-Type': 'application/json' },53 });54 } catch (error) {55 console.error('wordpress-proxy error:', error);56 return new Response(JSON.stringify({ error: String(error) }), {57 status: 500,58 headers: { ...corsHeaders, 'Content-Type': 'application/json' },59 });60 }61});Pro tip: The WP REST API includes pagination headers X-WP-Total and X-WP-TotalPages in responses. The Edge Function above passes these through so your frontend can build accurate pagination controls without an extra count request.
Expected result: The wordpress-proxy Edge Function deploys. A test request with path '/wp/v2/posts?per_page=5&_embed=1' returns a JSON array of recent posts including embedded featured images and author data. The pagination object shows total post count.
Build the blog or content display frontend
Build the blog or content display frontend
With the Edge Function deployed, build the Lovable frontend that fetches and displays WordPress content. The React component calls supabase.functions.invoke('wordpress-proxy', { body: { path: '/wp/v2/posts?per_page=10&_embed=1' } }) and renders the returned post data. Key WordPress REST API response fields to use in your React component: - post.title.rendered — post title (may contain HTML entities, use dangerouslySetInnerHTML for safety, or parse with a text decoder) - post.excerpt.rendered — post excerpt HTML - post.content.rendered — full post content HTML - post.date — ISO 8601 publish date - post.slug — URL-friendly post identifier - post._embedded['wp:featuredmedia']?.[0]?.source_url — featured image URL (only present with _embed=1) - post._embedded.author?.[0]?.name — author display name (only present with _embed=1) - post._embedded['wp:term']?.[0] — array of category objects For rendering WordPress HTML content safely, use DOMPurify to sanitize the rendered HTML before inserting it with dangerouslySetInnerHTML. WordPress content can contain arbitrary HTML including scripts if your editor allowed HTML blocks. Ask Lovable to generate the complete post listing, category filter, and single post detail components by describing the layout you want and the WordPress field names above.
Build a blog listing page that calls the wordpress-proxy Edge Function with path '/wp/v2/posts?per_page=9&_embed=1&orderby=date'. Display posts in a 3-column responsive grid. Each card shows: the featured image (_embedded['wp:featuredmedia'][0].source_url) at the top, category badges from _embedded['wp:term'][0], post title, excerpt (strip HTML tags for clean text preview), author name, and formatted date. Clicking a card navigates to /blog/:slug. Add a 'Load more' button that fetches the next page using the page query parameter.
Paste this in Lovable chat
Pro tip: Strip HTML tags from WordPress excerpts before displaying them as plain text previews — excerpts often contain paragraph tags. A simple regex like excerpt.replace(/<[^>]*>/g, '') removes all HTML for clean card descriptions.
Expected result: The blog listing page loads WordPress posts, displays them in a grid with featured images and category badges, and pagination works correctly. Navigating to a post slug shows the full post detail view with sanitized HTML content.
Common use cases
Display a WordPress blog with categories and pagination
Your marketing team manages a blog in WordPress. Your Lovable app fetches published posts from the WordPress REST API, displays them in a grid with featured images, excerpts, author names, and publish dates, and supports category filtering and pagination. The WP REST API supports all of these natively — no plugin required.
Build a blog page that fetches posts from the wordpress-proxy Edge Function. Display posts in a responsive card grid with: featured image, post title as a link to the full post page, excerpt, author name, category badges, and formatted publish date. Add category filter buttons at the top that refetch posts filtered by category slug. Implement pagination with Previous and Next buttons. Show a loading skeleton while posts are loading.
Copy this prompt to try it in Lovable
Render dynamic landing pages from WordPress page content
Marketing creates landing pages in WordPress using the Gutenberg block editor. The Lovable app fetches the page content via the REST API and renders it — using the rendered HTML from WP for rich content blocks. Custom page templates are identified by a custom field (page_template) set in WordPress, allowing the Lovable router to render different React layouts for different page types.
Create a dynamic page renderer that fetches WordPress page content. When a user navigates to /page/:slug, call the wordpress-proxy Edge Function to fetch the WP page with that slug. Render the page.content.rendered HTML in a sanitized container (use DOMPurify to sanitize). Display the page.title.rendered as an h1. Add the page's featured image as a hero image above the content. If the page has an ACF field 'show_cta_button' set to true, display a CTA section below the content.
Copy this prompt to try it in Lovable
Sync WooCommerce product catalog to Lovable frontend
A WooCommerce store's product catalog is managed in WordPress. The Lovable app fetches products via the WooCommerce REST API extension (/wp-json/wc/v3/products), displaying product listings, category pages, and individual product detail pages. Product prices, inventory status, and images are all retrieved from WooCommerce and cached in Supabase for performance.
Build a product catalog that reads from WooCommerce via the wordpress-proxy Edge Function calling /wp-json/wc/v3/products. Display products in a grid with product image, name, price, and an 'Add to Cart' button. Add category filter sidebar using WooCommerce product categories. When a user clicks a product, show a detail page with the full product description, image gallery, price, and stock status. Cache product data in Supabase and refresh it every 30 minutes.
Copy this prompt to try it in Lovable
Troubleshooting
Edge Function returns empty array [] even though posts exist in WordPress
Cause: The WordPress REST API only returns posts in 'publish' status by default. Draft posts, scheduled posts, or posts with 'private' visibility are excluded from unauthenticated requests.
Solution: If you need to fetch non-published posts, add Application Password authentication to the Edge Function. Also check that the WP_SITE_URL is the correct root URL without /wp-json (the Edge Function appends this). If your WordPress runs in a subdirectory like yoursite.com/blog, include that path in WP_SITE_URL.
WordPress REST API returns 401 or 'rest_forbidden' for authenticated requests
Cause: Application Password authentication is failing — the username or password is incorrect, the Application Password has been revoked, or the user account does not have the required capability for the requested resource.
Solution: Regenerate the Application Password in WordPress admin → Users → your profile → Application Passwords. Delete the old password entry and create a new one. Update WP_APP_PASSWORD in Cloud → Secrets with the newly generated password. Verify WP_APP_USERNAME exactly matches the WordPress username (not display name or email — the login username).
Featured images are null or missing even when _embed=1 is set
Cause: Posts do not have a featured image assigned in WordPress, the media attachment was deleted, or the _embedded key structure is accessed incorrectly.
Solution: Always null-check the embedded media chain before accessing source_url: post._embedded?.['wp:featuredmedia']?.[0]?.source_url — using optional chaining prevents runtime errors for posts without featured images. Also verify the posts actually have featured images set in WordPress admin (each post's Featured Image box should show an assigned image).
1// Safe featured image access2const featuredImageUrl = post._embedded?.['wp:featuredmedia']?.[0]?.source_url ?? '/default-image.jpg';Best practices
- Always use _embed=1 in WordPress REST API requests for posts — this includes featured images and author data in one request and eliminates the need for multiple API calls to get complete post data.
- Cache WordPress API responses in Supabase for frequently accessed content — blog post lists rarely change more than a few times per day, and caching for 5-10 minutes dramatically improves load times and reduces WordPress server load.
- Sanitize WordPress HTML content with DOMPurify before rendering with dangerouslySetInnerHTML — WordPress content editors can insert arbitrary HTML including script tags in HTML blocks, which is an XSS risk without sanitization.
- Use the slug field (not numeric ID) for post URLs in your Lovable app — slugs are human-readable, SEO-friendly, and more stable than numeric IDs if posts are ever migrated.
- Handle WordPress pagination using the X-WP-TotalPages response header rather than fetching all posts at once — large blogs with thousands of posts will time out or return truncated responses without pagination.
- For WooCommerce integration, use WooCommerce Application Passwords (separate from WordPress Application Passwords) — WooCommerce has its own consumer key/secret system for /wp-json/wc/v3/ endpoints.
- Test the REST API with at least one published post before connecting Lovable — a fresh WordPress install with no published content returns an empty array, which can be mistaken for a configuration error.
Alternatives
Choose Contentful if you are starting a new project without existing WordPress content — Contentful is a purpose-built headless CMS with a native Lovable connector requiring less configuration than the WordPress REST API proxy.
Choose Ghost if your use case is focused publishing and newsletters — Ghost is a cleaner, faster CMS for blogs and content-focused sites without WordPress's plugin complexity.
Choose TYPO3 if you need enterprise content management for a large European organization with complex permissions and multilingual content structures.
Frequently asked questions
Does the WordPress site need any plugins for the REST API integration?
No plugins are required for basic post and page retrieval — the WordPress REST API is built into WordPress core since version 4.7. For Advanced Custom Fields (ACF) data in API responses, the free 'ACF to REST API' plugin or the ACF Pro 'REST API' setting exposes custom field values. For Custom Post Types to appear in the API, the post type's 'show_in_rest' argument must be set to true in the registration code.
Can I write content to WordPress from my Lovable app, not just read it?
Yes. The WordPress REST API supports POST (create), PUT (update), and DELETE operations with Application Password authentication. A user with 'editor' or 'author' role can create and edit posts via the API. To create a post, call the wordpress-proxy Edge Function with path '/wp/v2/posts' using a POST method and pass the post data in the request body. This enables use cases like Lovable app forms that submit content directly to WordPress for publishing.
How do I fetch Advanced Custom Fields (ACF) data from the REST API?
ACF data appears in the 'acf' property of posts and pages when the ACF REST API support is enabled. For ACF Free, install the 'ACF to REST API' plugin from the WordPress plugin directory. For ACF Pro, go to Custom Fields → Tools → Show in REST API and enable it per field group. Once enabled, post responses include acf: { field_name: value, another_field: value } alongside the standard fields. Access custom fields in your React component using post.acf.your_field_name.
Will headless WordPress hurt my WordPress site's SEO?
Headless WordPress can help or hurt SEO depending on implementation. The WordPress backend and its URLs become unused, so you lose existing SEO equity unless you redirect old URLs. The new Lovable frontend must implement proper meta tags, structured data, and sitemap generation independently — WordPress SEO plugins like Yoast no longer automatically provide these for the new frontend. For a Lovable integration specifically using Yoast SEO metadata, see the yoast-seo-for-wordpress integration page which covers fetching Yoast meta fields via the REST API.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation