# Migrating from Config Pages

If a code-native integration uses [config pages](https://prismatic.io/docs/integrations/code-native/config-wizard.md), you can publish a new version that uses headless configuration instead. Existing instances keep running on the config pages version until you update them, and your `init` function converts their config variable values to the new configuration.

## What changes[​](#what-changes "Direct link to What changes")

| Config pages                                      | Headless configuration                                                                                                                      |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `configPages` with `configVar()` elements         | A `configuration()` with a schema                                                                                                           |
| Prismatic renders the config wizard               | Your app renders the form                                                                                                                   |
| Values are strings in `context.configVars`        | Values have real types in `context.configuration`                                                                                           |
| Data sources supply dynamic options               | [Server functions](https://prismatic.io/docs/integrations/headless-configuration/server-functions.md) supply dynamic options                |
| Connections are config variables on a page        | [Connections](https://prismatic.io/docs/integrations/headless-configuration/connections.md) are declared in the configuration               |
| A flow's schedule can come from a config variable | A flow's schedule is fixed, or [supplied by the deployer](https://prismatic.io/docs/integrations/headless-configuration.md#scheduled-flows) |

## Migrate the integration[​](#migrate-the-integration "Direct link to Migrate the integration")

1. **Write the new schema.** Describe the values your flows need with their real types, such as `batchSize: z.number()` instead of a `"Batch Size"` string. See [Defining a schema with zod](https://prismatic.io/docs/integrations/headless-configuration/defining-schema-with-zod.md).

2. **Describe the old values.** Add a `configPagesSchema` that matches the config variables your config pages collected, keyed by name. Config pages store scalars as strings, so accept either form:

   src/configuration.ts

   ```typescript
   export const configPagesSchema = z.object({
     Region: z.string(),
     "Sync Enabled": z.union([z.boolean(), z.string()]),
     "Batch Size": z.union([z.number(), z.string()]),
   });

   ```

3. **Replace config pages with a configuration, and convert the old values in `init`.** Remove `configPages` from your integration, and pass a `configuration` that includes the `configPagesSchema`.

   An instance that was configured with config pages has never saved a headless configuration, so in `init`, `context.configurationVersion` is `null`. In that case, `context.configuration` holds the config page values, keyed by config variable name. Spectral types it from your `configPagesSchema`, with every field optional. Convert each value, and report anything you can't carry over:

   src/configuration.ts

   ```typescript
   const DEFAULTS: Configuration = {
     region: "us-east",
     syncEnabled: true,
     batchSize: 100,
   };

   export const integrationConfiguration = configuration({
     instance: {
       schema: configurationSchema,
       version: "order-sync-v1",
       configPagesSchema,
     },
     init: {
       perform: async (context) => {
         if (context.configurationVersion === null) {
           // Typed from configPagesSchema, with every field optional
           const legacy = context.configuration;
           const batchSize = Number(legacy["Batch Size"]);
           const validBatch = Number.isInteger(batchSize) && batchSize > 0;
           return {
             configuration: {
               region: legacy.Region ?? DEFAULTS.region,
               syncEnabled:
                 legacy["Sync Enabled"] === undefined
                   ? DEFAULTS.syncEnabled
                   : String(legacy["Sync Enabled"]) === "true",
               batchSize: validBatch ? batchSize : DEFAULTS.batchSize,
             },
             unresolvedReasons: validBatch
               ? []
               : [
                   `Batch size "${String(legacy["Batch Size"])}" isn't valid; using 100.`,
                 ],
           };
         }
         // Handle the current version as shown in Configuration versions and init
         const saved = configurationSchema.safeParse(context.configuration);
         return {
           configuration: saved.success ? saved.data : DEFAULTS,
           unresolvedReasons: [],
         };
       },
     },
   });

   ```

4. **Read configuration in flows.** Replace `context.configVars` reads with `context.configuration`, and delete the string parsing your flows no longer need.

5. **Replace data sources with server functions** and declare your connections in the configuration.

6. **Replace config variable schedules.** A flow whose schedule reads a config variable, `schedule: { configVar: "..." }`, won't build once the integration has a configuration. Change it to a fixed `{ value }` schedule, or to `{ fromDeployer: true }` so the customer chooses the schedule in your app.

7. **Publish the new version** and make it available in your marketplace.

Always migrate through init

An instance on a config pages version has no headless configuration value, and your app can't read its config variables directly. The only way across is the new version's `init`, followed by `save`.

note

If an instance's config page values are missing, return sensible defaults and explain them in `unresolvedReasons`, so the customer can review the values before saving.

## Migrate instances[​](#migrate-instances "Direct link to Migrate instances")

To your app, the new version appears as an update that requires reconfiguration:

1. Your app sees `update.requiresReconfiguration` on the instance.
2. It opens the update's configuration and runs `init`, which converts the config variable values.
3. The customer reviews the suggested values, and any `unresolvedReasons`, in your form.
4. Your app saves the configuration, which moves the instance to the new version, and deploys it.

Until you migrate an instance, it stays on a config pages version. Its configuration experience is `hosted`, so your app can keep opening Prismatic's config wizard for it. See [Building configuration UIs with Solis React](https://prismatic.io/docs/embed/headless/react.md#routing-between-headless-and-hosted-configuration).
