How do I migrate a legacy public app to projects?
TL;DR — hs app migrate wraps a project around your legacy public app: features become source files, Build #1 is your baseline, and credentials, installs, and listings survive. Before running it: disable the GitHub integration, catalog your settings page, plan the legacy-CRM-card replacement, and, with v1/v3 timeline events, be ready to move to v4 within 7 days. The token model changes too.
What does the migration preserve?
Per HubSpot's public-app migration guide, the operation is designed to be invisible to your customers. Original auth credentials, every existing install, your marketplace listing, and supported features (app configuration, webhooks, custom workflow actions) all carry over. Your backend doesn't change. Nobody reinstalls.
What you get in exchange is the projects framework: your app's configuration becomes source files you version, build, and deploy through the CLI — which is also the prerequisite for everything modern (app cards, UI extensions, and the path to 2026.03).
Which four things don't carry?
Audit these before running anything.
1. Timeline events start a 7-day clock
If your app has a v1/v3 timeline event, migrating starts a hard countdown: after 7 days, calls to the old timeline endpoints return 401. This is the one item that breaks a running backend on a timer, so treat the v4 endpoint switch as part of the migration change, deployed the same day, not a follow-up ticket.
2. The GitHub integration must be off before you migrate
If your project auto-builds from GitHub, disable the integration first, migrate, then re-create CI/CD with GitHub Actions. Migrating with it enabled is an unsupported state.
3. Settings pages must be rebuilt
Legacy app settings pages don't convert. HubSpot's own advice: catalog every interface element in production before migrating, then recreate the page as a UI extension afterward. If you skip the cataloging step, you're reverse-engineering your own app from memory.
4. Legacy CRM cards aren't converted
hs app migrate neither converts nor refuses a legacy CRM card: the card keeps serving on the app until HubSpot's October 31, 2026 sunset, and replacing it is a separate step with its own deadline and its own guide. For marketplace apps, note the interplay: app cards are auto-hidden after migration for beta testing, and the feature-flags API controls the rollout (at most five accounts until HubSpot's app-card review approves wider distribution).
And one architectural change to brief your backend team on: the permission model moves to service accounts. New installs create a service-account user tied to your app, each account holds one refresh token no matter how many users reauthorize, and the OAuth token-metadata endpoints return a serviceAccountId after migration. If your token storage assumes a refresh token per authorizing user, fix that assumption before you migrate.
How do I run it?
CLI version matters: 7.6.0+ for this flow (and 8.4.0+ if you're continuing to 2026.03 — which you should, the functions question is settled there). One prerequisite from HubSpot's migration overview: if the app is unlisted and installed in 10 or more accounts, you must list it on the Marketplace before migrating. Then:
hs app migrateHubSpot also offers hs project clone-app, which copies the app into a new project without migrating the original; its own version of the testbed idea. HS-X's dupe goes further by re-expressing the app in 2026.03 terms.
You'll pick the app, confirm the feature list, choose a project name and local path, and assign UIDs to features. UIDs are permanent identity — change one in a later build and the platform treats the feature as new, with all the detachment that implies. Choose deliberately; never edit casually.
Confirmation triggers a single atomic operation: project created, features converted to source files, Build #1 built and deployed (completing the app↔project association), and the source downloaded to your machine. Build #1 is your safety rail — it captures the migrated state as a baseline you can redeploy to roll back later changes. It is not an un-migrate: after this point, project-defined features are no longer editable in the app management UI or the old developer APIs.
Where does HS-X fit?
The pattern this page keeps repeating — know what won't carry before running the one-way command — is what hs-x migrate inspect mechanizes. A UI-configured app has no project tree to point at, so the entry is hs-x migrate pull: it lists the migratable apps in your developer account (the same list hs app migrate picks from, with ineligible apps shown alongside their reason), pulls the one you choose, and writes a migration.json covering its cards, webhook subscriptions, and workflow actions. inspect then classifies every feature: what converts automatically, what needs human review (webhook and action forwarding targets, legacy-card ticket support, Lambda snippets in workflow actions), and why, with no side effects. HS-X's larger migration model takes the same caution further: generate the modern app as a separate testbed, verify it live against a real portal, and only then touch the app your customers have installed.
hs-x migrate pull # pick the live app; writes migration.json (cards, webhooks, workflow actions)
hs-x migrate inspect migration.json # what carries, what needs review — before anything is one-way
hs-x migrate run migration.json --out ./my-app-hsx # generate the separate testbedpull needs the credentials stored by hs-x connect hubspot (personal access key plus developer account ID); add --developer-api-key when you connect, or the legacy cards, webhooks, and actions surfaces tend to come back empty.
Last updated: August 27, 2026. Sourced from HubSpot's public-app migration guide. Refreshed whenever HubSpot changes the migrate command or what it preserves.