OptimoCMSDocs
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:

  1. Publish your Lovable project — Make sure the site is live on a .lovable.app URL
  2. Note your design choices — Colours, fonts and border-radius you want to keep
  3. 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?

ComponentImportedNotes
Page structureRoutes become pages
Text contentHeadings, paragraphs, lists
ImagesCopied to OptimoCMS Storage
Colours/fontsConverted to design tokens
FormsPartialBasic fields are carried over
AnimationsMust be rebuilt manually
Custom codeJavaScript/React not importable

Tips for a smooth migration

  1. Take a screenshot of your Lovable site before import, as reference
  2. Test on sandbox first — use a sandbox API key to test the import
  3. Check responsive — Verify the imported pages on mobile
  4. Preserve SEO — Set up redirects if you're changing domains
  5. Step by step — Import first, review, adjust, then publish

Next steps

On this page