What your code looks like
In an agent-led migration, Buildprint makes the base of your project first: your data types, option sets, API Connector calls and styles. Then the agents write your pages and workflows on that base. The agents choose how to divide each page into files, so the exact files change from app to app.
The examples on this page use an app with a Project data type and a Project Status option set.
Where each part of your Bubble app goes
In Bubble | In your code |
|---|---|
Page |
|
Reusable element |
|
Data type |
|
Privacy rules |
|
Option set |
|
Backend workflow |
|
API Connector call |
|
Styles |
|
App texts |
|
Folder structure
A project on Next.js and Supabase looks like this:
my-app/
├── AGENTS.md Instructions for coding agents
├── .buildprint/ Screenshots and notes that the agents use to check their work
├── supabase/migrations/ Creates your database tables
└── src/
├── app/ Your pages
│ └── dashboard/
│ ├── page.tsx The page
│ ├── actions.ts Actions that run on the server
│ ├── _components/ Parts of the page
│ └── workflows/ The page's workflows
├── components/
│ ├── reusable/ Your reusable elements
│ └── ui/ Buttons, inputs and other Bubble elements
├── db/schema/ Your data types
├── privacy/ Your privacy rules
├── options/ Your option sets
├── workflows/ Your backend workflows, in one folder for each area
├── integrations/ Your API Connector calls, in one folder for each API
├── i18n/ Your app texts
└── styles/ Your colors, fonts and named stylesA project on Convex has a convex/ folder instead of supabase/ and src/db/. Your data types and backend workflows go in it.
Example: a data type
The Project data type has the fields Name, Owner, Status, Due date and Budget. On Supabase, it becomes a table:
// Bubble data type ID: custom.project
export const project = pgTable(
"project",
{
id: text("id").primaryKey(),
// Bubble built-in field: Slug
slug: text("slug"),
// Bubble built-in field: Created By
createdBy: text("created_by"),
// Bubble field ID: budget_number
budget: doublePrecision("budget"),
// Bubble field ID: due_date_date
dueDate: timestamp("due_date", { withTimezone: true }),
// Bubble field ID: name_text
name: text("name"),
// Bubble field ID: owner_user
owner: text("owner"),
// Bubble field ID: status_option_project_status
status: text("status"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
modifiedAt: timestamp("modified_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [index("idx_project_owner").on(table.owner)],
);Each field keeps a comment with its Bubble ID, so you can find it in your Bubble app.
Each record keeps its Bubble ID. Links between records stay the same after you move your data.
Your app searches for projects by owner, so Buildprint adds an index for it. An index makes that search fast.
On Convex, the same data type looks like this:
export const project = defineTable({
bubbleId: v.optional(v.string()),
slug: v.optional(v.string()),
createdBy: v.optional(v.id("users")),
budget: v.optional(v.number()),
due_date: v.optional(v.number()),
name: v.optional(v.string()),
owner: v.optional(v.id("users")),
status: v.optional(v.string() /* option set option.project_status */),
modifiedAt: v.number(),
})
.index("by_bubbleId", ["bubbleId"])
.index("by_owner", ["owner"]);Example: an option set
The Project Status option set has the options Active and Archived, and a Color attribute:
// Bubble option set ID: option.project_status
export const projectStatus = {
bubbleId: "option.project_status",
label: "Project Status",
attributes: {
color: { bubbleId: "color_text", label: "Color" },
},
values: [
{ key: "active", label: "Active", bubbleId: "value.active", attributes: { color: "#16a34a" } },
{ key: "archived", label: "Archived", bubbleId: "value.archived", attributes: { color: "#6b7280" } },
],
} as const;
export type ProjectStatusValue = (typeof projectStatus.values)[number]["key"];Your code uses the option set in the same way as Bubble. For example, projectStatus.values is the list of all statuses.
Pages and workflows
The agents write each page and workflow by hand. They copy the behavior of your Bubble app, then add proof that it works. For example, screenshots or test results. Read Keep behavior the same to learn how they check it.
To tell the agents how you want the code to look, add your rules to AGENTS.md. For example, "Put each form in its own file."