# CMS Migration

Move posts, categories, and pages from another CMS into the erxes CMS


Update an older website integration to the current erxes API. This is an application-integration migration, not a database migration or a guarantee that every older deployment exposes the same schema.

## 1. Inventory the current integration

Record the deployed erxes revision, website dependency versions, gateway URL, credential headers, GraphQL operations, and CMS/portal associations. Identify which requests run in the browser and which already run on the server.

Distinguish public website reads from authenticated administration, previews, portal-user actions, and mutations. They may require different credentials and authorization.

## 2. Move credentials into server modules

Follow [Setup](https://erxes.io/docs/content-plug-cms-next-developers-setup). Remove credential values from `next.config.env`, `NEXT_PUBLIC_*` variables, static configuration files, and client components. Rebuild the website to remove those values from newly generated browser assets.

The public examples use a Client Portal token through `x-app-token`. Existing administrative integrations may continue to require an Apps token through `erxes-app-token`; keep those behind their own authorized server interface.

If an Apps credential was published in browser assets, treat it as exposed and coordinate revocation and replacement through the application's administration. The [portal JWT path](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/core-api/src/modules/clientportal/services/auth/jwtManager.ts) verifies the signature and portal existence but does not compare the submitted JWT with the portal's stored token ([`ClientPortal.token`](https://github.com/erxes/erxes/blob/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/core-api/src/modules/clientportal/db/models/ClientPortal.ts)). Changing `ClientPortal.token` alone is therefore not proof that older JWTs are invalid.

## 3. Update operation names and shapes

Compare each operation with [Queries](https://erxes.io/docs/content-plug-cms-next-developers-queries) and the schema for your installed version.

Common corrections:

- Use `categoryIds: [String]` for post filters, rather than `categoryId`.
- Most list operations use `limit` and `cursor`. The dedicated `cpPostListWithPagination` operation accepts `page` and `perPage`.
- A post list envelope contains `posts`; a page list contains `pages`; a category list contains `list`; a tag list contains `tags`.
- The post detail field is `cpPost`; there is no `cpPostDetail`.
- The page detail field is `cpCmsPageDetail`.
- A menu item's field is `contentTypeId`, with a lowercase final `d`.
- The `Author` union contains `User`; do not add an unsupported `ClientPortalUser` fragment.

The [schema directory](https://github.com/erxes/erxes/tree/f91e8c6f4714c910a32c6b6e14fe085f6435c89e/backend/plugins/content_api/src/modules/cms/graphql/schemas) defines these operations and fields. A nullable schema argument may still be required by a resolver, so inspect runtime behavior as well.

## 4. Enforce publication policy

Switching an operation from `cms*` to `cp*` does not automatically hide draft content. Post lists apply the requested status, while post detail reads do not add a published-only filter.

Use a fixed `status: published` filter for public post lists. Verify the returned status before exposing a detail record or using its fields in metadata. Check pages, menus, categories, and other content families individually; do not infer their policy from the post API.

The [SSR guide](https://erxes.io/docs/content-plug-cms-next-developers-ssr) demonstrates published-only list and detail handling. The [CSR guide](https://erxes.io/docs/content-plug-cms-next-developers-csr) exposes a fixed website search endpoint with bounded inputs.

## 5. Rendering and caching

Keep server credential modules outside the browser dependency graph. A component's imports become part of the client graph when reached through a client boundary, even if each imported file does not declare `"use client"`.

The Setup helper uses `cache: "no-store"`. Decide on a deliberate caching policy before adding revalidation settings; a page-level revalidation declaration does not override an explicitly uncached fetch.

The `cpPost` detail resolver increments a view counter. Repeated metadata and page queries can increase it, and cached results can reduce counted reads. Verify analytics expectations separately from content rendering.

Render stored content according to its format. Sanitize untrusted HTML before using an HTML insertion API, and preserve the application's image and link handling.

## 6. Test before switching traffic

Test these cases against a staging instance with known records:

- Published and draft posts, including direct detail URLs and metadata.
- Unknown slugs and an empty portal.
- Cursor boundaries, filters, and expected response envelopes.
- Missing/invalid credentials and unavailable upstream services.
- Browser requests and built assets without erxes credentials.
- Rejected unsupported parameters and operations.
- Authenticated previews or mutations through their separate authorized flows.
- Image URLs and rich content rendering.

If introspection is enabled in the development instance, compare the live schema with the query documents. Otherwise use the installed source schema or an administrator-provided export. A production deployment may intentionally disable introspection.

Keep the previous website build and configuration available during rollout. Restoring a website build does not reverse product database migrations; handle a product upgrade according to its own migration procedure.

## Troubleshooting

- **401/403 or GraphQL auth errors after migration**: the wrong credential family is being sent; the website needs the Client Portal token in `x-app-token`, not an Apps token, and not a `client-auth-token`.
- **Valid token, empty lists**: the portal matched but has no matching records; re-check `clientPortalId` on posts, pages, and menus after a portal change.
- **`Unknown argument` errors**: `page`/`perPage` only exist on `cpPostListWithPagination`; other lists take `limit`/`cursor`. Check the [Queries reference](https://erxes.io/docs/content-plug-cms-next-developers-queries).
- **Drafts visible on public routes**: a `cp*` prefix does not filter publication status; enforce `status: published` on lists and check `status` on detail reads.
- **Stale content after deployment**: the Setup helper uses `cache: "no-store"`; if you added caching, re-check `revalidate` settings and any CDN layer.

