Guides
Import from Lovable
Migrate your Lovable project to OptimoCMS. Import content, images and design tokens while preserving your design.
Import from Lovable
Have you built a website in Lovable and want to move it to OptimoCMS for more control, API access and scalability? This tutorial walks you through the full migration process.
In this tutorial:
- Connect a Lovable project via the SDK and MCP
- Import content and images
- Carry over design tokens from your Lovable design
- Manual adjustments after import
Prerequisites
- Node.js 18+
npm install @optimocms/sdk- The public URL of your Lovable project (e.g.
https://your-project.lovable.app) - An OptimoCMS API key
Step 1 — Prepare your Lovable project
Before importing:
- Publish your Lovable project — Make sure the site is live on a
.lovable.appURL - Note your design choices — Colours, fonts and border-radius you want to keep
- Check images — All images must be publicly accessible
Step 2 — Start the import via SDK
SDK
import { OptimoCMS } from '@optimocms/sdk';
const client = new OptimoCMS({
apiKey: process.env.OPTIMOCMS_API_KEY!,
});
const importResult = await client.import.fromLovable('site_abc123', {
projectUrl: 'https://my-restaurant.lovable.app',
name: 'My Restaurant (from Lovable)',
});
console.log(`Import started: ${importResult.importId}`);
console.log(`Status: ${importResult.status}`);
console.log(`Progress: ${importResult.progress}%`);MCP (Cursor / Claude)
In Cursor or Claude Desktop:
Import the Lovable project at https://my-restaurant.lovable.app to my OptimoCMS site "site_abc123". Name it "My Restaurant (from Lovable)".The MCP tool import_from_lovable is called.
REST
curl -X POST https://api.optimocms.com/v1/sites/$SITE_ID/import/url \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://my-restaurant.lovable.app",
"name": "My Restaurant (from Lovable)",
"includeImages": true,
"includeStyles": true,
"source": "lovable"
}'Response:
{
"importId": "imp_abc123",
"siteId": "site_abc123",
"status": "processing",
"progress": 0,
"pagesImported": 0,
"error": null,
"createdAt": "2026-05-26T14:00:00Z"
}Step 3 — Monitor import status
The import runs asynchronously. Poll the status until it's done:
SDK
async function waitForImport(siteId: string, importId: string): Promise<void> {
let status = await client.import.status(siteId, importId);
while (status.status === 'queued' || status.status === 'processing') {
console.log(`Import progress: ${status.progress}% (${status.pagesImported} pages)`);
await new Promise(resolve => setTimeout(resolve, 2000));
status = await client.import.status(siteId, importId);
}
if (status.status === 'completed') {
console.log(`✓ Import completed! ${status.pagesImported} pages imported`);
} else {
console.error(`✗ Import failed: ${status.error}`);
}
}
await waitForImport('site_abc123', importResult.importId);REST
curl https://api.optimocms.com/v1/sites/$SITE_ID/import/$IMPORT_ID/status \
-H "Authorization: Bearer $API_KEY"Response on completion:
{
"importId": "imp_abc123",
"siteId": "site_abc123",
"status": "completed",
"progress": 100,
"pagesImported": 5,
"error": null,
"createdAt": "2026-05-26T14:00:00Z",
"completedAt": "2026-05-26T14:02:15Z"
}Step 4 — Carry over design tokens
After import you can fine-tune the design tokens to exactly match your Lovable design:
SDK
const site = await client.sites.get('site_abc123');
console.log('Detected tokens:', site.designTokens);
await client.designTokens.update('site_abc123', {
colorPrimary: '#6366F1',
colorBackground: '#FAFAFA',
colorSurface: '#FFFFFF',
colorText: '#18181B',
colorTextMuted: '#71717A',
fontBody: 'Inter',
fontHeading: 'Inter',
radiusCard: '12px',
radiusButton: '8px',
radiusInput: '8px',
});
console.log('✓ Design tokens updated');MCP
View the design tokens of site "site_abc123" and change the primary colour to indigo (#6366F1) with Inter as font.Step 5 — Review and adjust content
After import you'll want to check the imported pages:
for await (const page of client.pages.list('site_abc123')) {
console.log(`Page: ${page.title} (/${page.slug}) — ${page.blocks.length} blocks`);
}
const homePage = (await client.pages.list('site_abc123')).data
.find(p => p.slug === 'home' || p.slug === '/');
if (homePage) {
await client.seo.update('site_abc123', homePage.id, {
title: 'My Restaurant — The finest Italian cuisine',
description: 'Reserve now at My Restaurant. Fresh pasta, pizza and antipasti in the heart of Amsterdam.',
});
console.log('✓ SEO updated for homepage');
}Step 6 — Publish
When everything looks good, publish the site:
SDK
const result = await client.sites.publish('site_abc123');
console.log(`✓ Site published: ${result.status}`);MCP
Publish site "site_abc123" to production.Full migration script
import { OptimoCMS } from '@optimocms/sdk';
const client = new OptimoCMS({
apiKey: process.env.OPTIMOCMS_API_KEY!,
});
async function migrateFromLovable(
lovableUrl: string,
siteName: string
): Promise<void> {
console.log(`🚀 Migration started: ${lovableUrl}\n`);
const site = await client.sites.create({
name: siteName,
subdomain: siteName.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
language: 'nl',
});
console.log(`✓ Site created: ${site.id}`);
const importJob = await client.import.fromLovable(site.id, {
projectUrl: lovableUrl,
name: siteName,
});
console.log(`✓ Import started: ${importJob.importId}`);
let status = importJob;
while (status.status === 'queued' || status.status === 'processing') {
await new Promise(resolve => setTimeout(resolve, 3000));
status = await client.import.status(site.id, importJob.importId);
console.log(` Progress: ${status.progress}%`);
}
if (status.status === 'failed') {
throw new Error(`Import failed: ${status.error}`);
}
console.log(`✓ ${status.pagesImported} pages imported`);
await client.designTokens.update(site.id, {
colorPrimary: '#6366F1',
fontBody: 'Inter',
fontHeading: 'Inter',
radiusCard: '12px',
radiusButton: '8px',
});
console.log('✓ Design tokens set');
const publishResult = await client.sites.publish(site.id);
console.log(`✓ Site published (${publishResult.status})`);
console.log(`\n🎉 Done! Your site is live at: https://${site.subdomain}.optimocms.com`);
}
migrateFromLovable(
'https://my-restaurant.lovable.app',
'My Restaurant'
).catch(error => {
console.error('Migration failed:', error.message);
process.exit(1);
});What gets imported?
| Component | Imported | Notes |
|---|---|---|
| Page structure | ✓ | Routes become pages |
| Text content | ✓ | Headings, paragraphs, lists |
| Images | ✓ | Copied to OptimoCMS Storage |
| Colours/fonts | ✓ | Converted to design tokens |
| Forms | Partial | Basic fields are carried over |
| Animations | ✗ | Must be rebuilt manually |
| Custom code | ✗ | JavaScript/React not importable |
Tips for a smooth migration
- Take a screenshot of your Lovable site before import, as reference
- Test on sandbox first — use a sandbox API key to test the import
- Check responsive — Verify the imported pages on mobile
- Preserve SEO — Set up redirects if you're changing domains
- Step by step — Import first, review, adjust, then publish
Next steps
- Build a restaurant site — Add extra features to your imported site
- Design tokens — More about the token system
- MCP setup for Lovable — Use MCP from Lovable