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

How to Integrate Bolt.new with Joomla

Integrate Joomla with Bolt.new by enabling the Web Services API plugin in Joomla 4+ and generating a user API token from Users → Your Profile → API Token. Build a Next.js API route in Bolt to proxy requests with Bearer token authentication, fetch articles and categories as JSON, and render them in React. Joomla's REST API is available since version 4.0 but has fewer community resources than WordPress.

What you'll learn

  • How to enable the Joomla Web Services API plugin and generate an API token
  • How to build a Next.js API route in Bolt.new that proxies Joomla content requests with Bearer authentication
  • How to fetch Joomla articles, categories, and menus via the REST API
  • How to handle Joomla's content structure and render HTML article content in React
  • How to deploy your headless Joomla React frontend to Netlify with production environment variables
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate18 min read25 minutesCMSApril 2026RapidDev Engineering Team
TL;DR

Integrate Joomla with Bolt.new by enabling the Web Services API plugin in Joomla 4+ and generating a user API token from Users → Your Profile → API Token. Build a Next.js API route in Bolt to proxy requests with Bearer token authentication, fetch articles and categories as JSON, and render them in React. Joomla's REST API is available since version 4.0 but has fewer community resources than WordPress.

Build a Headless Joomla Frontend with Bolt.new

Joomla is the third most popular PHP CMS globally, powering government websites, educational institutions, and community portals. Since Joomla 4.0, the platform includes a built-in Web Services API that exposes articles, categories, menus, users, contacts, and tags as JSON — no plugins required for the core content types. This makes it possible to use Joomla as a headless CMS backend while building a modern React frontend in Bolt.new, giving content editors the familiar Joomla administrative interface while the frontend gains full design control and modern JavaScript tooling.

Joomla's API uses Bearer token authentication rather than the Application Password system used by WordPress. You generate a token in the Joomla user profile (Users → Your Profile → scroll to the API Token tab) and include it as an Authorization Bearer header in every API request. The API is accessible at /api/index.php/v1/ — for example, /api/index.php/v1/content/articles returns a paginated list of published articles. For public-facing content on sites without authentication requirements, some Joomla installations allow anonymous API access, but enabling the API token for all requests is the safer default.

It is worth noting that Joomla's headless CMS ecosystem is smaller and less documented than WordPress's — you will find fewer tutorials, fewer starter templates, and a smaller community of developers who have done headless Joomla work. For new projects where you have freedom to choose the CMS, WordPress offers a more mature headless setup with more community resources. This guide is most valuable for teams that already have an existing Joomla installation they want to connect to a modern frontend, or organizations with a Joomla preference for their content workflow.

Integration method

Bolt Chat + API Route

Joomla 4+ includes a Web Services API plugin that exposes articles, categories, menus, users, and modules as JSON endpoints. In Bolt.new you build a Next.js API route that proxies requests to the Joomla API using Bearer token authentication, keeping credentials server-side. The API route fetches and normalizes Joomla content which React components consume to render the frontend independently of any Joomla template or theme.

Prerequisites

  • A running Joomla 4.0+ installation with administrator access and the Web Services API plugin enabled
  • A Joomla API token generated from Users → Your Profile → API Token tab in the Joomla backend
  • Your Joomla site's base URL (e.g., https://yoursite.com) — the API is at /api/index.php/v1/
  • At least a few published articles and categories in Joomla to test the integration
  • A Bolt.new account with a new Next.js project open

Step-by-step guide

1

Enable the Joomla Web Services API Plugin and Generate an API Token

Joomla 4.0 and later includes the Web Services API as a built-in feature, but the API plugin is disabled by default in most Joomla installations. To enable it, log into the Joomla backend as an administrator and navigate to System → Plugins. Search for 'Web Services' in the plugin manager — you will find several plugins listed, including 'Web Services - Content', 'Web Services - Categories', 'Web Services - Contacts', and others. Enable all the Web Services plugins that correspond to content types you want to expose. The most important is 'Web Services - Content' for articles. Each plugin can be enabled individually by clicking its name and toggling the status to Enabled. After enabling the plugins, verify the API is responding by opening https://yoursite.com/api/index.php/v1/content/articles in a browser. If the Joomla installation requires authentication, you will see a 401 response rather than JSON — that is expected and correct. To generate an API token, go to Users → Manage Users, click your administrator username, then scroll down to the API Token tab. Click the Generate Token button. Copy the token string immediately and store it securely — it is only shown once and cannot be retrieved later if lost (you can generate a new one, but this invalidates all existing tokens for that user). Create a dedicated Joomla user account with only the permissions needed for API access rather than using your main administrator credentials.

.env
1# .env add to project root
2JOOMLA_API_URL=https://yoursite.com
3JOOMLA_API_TOKEN=your_joomla_api_token_here
4
5# Verify API is enabled by opening in browser:
6# https://yoursite.com/api/index.php/v1/content/articles
7# With token: curl -H 'Authorization: Bearer YOUR_TOKEN' https://yoursite.com/api/index.php/v1/content/articles

Pro tip: If the Joomla API returns a 404 instead of JSON or a 401, check that the 'Web Services - Content' plugin is enabled in System → Plugins. Also verify that Joomla's URL routing is using SEF URLs (System → Global Configuration → SEO → Use URL Rewriting should be set to Yes).

Expected result: The Joomla Web Services API plugins are enabled. Your .env file contains JOOMLA_API_URL and JOOMLA_API_TOKEN. Calling the API URL with the Bearer token in a browser or curl returns a JSON response with articles data.

2

Prompt Bolt to Generate the Joomla Articles API Route

With the Joomla API enabled and your credentials in .env, the next step is building the server-side proxy route in Bolt.new. A proxy API route is essential for this integration: Joomla installations rarely include CORS headers for browser requests from external domains, so direct client-side fetch calls would fail in the Bolt preview. Routing through a Next.js API route also keeps the JOOMLA_API_TOKEN credential out of client-side code, which is critical because API tokens provide authenticated access to your entire Joomla site. The Joomla Web Services API follows JSON:API specification, which means responses are structured with a 'data' array containing items, each with an 'id' and 'attributes' object. The article attributes include title, alias (used as the URL slug), introtext (teaser text), fulltext (full article body), catid, publish_up (publication date), and images (a JSON string of image URLs). Joomla's API also returns pagination metadata in a 'links' object with first, prev, next, and last links. Use the Bolt chat prompt below to generate the articles API route. After Bolt generates the code, verify that it reads both JOOMLA_API_URL and JOOMLA_API_TOKEN from process.env without any NEXT_PUBLIC_ prefix, and that the Authorization header uses the Bearer scheme rather than Basic auth (which Joomla does not support for API tokens).

Bolt.new Prompt

Create a Joomla headless CMS integration in this Next.js project. Build an API route at app/api/joomla/articles/route.ts that fetches published articles from Joomla Web Services API. Read JOOMLA_API_URL and JOOMLA_API_TOKEN from process.env. Construct the request URL as JOOMLA_API_URL + '/api/index.php/v1/content/articles'. Add Authorization: Bearer JOOMLA_API_TOKEN header. Support optional query params: 'limit' (default 10), 'offset' (default 0), 'catid' to filter by category. Map Joomla's JSON:API response format (data[].attributes) to a clean object with id, title, slug (from alias), introtext, fulltext, catid, and publishDate fields. Return the normalized array and total count.

Paste this in Bolt.new chat

app/api/joomla/articles/route.ts
1// app/api/joomla/articles/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4interface JoomlaArticleAttributes {
5 title: string;
6 alias: string;
7 introtext: string;
8 fulltext: string;
9 catid: number;
10 publish_up: string;
11 images: string;
12 state: number;
13}
14
15interface JoomlaAPIItem {
16 id: string;
17 attributes: JoomlaArticleAttributes;
18}
19
20interface JoomlaAPIResponse {
21 data: JoomlaAPIItem[];
22 meta?: { 'total-pages'?: number };
23 links?: { self: string; next?: string };
24}
25
26export async function GET(request: NextRequest) {
27 const apiUrl = process.env.JOOMLA_API_URL;
28 const apiToken = process.env.JOOMLA_API_TOKEN;
29
30 if (!apiUrl || !apiToken) {
31 return NextResponse.json(
32 { error: 'JOOMLA_API_URL and JOOMLA_API_TOKEN must be configured' },
33 { status: 500 }
34 );
35 }
36
37 const { searchParams } = new URL(request.url);
38 const limit = searchParams.get('limit') || '10';
39 const offset = searchParams.get('offset') || '0';
40 const catid = searchParams.get('catid');
41
42 const url = new URL(`${apiUrl}/api/index.php/v1/content/articles`);
43 url.searchParams.set('page[limit]', limit);
44 url.searchParams.set('page[offset]', offset);
45 if (catid) url.searchParams.set('filter[category_id]', catid);
46
47 try {
48 const response = await fetch(url.toString(), {
49 headers: {
50 'Authorization': `Bearer ${apiToken}`,
51 'Accept': 'application/vnd.api+json',
52 'Content-Type': 'application/json',
53 },
54 });
55
56 if (!response.ok) {
57 return NextResponse.json(
58 { error: `Joomla API error: ${response.status} ${response.statusText}` },
59 { status: response.status }
60 );
61 }
62
63 const data: JoomlaAPIResponse = await response.json();
64
65 const articles = (data.data || []).map((item) => {
66 const attr = item.attributes;
67 let images: Record<string, string> = {};
68 try {
69 images = JSON.parse(attr.images || '{}');
70 } catch {
71 images = {};
72 }
73
74 return {
75 id: item.id,
76 title: attr.title,
77 slug: attr.alias,
78 introtext: attr.introtext,
79 fulltext: attr.fulltext,
80 catid: attr.catid,
81 publishDate: attr.publish_up,
82 introImage: images.image_intro || null,
83 fullImage: images.image_fulltext || null,
84 };
85 });
86
87 return NextResponse.json({
88 articles,
89 total: data.meta?.['total-pages'] || null,
90 });
91 } catch (error) {
92 const message = error instanceof Error ? error.message : 'Unknown error';
93 return NextResponse.json({ error: message }, { status: 500 });
94 }
95}

Pro tip: Joomla's JSON:API response wraps all content in a 'data' array where each item has 'id' and 'attributes'. This is different from WordPress's REST API which returns a flat array of objects. Always access article fields through item.attributes rather than directly on the item.

Expected result: Calling /api/joomla/articles in the Bolt preview returns a JSON array of normalized Joomla articles with title, slug, introtext, catid, publishDate, and image URL fields.

3

Build the Article Listing and Single Article Pages

With the articles API route working, build the React frontend pages that display Joomla content. A standard Joomla blog frontend needs at minimum a listing page showing article cards and a dynamic detail page rendering the full article content. Joomla article content is returned as raw HTML in the introtext and fulltext fields — render it using dangerouslySetInnerHTML, which is appropriate since the HTML comes from your own trusted Joomla installation. The listing page calls /api/joomla/articles and renders a grid of cards with the title, intro text, intro image, and publish date. For single article pages, build a separate API route that fetches a single article by its alias (slug) using Joomla's filter parameter: /api/index.php/v1/content/articles?filter[alias]=your-article-alias. Joomla also returns the fulltext field separately from introtext — combine them when rendering the full article view. For articles using Joomla's 'Read more' separator, introtext contains the content above the separator and fulltext the content below; concatenate them for the full article. Strip HTML tags from introtext before displaying it as a card excerpt — a simple regex replace handles this for most cases. Add navigation breadcrumbs using the catid to link back to the category filter view.

Bolt.new Prompt

Build an article listing and detail page for this Joomla headless Next.js project. Create a listing page at app/blog/page.tsx that fetches from /api/joomla/articles with limit=12 and displays articles as cards in a responsive grid. Each card should show the intro image (if available), title, trimmed plain-text excerpt from introtext (strip HTML tags), and formatted publish date. Create a second API route at app/api/joomla/articles/[slug]/route.ts that fetches a single article by alias using filter[alias]=SLUG from JOOMLA_API_URL/api/index.php/v1/content/articles with Bearer auth. Create a detail page at app/blog/[slug]/page.tsx that renders the full article (introtext + fulltext) using dangerouslySetInnerHTML in a Tailwind prose container.

Paste this in Bolt.new chat

app/api/joomla/articles/[slug]/route.ts
1// app/api/joomla/articles/[slug]/route.ts
2import { NextResponse } from 'next/server';
3
4export async function GET(
5 _request: Request,
6 { params }: { params: { slug: string } }
7) {
8 const apiUrl = process.env.JOOMLA_API_URL;
9 const apiToken = process.env.JOOMLA_API_TOKEN;
10
11 if (!apiUrl || !apiToken) {
12 return NextResponse.json(
13 { error: 'Joomla API credentials not configured' },
14 { status: 500 }
15 );
16 }
17
18 const url = new URL(`${apiUrl}/api/index.php/v1/content/articles`);
19 url.searchParams.set('filter[alias]', params.slug);
20
21 try {
22 const response = await fetch(url.toString(), {
23 headers: {
24 'Authorization': `Bearer ${apiToken}`,
25 'Accept': 'application/vnd.api+json',
26 },
27 });
28
29 if (!response.ok) {
30 return NextResponse.json(
31 { error: `Joomla API error: ${response.status}` },
32 { status: response.status }
33 );
34 }
35
36 const data = await response.json();
37 const items = data.data || [];
38
39 if (items.length === 0) {
40 return NextResponse.json({ error: 'Article not found' }, { status: 404 });
41 }
42
43 const attr = items[0].attributes;
44 let images: Record<string, string> = {};
45 try { images = JSON.parse(attr.images || '{}'); } catch { images = {}; }
46
47 const article = {
48 id: items[0].id,
49 title: attr.title,
50 slug: attr.alias,
51 introtext: attr.introtext,
52 fulltext: attr.fulltext,
53 content: [attr.introtext, attr.fulltext].filter(Boolean).join('\n'),
54 catid: attr.catid,
55 publishDate: attr.publish_up,
56 introImage: images.image_intro || null,
57 fullImage: images.image_fulltext || null,
58 };
59
60 return NextResponse.json({ article });
61 } catch (error) {
62 const message = error instanceof Error ? error.message : 'Unknown error';
63 return NextResponse.json({ error: message }, { status: 500 });
64 }
65}

Pro tip: Joomla's introtext field often contains HTML with Joomla-specific read-more tags like {readmore}. Strip these tags from the introtext before using it as a card excerpt. For full article pages, concatenate introtext and fulltext and render the combined HTML.

Expected result: The /blog listing page shows Joomla articles as cards with titles, excerpts, and images. Clicking a card navigates to /blog/[slug] which renders the full article content including both introtext and fulltext HTML.

4

Fetch Joomla Categories for Navigation and Filtering

Joomla's category system is a core organizational feature — articles are assigned to categories, and categories can be nested in parent-child hierarchies. The Web Services API exposes categories through the /api/index.php/v1/categories endpoint with a component query parameter to specify which component's categories to fetch. For articles, use component=com_content. The API returns category data including id, title, alias, parent_id, description, and level (depth in the hierarchy). You can use this data to build a category navigation sidebar, filter tabs above the article listing, or breadcrumb navigation on article detail pages. When fetching articles filtered by category, pass the category id using the filter[category_id] parameter. Note that Joomla's category alias (slug) is separate from the category ID — if you want URL-based category filtering (e.g., /blog/category/news), you will need to first fetch the category by alias to get its ID, then fetch articles using that ID. For most implementations, storing the category list in React state on first load and using the category ID for filtering is the most efficient approach. The category API also supports pagination with the same page[limit] and page[offset] parameters as the articles endpoint, which matters for sites with large numbers of categories.

Bolt.new Prompt

Add category navigation to this Joomla headless Next.js project. Create an API route at app/api/joomla/categories/route.ts that fetches content categories from process.env.JOOMLA_API_URL + '/api/index.php/v1/categories?component=com_content' with Bearer auth. Return categories with id, title, alias, parentId, and level fields. Update the app/blog/page.tsx listing page to fetch categories on mount and display them as filter tabs above the article grid. Clicking a category tab should re-fetch /api/joomla/articles with the selected catid filter. Show 'All' as the first unfiltered tab.

Paste this in Bolt.new chat

app/api/joomla/categories/route.ts
1// app/api/joomla/categories/route.ts
2import { NextResponse } from 'next/server';
3
4export async function GET() {
5 const apiUrl = process.env.JOOMLA_API_URL;
6 const apiToken = process.env.JOOMLA_API_TOKEN;
7
8 if (!apiUrl || !apiToken) {
9 return NextResponse.json(
10 { error: 'Joomla API credentials not configured' },
11 { status: 500 }
12 );
13 }
14
15 const url = new URL(`${apiUrl}/api/index.php/v1/categories`);
16 url.searchParams.set('component', 'com_content');
17 url.searchParams.set('page[limit]', '50');
18
19 try {
20 const response = await fetch(url.toString(), {
21 headers: {
22 'Authorization': `Bearer ${apiToken}`,
23 'Accept': 'application/vnd.api+json',
24 },
25 });
26
27 if (!response.ok) {
28 return NextResponse.json(
29 { error: `Joomla Categories API error: ${response.status}` },
30 { status: response.status }
31 );
32 }
33
34 const data = await response.json();
35 const categories = (data.data || []).map((item: { id: string; attributes: { title: string; alias: string; parent_id: number; level: number; description: string } }) => ({
36 id: item.id,
37 title: item.attributes.title,
38 alias: item.attributes.alias,
39 parentId: item.attributes.parent_id,
40 level: item.attributes.level,
41 description: item.attributes.description,
42 }));
43
44 return NextResponse.json({ categories });
45 } catch (error) {
46 const message = error instanceof Error ? error.message : 'Unknown error';
47 return NextResponse.json({ error: message }, { status: 500 });
48 }
49}

Pro tip: Joomla categories include system categories that you may not want to display (such as 'Uncategorised' with ID 2 and root categories with level 0 or 1). Filter the returned categories to only show those with level >= 2 and a meaningful title before rendering them as navigation items.

Expected result: The /api/joomla/categories endpoint returns Joomla content categories. The blog listing page displays category filter tabs, and selecting a category filters the article grid to show only articles in that category.

5

Deploy to Netlify and Configure Production Environment Variables

Your headless Joomla frontend works in Bolt's WebContainer preview because all Joomla API calls are outbound HTTP requests from the server-side Next.js API routes — outbound calls to external servers work correctly in WebContainer during development. When you are ready to share the site publicly, connect your Bolt project to Netlify through Settings → Applications → Netlify → Connect. Bolt will build the Next.js project and deploy it to a *.netlify.app URL automatically. After the first deployment, open your Netlify dashboard → Site Configuration → Environment Variables and add JOOMLA_API_URL (your full Joomla site URL) and JOOMLA_API_TOKEN. Trigger a redeploy from the Netlify dashboard for the environment variables to take effect — environment variables added after an initial deploy require a new build to be available in the application code. One critical WebContainer limitation to understand: incoming webhooks from Joomla extensions cannot reach the Bolt preview URL during development. Bolt's WebContainer runs inside a browser tab and does not expose a public-facing HTTP server, so any Joomla extension that sends HTTP callbacks (workflow notifications, content publishing hooks, or third-party integrations) requires your deployed Netlify URL. After deployment, configure any Joomla extension webhooks to point at your Netlify site URL. If you want content updates in Joomla to automatically refresh cached pages in your Next.js frontend without a full Netlify redeploy, add a revalidation endpoint that Joomla can call when articles are published — though Joomla has fewer webhook plugins than WordPress, making on-demand revalidation less commonly used in Joomla headless setups.

Bolt.new Prompt

Add a cache revalidation endpoint to this headless Joomla Next.js project. Create an API route at app/api/joomla/revalidate/route.ts that accepts a POST request with a secret token in the x-revalidate-secret header matching process.env.JOOMLA_REVALIDATE_SECRET. Parse the request body for an optional 'alias' string (the article slug). Call revalidatePath for /blog and for the specific article path if alias is provided. Return 200 with revalidated: true on success.

Paste this in Bolt.new chat

app/api/joomla/revalidate/route.ts
1// app/api/joomla/revalidate/route.ts
2import { NextResponse } from 'next/server';
3import { revalidatePath } from 'next/cache';
4
5export async function POST(request: Request) {
6 const secret = request.headers.get('x-revalidate-secret');
7
8 if (!secret || secret !== process.env.JOOMLA_REVALIDATE_SECRET) {
9 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
10 }
11
12 try {
13 const body = await request.json();
14 const alias = body?.alias as string | undefined;
15
16 revalidatePath('/blog');
17
18 if (alias) {
19 revalidatePath(`/blog/${alias}`);
20 }
21
22 return NextResponse.json({
23 revalidated: true,
24 paths: alias ? ['/blog', `/blog/${alias}`] : ['/blog'],
25 });
26 } catch {
27 return NextResponse.json({ error: 'Revalidation failed' }, { status: 500 });
28 }
29}

Pro tip: Add JOOMLA_REVALIDATE_SECRET to Netlify's environment variables and configure a Joomla webhook plugin (such as 'Webhooks for Joomla' from the JED directory) to POST to your deployed /api/joomla/revalidate endpoint when articles are published or updated.

Expected result: Your headless Joomla frontend is deployed on Netlify. Joomla articles and categories appear on the deployed site. The revalidation endpoint allows on-demand cache refresh when Joomla content is published.

Common use cases

Existing Joomla Site with Modern React Frontend

Replace a Joomla template theme with a custom React application while keeping all content, categories, and menus in Joomla. Content editors continue using the familiar Joomla backend, while the React frontend delivers improved performance, custom design, and modern component-based UI that Joomla templates cannot easily achieve.

Bolt.new Prompt

Build a headless Joomla CMS integration in this Next.js project. Create an API route at app/api/joomla/articles/route.ts that fetches published articles from the Joomla Web Services API using process.env.JOOMLA_API_URL + '/api/index.php/v1/content/articles'. Add an Authorization header with Bearer process.env.JOOMLA_API_TOKEN. Support pagination with 'page[limit]' and 'page[offset]' query params. Return articles with id, title, alias (slug), introtext, fulltext, catid, and publish_up date fields.

Copy this prompt to try it in Bolt.new

Joomla Category Blog Page

Fetch Joomla categories and their articles to build a structured content hub with category filtering. Joomla's category system is hierarchical — categories can have parent and child relationships — which maps well to a React navigation sidebar and filtered article listing.

Bolt.new Prompt

Create a category blog page for this Joomla headless Next.js project. Build an API route at app/api/joomla/categories/route.ts that fetches published categories from process.env.JOOMLA_API_URL + '/api/index.php/v1/categories?component=com_content' with Bearer auth. Build a second API route at app/api/joomla/articles/route.ts that accepts a 'catid' query param to filter by category. Create a blog page at app/blog/page.tsx showing categories as filter tabs and articles as cards in a responsive grid.

Copy this prompt to try it in Bolt.new

Joomla Contact Directory

Fetch Joomla contact records from the com_contact component API to build a staff directory or contact listing page. Joomla's Web Services API exposes the contact component endpoints, making it straightforward to pull contact names, roles, emails, and images from an existing Joomla directory into a React UI.

Bolt.new Prompt

Build a contact directory page using Joomla's Web Services API. Create an API route at app/api/joomla/contacts/route.ts that fetches contacts from process.env.JOOMLA_API_URL + '/api/index.php/v1/contacts' with Bearer authorization header using process.env.JOOMLA_API_TOKEN. Return contact name, position, email_to, and image fields. Render contacts in a responsive card grid at app/team/page.tsx with name, position, and email visible on each card.

Copy this prompt to try it in Bolt.new

Troubleshooting

Joomla API returns 404 Not Found when calling /api/index.php/v1/content/articles

Cause: The Joomla Web Services API plugins are disabled, or Joomla's URL rewriting is not configured correctly for API routing.

Solution: Log into the Joomla backend and go to System → Plugins. Search for 'Web Services' and enable all relevant plugins, especially 'Web Services - Content'. Also verify that SEF URLs are enabled in Global Configuration → SEO → Use URL Rewriting. If the Joomla installation does not have a .htaccess file from the htaccess.txt sample, rename it to enable Apache mod_rewrite support.

Joomla API returns 401 Unauthorized despite correct API token

Cause: The API token is not associated with a user who has adequate permissions, or the token was regenerated after copying and the old token is now invalid.

Solution: Go to Users → Manage Users in the Joomla backend, click the user whose token you are using, and verify their user group has at least 'Manager' access level for content API endpoints. If you recently regenerated the token, update JOOMLA_API_TOKEN in your .env file with the new token value. Note that regenerating a token immediately invalidates the previous one.

typescript
1// Verify the token works with a direct curl test:
2// curl -H 'Authorization: Bearer YOUR_TOKEN' \
3// -H 'Accept: application/vnd.api+json' \
4// https://yoursite.com/api/index.php/v1/content/articles
5// Expected: JSON response with 'data' array

CORS error when calling the Joomla API from a React component in the Bolt preview

Cause: Joomla does not add CORS headers for arbitrary origins by default, so direct browser-side fetch calls to your Joomla URL fail when the Bolt preview origin does not match.

Solution: Route all Joomla API calls through the Next.js API routes at /api/joomla/* rather than calling the Joomla URL directly from client React components. The API routes execute server-side and bypass browser CORS restrictions entirely.

typescript
1// Wrong — direct call from a React component
2const res = await fetch('https://yoursite.com/api/index.php/v1/content/articles');
3
4// Correct — call through your Next.js API route
5const res = await fetch('/api/joomla/articles?limit=10');

Joomla webhooks or content update callbacks do not reach the Bolt preview URL

Cause: Bolt's WebContainer runs in a browser tab and cannot accept incoming HTTP connections from external services — no public HTTP server exists in the WebContainer development environment.

Solution: Deploy to Netlify first to get a stable public URL, then configure any Joomla webhook extensions to point at your deployed /api/joomla/revalidate endpoint. The Bolt preview URL is only suitable for testing outbound API calls, not for receiving incoming requests from Joomla.

Best practices

  • Never expose JOOMLA_API_TOKEN as a NEXT_PUBLIC_ variable — keep it server-side in API routes only, since the token provides authenticated access to your entire Joomla backend
  • Create a dedicated Joomla user account with minimum necessary permissions for API access rather than using your main administrator token
  • Always access Joomla JSON:API responses through item.attributes — the JSON:API format wraps all fields in attributes, not at the top level of each item
  • Filter system categories (level 0 and 1, and 'Uncategorised') from category listings before rendering navigation
  • Concatenate introtext and fulltext fields to render full Joomla articles — articles using the 'Read More' separator split content between these two fields
  • Test all Joomla API calls in the Bolt WebContainer preview before deploying — outbound calls to your Joomla server work correctly in the development environment
  • For new projects without an existing Joomla CMS, consider WordPress as the headless CMS alternative — WordPress has a larger headless community and a more mature REST API with more tutorials and starter kits
  • Add error boundaries in React components that render Joomla HTML content, since introtext and fulltext may occasionally contain malformed HTML from the Joomla editor

Alternatives

Frequently asked questions

Does Bolt.new work with Joomla?

Yes. Joomla 4.0 and later includes a Web Services API that works with Bolt.new's Next.js API routes. You enable the Web Services plugins in the Joomla backend, generate an API token, and build a proxy API route in Bolt that calls the Joomla API with Bearer authentication. Outbound API calls to Joomla work correctly in Bolt's WebContainer preview.

Do I need any Joomla extensions to use the Web Services API?

No additional extensions are required — the Web Services API is built into Joomla 4.0+. However, the API plugins are disabled by default and must be enabled individually in System → Plugins. Enable 'Web Services - Content' for articles, 'Web Services - Categories' for categories, and any other component plugins matching your content types. For Joomla 3.x, the Web Services API is not available and you would need a third-party extension.

Can I use Bolt.new with Joomla 3.x?

Joomla 3.x does not include a built-in Web Services API. For Joomla 3.x you would need a third-party API extension such as 'API for Joomla' from the Joomla Extensions Directory. However, Joomla 3.x reached end of life in August 2023, so migrating to Joomla 4 or 5 is strongly recommended before building a headless frontend.

How is Joomla's API different from WordPress's REST API?

Joomla's API follows the JSON:API specification, which wraps all response data in a 'data' array where each item has an 'id' and an 'attributes' object. WordPress returns flat JSON objects directly. Joomla also uses Bearer token authentication, while WordPress can use Basic auth with Application Passwords. Joomla's API community and documentation are smaller than WordPress's, so you may find fewer ready-made examples and tools.

Can Joomla webhooks reach the Bolt.new development preview?

No. Bolt's WebContainer runs inside a browser tab and cannot accept incoming HTTP connections. Joomla extension webhooks, workflow notifications, and content publishing callbacks all require a deployed URL. Deploy to Netlify first, then configure Joomla extensions to use your deployed URL's revalidation endpoint.

Is Joomla a good choice for new projects using Bolt.new?

For new projects, WordPress or Ghost are typically better choices due to their larger headless CMS ecosystems and more documentation. Joomla is most valuable when connecting to an existing Joomla installation that your team already uses and maintains. The Joomla Web Services API works well with Bolt.new, but the smaller community means less support when you encounter integration issues.

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.