Farther ShoreDocs
Go to Farther Shore
What is FartherShore
Install the CLI
Quickstart
Core concepts
The @Product class
Meters & resources
Features & routes
Capabilities & entitlements
Plans & pricing
The Manifest IR
Bring your own backend
Transport modes
Metering & verification
Runtime tokens
Frontend SDK
Root & data components
Auth & sessions
Entitlement gates
Connect Stripe
Subscriptions & usage
Plan changes & grandfathering
Billing strategies
Apply & deploy
Environments
Migrations
How it worksA typical change-then-migrate flowFor agents (MCP)See also
Docs versions & archive
Operate with an agent
Operation classes
MCP server
End-to-end via CLI/MCP
CLI reference
@farthershore/product
@farthershore/backend
@farthershore/farthershore-js
Environment variables
Response & deny codes
Add a metered capability
Gate a feature
Change a price
Prepaid credits
Meter AI tokens
Operate via an agent
Prepare for launch
Status
Docs/Operate live/Migrations

Migrations

Migrate subscribers between plan versions — an OPERATE verb you run against live state, never authored in the manifest.

PreviousEnvironmentsNextDocs versions & archive

When you change a plan, existing subscribers don't move automatically. By default they're grandfathered: they keep their current plan version (and price) at renewal, while new subscribers get the new version. Moving existing subscribers onto a new version is a migration — a transition over live subscriber state, not a property of the product.

That distinction is why migration is an operate verb, not part of your @Product class. A manifest is a snapshot of what the product is; a migration is a transition you run once, against subscribers who exist right now. There is no @Migration decorator — you run a migration via the CLI or MCP.

# Move "pro" subscribers from version 1 to the latest version at each
# subscriber's next renewal.
farthershore plan migrate croncloud pro \
  --from 1 \
  --to head \
  --policy next_renewal \
  --format json

How it works

You pass the plan key plus a source and target version. Each version is an integer (e.g. 1) or the alias head / latest; the server resolves them to the right plan versions. A policy decides when and how subscribers move.

FlagMeaning
<planKey>Positional — the stable plan key to migrate within (e.g. pro).
--from <version>Source version: an integer, or head/latest. Required.
--to <version>Target version: an integer, or head/latest. Required.
--policy <policy>When/how to migrate (see below). Required.
--proration <p>Optional billing reconciliation: none, prorate, or credit.
--complete-by <iso>ISO 8601 deadline — required by the by_date policy.

Policies

PolicyBehaviour
grandfatherKeep existing subscribers on their current version (the default — running it explicitly is a no-op transition).
next_renewalMigrate each subscriber at their own next renewal.
immediateMigrate everyone now.
by_dateMigrate everyone by a deadline (pass --complete-by).
opt_inLet subscribers choose when to move.
# Migrate everyone immediately, prorating the price difference.
farthershore plan migrate croncloud pro \
  --from 1 --to 2 --policy immediate --proration prorate --format json

# Migrate everyone by a hard deadline.
farthershore plan migrate croncloud pro \
  --from 1 --to head --policy by_date --complete-by 2026-09-01T00:00:00Z --format json

Migration is owner-only and idempotent — it accepts an idempotency key, so a retried call won't schedule the batch twice. Use --dry-run to preview the batch the policy would schedule without committing it.

Grandfathering is the verified default: do nothing and existing subscribers keep their old plan version (and price) at renewal. Run a migration only when you actually want to move them. immediate changes what live subscribers are billed — prefer next_renewal or by_date unless you mean to charge the difference now.

A typical change-then-migrate flow

  1. Edit the plan in the manifest and push — the new plan version ships, but existing subscribers stay on the old one (grandfathered). See Apply & deploy.
  2. New subscribers get the new version automatically.
  3. When you're ready to move existing subscribers, run a migration:
# 1. (in product/product.config.ts) bump pro's price, then push.
# 2. confirm the new version is live:
farthershore plan list croncloud --format json
# 3. move existing subscribers at their next renewal:
farthershore plan migrate croncloud pro --from 1 --to head --policy next_renewal --format json

For agents (MCP)

Migration is exposed over MCP as fs_plan_migrate (an operate action — it changes live subscriber state, not the code-managed definition). It takes productId, planKey, fromVersion, toVersion, policy, optional proration, and completeBy. Unlike plan create/update/delete — which are contract class and must be done in the repo — migration is intentionally callable via the API.

See also

  • Plans — defining and versioning plans in the manifest.
  • Operation classes — why migrate is operate, not contract.
  • Subscribers — the live state a migration moves.