Migrate from Framer
Move a published Framer site into Modulify, see what each part of it becomes, where the pages and CMS data are stored, and how to fix a failed import.
On this page
A Framer migration copies your published Framer site into a new Modulify site: its pages, CMS collections and items, images, fonts, languages, forms and redirects. It reads the project through Framer's API with a key you create, and reads each page as Framer publishes it. The import dialog itself is covered in Import from Framer.
Before you start
- Publish the site in Framer, a few minutes ahead. Only published pages are read, so draft pages are left out, and a page with changes you have not published yet comes across as it was last published. The import names those pages in a warning. Right after a publish, Framer serves pages empty while it prepares them. The import waits for them for a while, but a page that is still not ready is left out.
- Have three things ready: the address your site is published at, the link to the project in Framer, and an API key from the project's settings. Run the migration shows where to find each.
- Start from Home. A Framer import creates a new site, so From Framer is greyed out inside a project's chat.
The key reads the project as you. Modulify keeps it encrypted with your site, and you can delete it in Framer once the import has finished.
How the migration works
The migration runs in the background once the site is created. It reads from two places:
- Framer's API, with your key: the project, where it is published, its languages, its page list, the CMS collections with their fields and items, the redirects, the custom code from your site settings, the color and text styles, and the code components.
- The published site: every page Framer publishes, found through the sitemap and the page list. Pages hidden from search engines, each published CMS item page and the pages in each language are read too.
The import shows in the site's chat as a normal message, Importing <site name> from Framer. The AI takes over as soon as it finishes., with a row under it that goes one step at a time, from Reading the site to Copying images, Saving the data for the AI, Building pages and Saving pages. When it ends, the message reads Imported <site name> from Framer., the row of steps reads Did 5 actions, and any Warnings from the import are listed below it. While a step reads pages, copies images and files, or saves records, its row counts them. If the import stops for a reason that is not yours to fix, it starts again on its own once. If it stops again, or for a reason you have to fix, the chat says so, with the step it stopped at, the reason and Try again.
Each page is converted into an editable React page at the same address. The images and files it uses are copied to the site's storage, the fonts and stylesheets into the site's code, and the CMS items into the site database, where the AI builds the CMS tables from them as soon as the import finishes. The pages are copied rather than rebuilt, so the site starts as close to the Framer original as the import can get.
Run the migration
Publish the site in Framer
Publish the latest version of the site, so the pages and CMS items you want are live.
Copy the project link
Open your project in Framer and copy the link from the address bar. It looks like framer.com/projects/Name--abc123.
Create an API key
In Framer, open Site settings and choose General. Under API Keys, create a key and copy it.
Open the import dialog
On Home, click + on the prompt, then choose From Framer under Import.
Fill in the dialog
Enter the Site address, the Framer project link and the Framer API key, tick I own this site, or I have permission to rebuild it., then select Confirm. The dialog checks the key and checks that the project is published at the address you entered.
Start the import
The prompt shows the site as a card with a screenshot of it and what the import found. Select Import site to create the site and start the migration.
Read the warnings
When the chat shows Did 5 actions, read Warnings from the import below it. Each one names what did not come across and why.
What comes across
| In Framer | In Modulify |
|---|---|
| Published pages | Editable React pages at the same addresses, with their title, description, favicon, social share tags and robots tag |
| Pages hidden from search engines | Imported like every other page, and left out of the sitemap |
| CMS item pages | One page for each published item, at the same address |
| Languages | The same addresses with each language's prefix, such as /fr, and the site's language list |
| Stylesheets and breakpoints | Kept as Framer publishes them, so each screen width looks as designed |
| Fonts hosted by Framer | Copied into the site. Google Fonts load from Google as before |
| Images, videos and files hosted by Framer | Copied to the site's storage |
| Entrance effects and tickers | Entrance effects play when they scroll into view and tickers keep scrolling. Visitors who ask for reduced motion see everything in place |
| Language picker | Lists the languages the page is published in and opens the page in the one a visitor picks |
| CMS collections and items | Saved in the site database, then built into tables by the AI right after the import |
| Translated CMS text, links and slugs | Kept with each item, and a column per language in the tables the AI builds |
| Draft CMS items | Saved with the other items and marked as drafts, without pages of their own |
| Required CMS fields | Required in the tables the AI builds, when every item has a value |
| Image alt text in the CMS | A field next to the image |
| Forms | The same fields, with each submission saved in the site database and emailed to the site's address |
| Redirects | Redirects in the site, copied for each language when a redirect applies to all languages |
| Custom code from site and page settings | Kept on each page, with scripts loading from the addresses they used on Framer |
| robots.txt | Copied without its sitemap lines, which point at Framer's address |
| Sitemap | A new /sitemap.xml that lists the imported pages |
| Color styles and text styles | Saved for the chat in .modulify/import/design.json |
| Code components and overrides | Their source saved for the chat in .modulify/import/code, for reference only |
How the data is stored
Pages. Each route in src/pages renders a page component in src/components/framer that holds the published markup. The stylesheets are in public/framer/css and the fonts in public/framer/fonts. The markup depends on Framer's class names, so keep them when you change a page.
CMS collections. The import does not create tables. It saves every item, published or draft, in the hidden _import_rows table of the site database, one row per item: the collection it belongs to in dataset, the Framer item's id in id, its slug in slug, and its fields in data, a JSON object:
- Plain text, formatted text, numbers, toggles, colors, dates, links, images and files keep their values. Images and files hold the address of the copy in the site's storage.
- Option fields store the name of the chosen option.
- A gallery is a list of images, and other lists are kept as structured data.
- A field that links to one item holds that item's Framer id, and a field that links to several items holds a list of ids.
- Translated text, formatted text and links are under
_translations, keyed by language code and then by field, with the item's slug in that language when it differs. - A field that Framer fills from another field when it is empty holds that field's value, the way the site shows it.
- An image field whose items have alt text gets an extra field next to it that holds the alt text.
- A draft item has
_draftset and no page of its own. _createdand_updatedkeep the item's dates from Framer.
As soon as the import finishes, the AI builds the CMS tables from these items and fills them, usually one table per collection, with references between the tables and a column for each language, as Multiple languages describes. It picks the names of the tables and columns itself, and the CMS shows them like any other table, while _import_rows stays hidden. How migrations work explains this step for every platform.
Form submissions. Every form posts to /api/forms/<name>, which creates the form_submissions table of the site database on the first submission and saves every submission there.
Notes for the chat. The import saves .modulify/import/README.md, with the same details in manifest.json beside it: the pages, which pages were copied from CMS items and from which collection, a "Data from Framer" section that lists each collection with its item count, its fields and the collections its links point to, the languages, the pages that need work, what did not come across and the warnings. It adds a section to the site's guide file, CLAUDE.md, that points the chat at these notes.
What does not come across
- Interactions that Framer runs with its own code: hover and press effects, menus that open and close, sliders, tabs, accordions and site search. A menu's open state is not in the published page at all, so it has to be built again.
- Components that only appear once Framer's code runs in the browser, such as icons from an icon set. They come across empty, and the import lists them as a warning.
- Redirects that use a wildcard, and redirects whose page was removed in Framer. Each one is listed as a warning.
- Draft pages, and changes to pages that are not published yet. Draft CMS items do come across, marked as drafts.
- The canonical link and the links to other languages in each page's head, because they point at the Framer address.
- Smaller copies of images: every resized version of an image uses the copied original.
- Form submissions that Framer already holds, and Framer Analytics.
- What is over the import's size limits: a very large site can have more pages, collections or items than one import brings across. A warning says when that happens.
After the migration
When the migration finishes, the AI takes over on its own. It reads the notes, builds the CMS tables from the saved items, connects the pages that show them, such as a blog list or the copied CMS item pages, to those tables while keeping them looking the same, and checks the pages. Then it answers with the pages, the tables it built with their row counts, the languages, the warnings and what still needs work. If you were already chatting, it gets the notes with your next message instead. From then on the chat builds on the imported site and never starts it over, and when it needs more than the notes it reads your Framer project directly through the migration's connection.
- Open the preview and check each page against your Framer site.
- Ask the chat to rebuild the interactions the warnings list. The notes tell it which pages need work and what Framer ran in the browser.
- Check the tables the AI built, and edit content in the CMS. A CMS item page that does not read its table yet is a copy, so an item you add later gets no page of its own until you ask the chat to turn the copied pages into one page that reads the table.
- Publish the site, then connect your domain when you are ready to move it from Framer.
- Keep the API key while you want the chat to read your Framer project. Once you delete it in Framer, the chat works from what was imported.
Troubleshooting
The dialog says "That does not look like a Framer project link"
The project link was not recognized. Open the project in Framer, copy the link from the address bar, and paste it into Framer project link. It looks like framer.com/projects/Name--abc123.
The dialog says "That token was rejected"
Framer refused the API key. Check that you copied the whole key, and that it was created in this project under Site settings, General, API Keys.
The dialog says "That token is valid but belongs to a different site"
The key works, but its project is not published at the address you entered. Check the Site address, or create the key in the project that is published there.
The dialog says "The platform is rate limiting us right now" or "We could not reach the site to check that token"
Framer is busy or could not be reached. Wait a minute and try again.
The dialog says "That does not look like a Framer site"
The Site address does not lead to a site published with Framer. Enter the address Framer publishes the site at, such as yoursite.framer.website or your own domain.
The import failed with "Framer did not let Modulify read the project"
The API key was deleted or no longer works. Create a new key in the project's settings, then start the import again from Home.
The import failed with "Publish the site in Framer before importing it"
The project has no published version. Publish it in Framer, then start the import again.
The import failed with "No published pages could be read from Framer"
None of the published pages could be read. Check that the site opens at its published address, then start the import again.
The import failed with "Framer had not finished preparing the published pages"
Framer was still preparing every page after the last publish, so they all came back empty. Wait a few minutes, then start the import again.
A warning says "Framer had not finished preparing the page"
That page was still empty on Framer when the import gave up waiting, so it was left out. Wait a few minutes, then import the site again to bring it across.
The import failed with "The import stopped before it finished"
The import stopped making progress, so it was started again on its own once, and that try stopped too. Choose Try again in the alert to run it again.
The import failed with "The import took longer than an import is allowed to run"
The import ran for too long, so it was stopped and started again on its own once, and that try ran too long as well. Choose Try again in the alert to run it again.
A warning says some components are empty
Components such as icons from an icon set only appear once Framer's own code runs in the browser. Ask the chat to add them back. The notes in .modulify/import/README.md list the pages that have them, under "Pages that need work".
A warning says "The imported data could not be saved in the site database"
The CMS items could not be saved for the AI, so it reads them again from your Framer project through the import's connection to build the tables. Keep the API key until the tables are built.
A warning says "The preview did not reload with the imported pages"
The pages were saved, but the preview had not loaded them yet. It shows them after its next sync.