migrate-to-engine
Skill1 starsChanged 27 days agoMITView on GitHub
Engine projects only: finish moving a Buildpad project from Supabase to the Buildpad Engine. Rewrites the hooks, cron jobs and custom services the Engine could not run as they were (engine-plan/automation-to-review.json) for the Engine's runtime, and ports the app's code: environment, sign-in, data calls, files, administrator code, schema and access rules, database functions. Use after a project was moved, when .buildpad/project.json says the backend is engine, or when asked to port an app or automation to the Engine. On a Supabase project it explains the move and stops.
- Reads credentials
In this repository66 other files it gives its agents.
- add-ai-chatSkill
- add-buildpadSkill
- add-commentsSkill
- add-cronSkill
- add-external-oauthSkill
- add-filesSkill
- add-i18nSkill
- add-mcp-serverSkill
- add-microappSkill
- add-microfrontendSkill
- add-microfrontend-zonesSkill
- add-mobile-appSkill
- add-multitenancySkill
- add-usersSkill
- add-workflowsSkill
- amplify-env-varsSkill
- authentication-proxySkill
- buildpad-debugSkill
- buildpad-discoverySkill
- buildpad-implSkill
- buildpad-referenceSkill
- buildpad-reviewSkill
- buildpad-spec-batchSkill
- buildpad-spec-designSkill
- buildpad-spec-initSkill
- buildpad-spec-quickSkill
- buildpad-spec-requirementsSkill
- buildpad-spec-statusSkill
- buildpad-spec-tasksSkill
- buildpad-steering-customSkill
- buildpad-steeringSkill
- buildpad-validate-designSkill
- buildpad-validate-gapSkill
- buildpad-validate-implSkill
- buildpad-verify-completionSkill
- code-simplificationSkill
- context-engineeringSkill
- create-api-routeSkill
- create-collectionSkill
- create-componentSkill
- create-cronSkill
- create-featureSkill
- create-form-builderSkill
- create-migrationSkill
- create-projectSkill
- create-rbacSkill
- create-serviceSkill
- create-testsSkill
- create-workflowSkill
- daas-platformSkill
- debugging-and-error-recoverySkill
- generate-docsSkill
- git-workflow-and-versioningSkill
- grant-module-accessSkill
- hooks-extensionsSkill
- idea-refineSkill
- incremental-implementationSkill
- manage-scopeSkill
- performance-optimizationSkill
- planning-and-task-breakdownSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 4 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 15
Read .buildpad/project.json at the project root.
- Very specific: line 20
If a folder named supabase at the project root holds a file named config.toml, or holds a folder named migrations that holds a file whose …
- Distinctive: line 56
Use this skill on an Engine project that was moved from a Supabase project, or when the developer asks to port an app, a hook, a cron job …
- Distinctive: line 62
Part B, app code: every place where the app still talks to Supabase.
- Very specific: line 72
First: check that .buildpad/project.json (from the new project's starter kit) is committed and that its backend is "engine".
- Distinctive: line 125
Test it: run a custom service's tests.
- Distinctive: line 172
Run the migration tool's app scan on the repository if the developer has it, and work through the checklist below in order.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Migrate to the Buildpad Engine
<!-- backend-gate:begin v1 -->Establish the backend first. A Buildpad project's backend is either the Buildpad Engine or Supabase, and some parts of this file hold for one of them only. Before you act on anything in this file, find the backend with these steps, in order, and stop at the first step that answers:
- Read
.buildpad/project.jsonat the project root. If it is JSON and its fieldbackendis"engine"or"supabase", that is the backend. If.buildpad/backend-answer.jsonnames the other backend, tell the developer the two files disagree, and follow.buildpad/project.json. - Otherwise read
.buildpad/backend-answer.jsonin the same way; if itsbackendis"engine"or"supabase", that is the backend. - If a folder named
supabaseat the project root holds a file namedconfig.toml, or holds a folder namedmigrationsthat holds a file whose name ends in.sql, the backend is Supabase. - Otherwise ask the developer once: "Is this project's backend the Buildpad Engine or
Supabase?" Write the answer to
.buildpad/backend-answer.jsonas{"buildpadBackendAnswer": 1, "backend": "engine"}or{"buildpadBackendAnswer": 1, "backend": "supabase"}, and ask the developer to commit that file. Do not add it to.gitignore. Never write.buildpad/project.json.
To find the backend, do not open an environment file (.env, .env.*) or an MCP configuration
file, and do not call a tool that returns project credentials.
Then follow only the parts for that backend. A part starts at a line that holds only a comment
whose text is buildpad + -backend: engine or buildpad + -backend: supabase, and ends at the
next line that holds only a comment whose text is /buildpad + -backend. A comment that merely
mentions a backend is not a part. A file whose first line, after any front matter, holds only a
comment whose text is buildpad + -backend-file: engine (or supabase) holds for that backend
only, and so does every file in a folder named engine-only or supabase-only. Skip the other
backend's parts and copy no code from them; everything else holds for both. On an Engine project, when this file says that
something is not available, tell the developer so and stop: do not build a substitute.
<!-- backend-gate:end --><!-- buildpad-backend: supabase -->On a Supabase project
This skill is used after a project has moved to the Buildpad Engine. This project's backend is Supabase, so there is nothing to port yet. Tell the developer so, and stop: change no code for the Engine while the project runs on Supabase, because the app would stop working.
A project keeps its backend for its whole life. To move, the developer creates a new Buildpad
Engine project and moves the data, access rules, automation and app code to it, with the migration
tool from their platform operator. The steps are in the public guide
https://app.buildpad.ai/docs/migrate-to-engine. The new project's starter kit holds a
.buildpad/project.json whose backend is "engine"; once the app's repository commits that file,
this skill applies.
<!-- /buildpad-backend --><!-- buildpad-backend: engine -->Use this skill on an Engine project that was moved from a Supabase project, or when the developer asks to port an app, a hook, a cron job or a custom service to the Engine. The move (the operator's migration tool) has already copied the schema, the roles, policies and permissions, the users, the files and the records. Two things are left, and this skill does both:
- Part A, automation: each hook, cron job or custom service the Engine could not run as it was.
- Part B, app code: every place where the app still talks to Supabase.
On an Engine project with Customer_Tenants each customer of the application is a tenant of its own, and the project's tenant is the Blueprint every customer is copied from. Establish the tenancy mode as the add-multitenancy skill says: from the field tenancy of .buildpad/project.json, else from .buildpad/tenancy-answer.json. In the mode customer-tenants that skill's guide engine-customer-tenants replaces what ES-3, ES-4, ES-5, ES-7 and ES-9 say about the project's tenant; in the mode scope they hold as written.
On an Engine project with Customer_Tenants moving an application built on scopes into Customer_Tenants is not available: nothing moves a customer's rows, people or files out of the project's tenant into a tenant of its own. Tell the developer so and stop.
Removing what the Engine lacks is the work of this skill. Where a step below says a feature is not available on an Engine project, remove its code, tell the developer what you removed and what takes its place, build no substitute, and go on with the next step.
First: check that .buildpad/project.json (from the new project's starter kit) is committed
and that its backend is "engine". Work on a branch.
What the move leaves you
| What | Where | What it holds |
| Automation to rewrite | engine-plan/automation-to-review.json, in the folder the migration tool wrote | One entry per item: kind (extension, cron or service), id, name, file (the export file that holds the original row and its code), was_active, findings (each with rule, line and message) and a ready prompt that names this skill |
| Automation that passed | The new project, switched off | Copied as it was; switched on at cut-over |
| App-code places | The migration tool's app scan | file:line and a pattern id for each place that uses a Supabase-only feature |
| Permission rules to review | The move's report | Copied rules whose meaning changes on the Engine (see Permission rules that change meaning) |
The migration tool's folder holds users' emails, records and files. Read only the engine-plan
file and the one export file an entry names; copy nothing from that folder into the repository,
and never commit it.
Part A: Automation
On an Engine project stored code (hooks, cron jobs, custom services) runs in the Engine's runtime, which provides only what the table Engine runtime lists; for each interface it lacks, the table says what replaces it.
The runtime is JavaScript. The code is the body of an async function: await and return work at
the top level. Its globals are context, services, console, JSON, Date and Math, plus
payload, meta and HookError in a hook; a custom service reaches the services through
context.services and has no console. The services are listed, with every signature, in
hooks-extensions (its Engine services API reference).
Steps, for each entry
Work through custom services first (an entry with a dependency finding waits for the service it
names), then hooks, then cron jobs: hooks and jobs call services by name.
- Read the original. Find the row whose
idis the entry'sidin the export file the entry names (file), and read itscodeand its other fields (event,type,schedule,timezone,timeout_ms,tests,dependencies). If the developer points at code instead, start from that code. - Explain each finding to the developer: the line, what the Engine lacks, and what replaces it (the table below). The check is static: read the whole body for what it cannot see, such as the rows of Also check.
- Rewrite the body for the Engine's runtime and keep its behaviour: the same events, the same writes, the same mail and the same outside calls. Before-and-after examples for every finding are in Automation rewrites. Where the Engine cannot do the work at all (a library, a hash, a long job), move that work into an API route of the app (see create-api-route) or a custom service, and keep the stored code small.
- Show the rewrite and the list of what changed to the developer before you create anything.
- Create it switched off, with the original's
name, so callers find it: a hook withstatusinactive, a cron job withstatusinactive, a custom service withstatusdraft. Use the Studio (its Extensions, Cron and Custom Services modules) or, as an administrator's agent, the Engine's MCP toolsextensions,cronandservices, which are listed and served to an administrator only. If a tool is not listed, the project's Engine is older than 0.1.0-dev.4: use the Studio. Before you create an item, list the existing ones: an item of that name made by an earlier run is updated, not created twice. See hooks-extensions, create-cron and create-service. - Test it: run a custom service's tests; run a cron job by hand and read its history; for a hook, follow the check in hooks-extensions (activate it, write a scratch item, read the answer and the log, delete the item) and switch it off again. Before you switch on a hook that sends mail or calls an outside system, ask the developer: it acts for real.
- Leave it switched off until cut-over, so the old and the new project never both run it. At
cut-over the migration tool switches on the items it copied and the items you rewrote: it finds
a rewritten item by its kind and its name, and switches it on when the original's
was_activeistrue. So keep each rewritten item's original name. An item it cannot find is reported as still to rewrite. On an Engine project an active cron job fires at its scheduled time.
Engine runtime
The rule of each finding, what it means, and what replaces it.
rule | On the Engine | Replacement |
require, import, module | There are no modules: no package and no other file can be loaded | Inline a small helper; put shared code in a custom service (await services.custom('name')) that returns an object; move work that needs a package into an API route |
process | process does not exist | services.env.KEY (context.env.KEY in a custom service). It is empty unless the project's Environment Variables name the key: tell the user to name each key the code needs in the project's Environment Variables (Engine list) and apply it. Never write a secret into stored code: every administrator can read it |
fetch | The global fetch does not exist | services.fetch(url, init), to the hosts in the project's EXTENSION_ALLOWED_DOMAINS: tell the user to add each host in the project's Environment Variables and apply it. Send a string body (JSON.stringify(...)) |
timers | setTimeout, setInterval and the rest do not exist | No delay inside a run. A wait or a later retry becomes a cron job that picks up pending rows; a job can also be run at once with services.cron.trigger('name') |
eval | eval and Function are removed | Write the code out |
supabase | The Supabase service is refused permanently (501 RUNTIME_SUPABASE_UNAVAILABLE); there is no SQL and no Supabase client | services.items(collection), with { elevated: true } where a hook or custom service must act as an administrator; a database function becomes items calls or a custom service |
node-global | Buffer, URL, URLSearchParams, TextEncoder, atob, btoa, crypto, structuredClone, window and the other browser and Node globals do not exist | Build query strings with encodeURIComponent; copy with JSON.parse(JSON.stringify(x)); let a uuid field make ids; move hashing, signing and binary work into an API route |
services-member | That services.<name> is not part of the Engine's services | services.items, files, mail, fetch, env, cron, custom and the read-only schema services are all there is |
dependency | The custom service it needs is itself on the list | Rewrite that service first, under the same name |
empty | The item has no code | Ask the developer whether it is still needed |
Also check (what the scan does not flag)
readByQueryreturns the array itself: readresult, notresult.data.- A read returns 25 rows without
limit, and at most 1000. Query keys other thanfields,filter,sort,limit,page,offsetandsearchare refused (deep,aggregate,groupBy). - There is no query-wide update or delete: read the keys, then
updateMany(keys, data)ordeleteMany(keys). - In update and delete hooks the ids are
meta.keys(an array);meta.keyis set on create only. services.mail(...)does not throw when mail cannot be sent: checksuccess.- A filter hook refuses a write with
throw new HookError(message, status, code). consoleis not in scope in a custom service body;Intlis not in the runtime.- The services' write methods are
createOne,createMany,updateOne,updateMany,deleteOneanddeleteMany; schema services andservices.filesare read only.
The items the move copied passed the scan, so they can still hold one of these: test each one before cut-over.
Part B: App code
Run the migration tool's app scan on the repository if the developer has it, and work through the checklist below in order. The scan's pattern ids are given with each step. Details and before-and-after code are in App code.
On an Engine project the data API's address is the Engine_URL, which both NEXT_PUBLIC_BUILDPAD_DAAS_URL and NEXT_PUBLIC_SUPABASE_URL hold. The Studio's address is an administration interface: send no data call to it.
Checklist
- 1. Environment (
service-role,platform-supabase). Take the new starter kit's.env.localas it is. In every other place that sets the app's environment (.env.example, the deployment's settings, CI secrets), set these values and remove the service-role key variable. On an Engine project the environment is:NEXT_PUBLIC_SUPABASE_URL=<Engine_URL>,NEXT_PUBLIC_BUILDPAD_DAAS_URL=<Engine_URL>,NEXT_PUBLIC_SUPABASE_ANON_KEY=buildpad-engine-public-anon-key(a fixed value that is not a secret; the Engine does not read it, the Supabase client needs one), andNEXT_PUBLIC_BUILDPAD_TENANT_ID=<the project's tenant id>. There is no service-role key on an Engine project: add none. - 2. Sign-in (
client-auth,sign-up,stored-supabase). Sign in with a password through the app's own server route, as the Engine starter does: the login route of create-api-route sends the sign-in to<Engine_URL>/auth/v1/token?grant_type=passwordwith the headerX-Tenant-Idset toNEXT_PUBLIC_BUILDPAD_TENANT_ID. Remove the sign-up, magic-link, one-time-code, password-reset, change-password and change-email pages and routes, the OAuth buttons and the code-exchange callback route. For XAppHub sign-in, use the Engine variant in add-external-oauth. On an Engine project the Supabase client is kept for sign-in only. Password sign-in, session refresh, reading the signed-in user and sign-out work through it, and its default sign-out ends the user's sessions on every device. An account that holds no role in the project's tenant is refused at password sign-in. XAppHub sign-in also works through it while the project's XAppHub connector is connected: the application's server gives the ID token from XAppHub tosignInWithIdTokenwith the providermy-apps, and ends the session of an account that holds no role yet (theadd-external-oauthskill). Not available on an Engine project: sign-up; OAuth sign-in with any provider other than XAppHub; magic link and one-time code; password recovery; changing a password, an email or a profile through the Supabase client; the Supabase client's code exchange; and the Supabase administration API. The Platform's password-less handoff opens the Studio; it is not a way for an application to sign a user in. On an Engine project multi-factor enrolment, challenge and verification are not served through the Supabase client. The Engine's own routes, each called with the user's session, arePOST /api/auth/mfa/enroll,GET /api/auth/mfa/factors,POST /api/auth/mfa/verifyandPOST /api/auth/mfa/unenroll; a session that still needs its second factor is accepted elsewhere only afterPOST /api/auth/mfa/verify. - 3. Data calls (
rest,bare-items,cookies). Every data path starts with/api(/api/items/:collection,/api/files,/api/users/me). Replace the Supabase client's table calls and PostgREST addresses with the items API, through the app's forwarding routes (create-api-route) or the Buildpad SDK. Send no cookie and no tenant header after sign-in. On an Engine project every data call carries the signed-in user's access token asAuthorization: Bearer <access token>, from the application's own server route or from the SDK. The Engine accepts no cookie session on its data API. The token in the project's MCP configuration is accepted only on the MCP endpoint/api/mcp: never send it, or any other administrator token, to the data API. - 4. Administrator code (
service-role,auth-admin). Remove every Supabase client built with the service-role key. Do administrator tasks in the Studio, or in a server route that forwards a signed-in administrator's own access token. Static tokens on user records are not accepted by the data API: give their holders a signed-in session instead. On an Engine project an administrator creates every user who signs in with a password, through the Engine's user API (POST /api/userswith an administrator's session) or the Studio's Users module, and gives the user a role in the project's tenant (POST /api/users/:id/roleswithrole_id, or the Users module). A user's first XAppHub sign-in creates that user's account, which holds no role until an administrator gives it one in the same way. An administrator resets a user's password, in the Studio's Users module. For admin pages in the app, see add-users. On an Engine project some files the Buildpad CLI generates cannot be used yet:lib/supabase/admin.ts, the OAuth callback and provider routes of theexternal-oauthmodule, the server-side enforcer and session modules, andlib/module-access/enforce.ts. Do not call them; check module access by forwarding the user's token toGET <Engine_URL>/api/permissions/meand readingisAdminandmoduleAccess. For XAppHub sign-in the CLI installs theexternal-oauth-enginemodule in place ofexternal-oauth; its routes are used as theadd-external-oauthskill says. - 5. Files (
storage,storage-url). Replace Supabase Storage (calls onsupabase.storageand/storage/v1addresses) with the Engine's file routes; see add-files. The move kept every file's id, so a record that points to a file still works; code that named a file by bucket and path names it by its id now. Image transform parameters (width,height,fit,quality,format,withoutEnlargement, or a presetkey) work on/api/files/assets/:idand on signed URLs; a request for WebP or AVIF can be answered as JPEG or PNG. On an Engine project files are stored and served by the Engine:/api/files,/api/folders,/api/files/signed-url, and file content at/api/files/assets/:id(with a session or a signed token). A file shown in a browser, a public one included, is loaded through a signed URL from/api/files/signed-url, not by an anonymous read: an<img>element or a link sends noX-Tenant-Id. Set no storage bucket option: the Engine stores files on its own disk or in S3. - 6. Schema and access (
migration,migration-file,sql-policy). Delete the Supabase migrations folder (supabase/migrations) and the rest of thesupabasefolder once nothing in it is still to port (database functions and edge functions: step 7). Change the schema in the Studio or over MCP. Each row-level security policy becomes roles, policies and permissions: check that the moved permissions give the same rows, and add what is missing with create-rbac. Then review Permission rules that change meaning. The scan does not see three things; check each by hand (App code, step 6). Scope ids and URIs: scope items have new ids, and eachuri_pathis nested under the project's own item (/<project type id>:<tenant id>/<region type id>:<region id>, not/<type>:<id>); a hard-coded URI or id is answered 403FORBIDDEN_SCOPE. Role ids can change: find each role by name. Customdaas_usersfields were not moved: create them again. On an Engine project collections, fields, relations, roles, policies and permissions are created through the Engine's API (/api/collections,/api/fields/:collection,/api/relations,/api/roles,/api/policies,/api/access,/api/permissions) or its MCP toolscollections,fields,relations,roles,policies,accessandpermissions. Access is roles, policies and permissions: a permission belongs to a policy, and a policy is attached to a role or a user. The MCPcollectionsandfieldstools keep a field's name, type andmetabut not itsschemablock (default, not-null, length): set those throughPOST /api/fields/:collectionwith an administrator's session. - 7. Database functions (
rpc). Calls to the Supabase client'srpcmethod, database functions, triggers, views and edge functions did not move. Move each one's logic into an API route of the app, an Engine hook (hooks-extensions) or a custom service (create-service): a trigger usually becomes a hook, a function the app calls becomes an API route, and logic shared by hooks and jobs becomes a custom service. - 8. Cross-origin settings. Remove any code or setup that sets them. On an Engine project the data API accepts requests from any origin and never with cookies, as the project is provisioned. Change no cross-origin setting, and do not add the application's origin anywhere.
- 9. What sits around the app. Tests: build their users, sign-in and environment for the Engine (create-tests). A runtime worker: follow worker-messaging. A mobile wrapper: point it at the new deployment (add-mobile-app). The deployment: the app moves to a microapp of the new project, with the environment of step 1.
- 10. Verify. Run the app scan again (it should report nothing), then the build, the lint and the tests, and fix what fails. Sign in as a moved user with their old password, and read and write a record of each collection the app uses. Report to the developer what changed, what was removed, and what is left for cut-over.
On an Engine project with Customer_Tenants each customer of the application is a tenant of its own, and the project's tenant is the Blueprint every customer is copied from. Establish the tenancy mode as the add-multitenancy skill says: from the field tenancy of .buildpad/project.json, else from .buildpad/tenancy-answer.json. In the mode customer-tenants that skill's guide engine-customer-tenants replaces what ES-3, ES-4, ES-5, ES-7 and ES-9 say about the project's tenant; in the mode scope they hold as written.
Permission rules that change meaning
The move copied the project's permission rules. A rule that uses one of these variables matched as
null on the Supabase project, because Supabase did not resolve them, and resolves on the Engine:
$CURRENT_ROLES$CURRENT_ROLE.<field>$CURRENT_USER.role.<field>$NOWwith an adjustment, such as$NOW(-1 day)
Such a rule can now match rows it never matched before, so a user can see or change more than on the old project. The move flags each one for the project owner to acknowledge before cut-over. Show every such rule to the developer, with its policy, collection, action and filter, and say what it matched before and what it matches now. Change a rule only when the owner asks, through create-rbac. Rules the move could not copy at all are listed for rewriting in the move's report: rewrite them the same way.
<!-- /buildpad-backend -->Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

