Migrating to Arranger 3.1
This release represents a dramatic evolution in Arranger's internal architecture. It also introduces "multicatalogue" support, support for OpenSearch deployments, improvements to the network search federation, preliminary implementation of an MCP server (work in progress), and significant infrastructure improvements alongside several breaking changes.
This guide covers what you need to change when upgrading from 3.0.x. The companion Helm chart 0.4.0 reflects the same breaking changes for Kubernetes deployments, and you may use it as additional reference for any values that changed.
Breaking changes
Environment variables renamed
Two environment variables were renamed. Update your .env files, container environment configurations, and Helm values accordingly.
| Old name | New name | Purpose |
|---|---|---|
PORT | SERVER_PORT | HTTP port the server listens on (default: 5050) |
SEARCH_CLIENT_TYPE | SEARCH_ENGINE | Search engine type: opensearch or elasticsearch |
Docker image renamed
The search server Docker image was renamed to reflect that it is one of several server applications in the monorepo:
ghcr.io/overture-stack/arranger-server -> ghcr.io/overture-stack/arranger-search-server
Update any docker-compose.yml, Helm values files, or deployment manifests that reference the image name.
MAX_RESULTS_WINDOW is now enforced
MAX_RESULTS_WINDOW was listed in the environment schema in 3.0 but was not yet applied at runtime. In 3.1 that variable actively caps the number of hits returned per query, defaulting to 10000.
If your deployment returns more than 10,000 documents per query, set MAX_RESULTS_WINDOW explicitly in your environment, or override it per catalogue via table.json:
{
"table": {
"maxResultsWindow": 50000
}
}
Per-catalogue config always wins over the environment variable. Use this with caution, to prevent overloading your search engine.
What doesn't change
If you currently run one Arranger instance per catalogue, no changes are required for the upgrade itself. The flat config layout (JSON files placed directly inside CONFIGS_PATH) continues to work as a single-catalogue deployment in 3.1. Multicatalogue support is additive; it activates only when CONFIGS_PATH contains subdirectories.
If you want to consolidate multiple instances into one server after upgrading, see Consolidating multiple instances below.
Deprecations
configsSource parameter will be removed in v3.2
If you use @overture-stack/arranger-graphql-router directly (rather than through apps/search-server), the configsSource string parameter on the router factory is deprecated in 3.1 and will be removed in 3.2. Migrate to passing a configs object directly. See the graphql-router README for the updated API.
New features
These additions require no migration for existing deployments. See the CHANGELOG for the full feature list with details.
Recommended migrations
These changes are not required (existing behaviour still works), but they align your integration with 3.1's recommended patterns and will make future upgrades easier.
Replace hasValidConfig with the introspection API
If your UI performs a startup check against Arranger using the hasValidConfig GraphQL query, switch to GET /introspection instead. hasValidConfig ties your frontend to the ES index name and is a candidate for deprecation in a future release.
The introspection endpoint returns the registered catalogues and their document types, which is what the check actually needs:
GET /introspection
{
"catalogs": {
"participants": { "documentType": "participant", ... },
"variants": { "documentType": "variant", ... }
}
}
Replace the per-catalogue hasValidConfig calls with a single preflight fetch:
const checkArrangerCatalogues = async () => {
const response = await fetch(urlJoin(apiUrl, 'introspection'));
if (!response.ok) throw new Error('Arranger introspection endpoint unavailable');
const { catalogs } = await response.json();
const documentTypes = Object.values(catalogs).map((c: any) => c.documentType);
const expected = [PARTICIPANTS_DOCUMENT_TYPE, VARIANTS_DOCUMENT_TYPE];
const missing = expected.filter((dt) => !documentTypes.includes(dt));
if (missing.length > 0) {
throw new Error(`Expected catalogues not registered: ${missing.join(', ')}`);
}
};
Your gateway or BFF layer will need a GET /introspection route that forwards to Arranger's introspection endpoint. Once this is in place, the INDEX_NAME env vars (which held ES index names that were never queried directly by the UI) can be removed from the frontend config.
See Introspection API for the full response schema.
Lock down CORS in production
3.1 adds ALLOWED_CORS_ORIGINS to control which origins may access Arranger. When unset, all origins are permitted: suitable for local development but not for production.
ALLOWED_CORS_ORIGINS=https://your-portal.example.com,https://admin.example.com
The value is a comma-separated list. When present, Arranger passes it to the cors middleware as an origin allowlist; all other origins receive a 403.
If your deployment routes all Arranger traffic through a backend-for-frontend or reverse proxy that enforces access control, open CORS may be acceptable. For any deployment where Arranger is accessible to untrusted clients, set this variable explicitly.
Set GraphQL query complexity limits
3.1 introduces two optional query validation limits:
| Variable | What it limits |
|---|---|
GRAPHQL_MAX_ALIASES | Maximum number of aliased fields per query |
GRAPHQL_MAX_DEPTH | Maximum nesting depth of a GraphQL selection set |
If left unset, both still default to conservative limits: 15 aliases and a nesting depth of 7. These defaults are enforced in code (not visible via the GraphQL schema itself), so a client that was working against an older Arranger version, or against a deployment that hasn't set these variables, may start seeing validation errors on queries that exceed them after upgrading. Set GRAPHQL_MAX_ALIASES/GRAPHQL_MAX_DEPTH explicitly to override the defaults for your deployment's actual query shapes.
These can also be set per catalogue in base.json under maxAliases and maxDepth, which takes precedence over the environment variables.
Whether the built-in defaults (15 aliases, depth 7) suit your deployment depends on the queries your frontend issues. Check your GraphQL operation depth and alias count against them before relying on the defaults, or before overriding them, to avoid breaking production traffic in either direction.
Consolidating multiple single-catalogue instances
If you run one Arranger instance per catalogue today, you can optionally consolidate them into a single multicatalogue server. This is optional: the one-instance-per-catalogue pattern continues to work unchanged after upgrading.
How multicatalogue routing works
apps/search-server reads CONFIGS_PATH. When that directory contains subdirectories, each subdirectory becomes a named catalogue, and routes include the catalogue ID as a prefix:
- GraphQL:
/{catalogueId}/graphql - Download:
/{catalogueId}/download - Fields introspection:
/introspection/{catalogueId}
When CONFIGS_PATH contains .json files directly (the flat layout), the server continues to behave as a single-catalogue deployment and routes remain at /graphql with no prefix.
The catalogue ID defaults to the subdirectory name. To override it, add "catalogId" to base.json.
Steps
1. Plan your catalogue IDs. The subdirectory name becomes the URL prefix: use something stable and URL-safe, for example file, analysis, or clinical.
2. Restructure your config files into subdirectories:
Before (separate instances, flat layout):
arranger-file/configs/ arranger-analysis/configs/
├── base.json ├── base.json
├── extended.json ├── extended.json
└── table.json └── table.json
After (single instance, subdirectory layout):
arranger/configs/
├── file/
│ ├── base.json
│ ├── extended.json
│ └── table.json
└── analysis/
├── base.json
├── extended.json
└── table.json
3. Move per-instance env vars into base.json. If ES_INDEX and DOCUMENT_TYPE were set at the container level for each instance, move those values into each catalogue's base.json. Use "esIndex" as the config key: not "index", which refers to the Sets feature index and is silently ignored for catalogue config:
{ "esIndex": "file_centric", "documentType": "File" }
4. Point CONFIGS_PATH at the parent directory. Each subdirectory is then an independent catalogue.
5. Update clients and downstream services. With more than one catalogue, the GraphQL path includes the catalogue ID prefix. Update any hardcoded paths:
Before: /graphql
After: /file/graphql
/analysis/graphql
Tip: A server with exactly one subdirectory routes at
/graphqlwith no prefix, the same as the flat layout. You can migrate one catalogue at a time: start the consolidated server with one subdirectory, validate all clients, then add the remaining catalogues.
6. Retire the extra instances once traffic is cut over.