Backing Up Integrations and Instances
The backup-integrations-and-instances example script wraps Prismatic's GraphQL API to save your integrations and their deployed instances to local files, and to recreate them in a tenant later.
This guide walks through the queries the script runs to export your data and the mutations it runs to restore it. Use it to adapt the script to your own backup process, or as a reference for exporting integration definitions, reading instance configuration, and deploying instances programmatically.
Run the script
You need Node.js 22 or later and an API token.
Clone the examples repository, install dependencies, and run the backup script with your token in the PRISMATIC_API_KEY environment variable:
git clone https://github.com/prismatic-io/examples.git
cd examples/scripts/backup-integrations-and-instances
npm install
PRISMATIC_API_KEY=$(prism me:token) npm run backup
The script sends requests to https://app.prismatic.io/api by default.
To use a tenant in another region, set PRISMATIC_URL to the region's URL from deployment regions, such as https://app.eu-west-1.prismatic.io.
To restore a backup, run the restore script against the tenant you want to restore into:
PRISMATIC_API_KEY=$(prism me:token) npm run restore
Each request uses the token as a bearer token, and the script retries failed requests up to five times.
Back up integrations and instances
The backup writes a backups/ directory with a subdirectory for each integration:
backups/
├── metadata.json # Integration and instance counts
└── <integration-name> - <integration-id>/
├── integration.yaml # Integration YAML definition
├── metadata.json # Other integration fields
├── icon.png # Integration icon, if one exists
└── instances/
└── <instance-id>.json # Instance name, customer, and config variables
Instance files include config variable values, connection inputs, and OAuth 2.0 tokens for your customers' third-party apps.
Store backups somewhere with access controls equivalent to your Prismatic tenant, such as an encrypted bucket.
The script's .gitignore excludes backups/ so you don't commit these files to source control by accident.
Count integrations
The script first requests the total number of integrations, which it uses to display progress:
{
integrations {
totalCount
}
}
Export integrations
Next, the script pages through the integrations query, oldest first, and requests 20 integrations per page:
query GetIntegrations($cursor: String) {
integrations(
orderBy: { direction: ASC, field: CREATED_AT }
after: $cursor
first: 20
) {
nodes {
id
customer {
name
externalId
}
definition(useLatestComponentVersions: true)
avatarUrl
category
description
documentation
isCodeNative
marketplaceConfiguration
name
overview
instances {
totalCount
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
The script passes each response's pageInfo.endCursor as the next cursor until hasNextPage is false.
Sorting by CREATED_AT keeps pages stable while you paginate.
These fields determine what the backup contains:
| Field | How the script uses it |
|---|---|
definition(useLatestComponentVersions: true) | The integration's YAML definition, written to integration.yaml. The argument sets each component reference to the latest available component version. |
isCodeNative | Saved in metadata.json so you can identify integrations to restore from source. |
customer | The owning customer of a customer-specific integration, saved in metadata.json. |
avatarUrl | A path used to download the integration's icon. |
instances.totalCount | Tells the script whether to query instances for this integration. |
The remaining fields are saved to metadata.json as a record of the integration's settings.
To download an icon, the script sends a GET request to avatarUrl on your Prismatic URL (for example, https://app.prismatic.io followed by the avatarUrl path) with the same bearer token.
The response is a JSON object with a url property, and the script downloads the image from that URL.
Export instances
For each integration with instances, the script pages through the instances query, filtered to that integration:
query GetInstanceByIntegration($integrationId: ID!, $cursor: String) {
instances(
integration: $integrationId
after: $cursor
orderBy: { direction: ASC, field: CREATED_AT }
) {
nodes {
id
name
customer {
id
name
externalId
}
labels
lastDeployedAt
configVariables {
nodes {
requiredConfigVariable {
key
}
authorizeUrl
refreshAt
value
scheduleType
timeZone
meta
inputs {
nodes {
name
value
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
Each instance is saved as a JSON file in the integration's instances/ directory.
The config variable fields hold different data depending on the config variable's type:
| Field | Contents |
|---|---|
requiredConfigVariable.key | The config variable's key, which identifies it when you set values on a new instance |
value | The value of a standard config variable |
inputs | The name and value of each input of a connection |
authorizeUrl, meta, refreshAt | For OAuth 2.0 connections, the authorization URL, connection metadata including the current token, and the time of the next token refresh |
The customer's externalId is what the restore step uses to match each instance to a customer in the target tenant.
Restore integrations and instances
The restore step reads each integration directory in backups/ and recreates its contents with a series of mutations.
The restore step creates new integrations and instances; it doesn't update or replace existing ones. Restoring into a tenant that already contains the backed-up integrations creates duplicate integrations and instances.
Before you restore, set up these resources in the target tenant, because the backup doesn't include them:
- Customers: Each backed-up instance's customer must exist with the same external ID.
- Custom components: Publish any custom components your integrations use. To publish components to another region, see syncing components between regions.
Import and publish each integration
The script passes the contents of integration.yaml to importIntegration:
mutation importIntegration($definition: String!) {
importIntegration(input: { definition: $definition }) {
integration {
id
}
errors {
field
messages
}
}
}
Because the mutation doesn't include an integrationId, Prismatic creates a new integration.
If errors isn't empty, the script logs each error and stops.
An import commonly fails when the definition references a custom component that isn't published to the tenant.
The script then publishes the new integration with publishIntegration, which returns the ID of the published version:
mutation publishIntegration($integrationId: ID!) {
publishIntegration(input: { id: $integrationId }) {
integration {
id
}
errors {
field
messages
}
}
}
Find the customer for each instance
For each instance file, the script looks up the instance's customer in the target tenant by external ID with the customers query:
query getCustomerByExternalId($externalId: String!) {
customers(externalId: $externalId) {
nodes {
id
}
}
}
The script uses the first matching customer's id.
If no customer matches, or the backed-up customer has no external ID, the script stops.
Create the instance
The script converts each backed-up config variable into an InputInstanceConfigVariable.
A standard config variable sets value.
A connection sets values to a JSON-encoded array of its inputs, each with type: "value":
const parsedConfigVariables = configVariables.map((c) => {
if (c.value) {
// Standard config variable
return { key: c.requiredConfigVariable.key, value: c.value };
} else {
// Connection config variable
return {
key: c.requiredConfigVariable.key,
values: JSON.stringify(
c.inputs.nodes.map((input) => ({
type: "value",
...input,
})),
),
};
}
});
It then creates an instance of the published integration version with createInstance:
mutation createInstance(
$customerId: ID!
$instanceName: String!
$integrationVersionId: ID!
$configVars: [InputInstanceConfigVariable]
) {
createInstance(
input: {
customer: $customerId
integration: $integrationVersionId
name: $instanceName
configVariables: $configVars
}
) {
instance {
id
configVariables {
nodes {
id
requiredConfigVariable {
key
}
}
}
}
errors {
field
messages
}
}
}
The response includes the ID of each config variable on the new instance, which the next step needs to restore OAuth 2.0 connections. For more about setting config variables, see updating config variables.
Restore OAuth 2.0 tokens
For each backed-up config variable with an authorizeUrl, the script finds the matching config variable on the new instance by key.
It then calls updateOAuth2Connection with the token from the backed-up meta field and the backed-up refreshAt time:
mutation (
$configVarId: ID!
$additionalTokenFields: String
$refreshAt: DateTime
) {
updateOAuth2Connection(
input: {
id: $configVarId
status: "active"
tokenType: "bearer"
additionalTokenFields: $additionalTokenFields
refreshAt: $refreshAt
}
) {
instanceConfigVariable {
status
meta
}
errors {
field
messages
}
}
}
Setting status to active marks the connection as authorized, so your customer doesn't need to repeat the OAuth 2.0 flow.
The connection uses the backed-up refresh token at its next refresh, so the restore works only while that token is still valid with the third-party app.
Deploy the instance
Finally, the script deploys the instance with deployInstance:
mutation ($instanceId: ID!) {
deployInstance(input: { id: $instanceId }) {
instance {
id
}
errors {
field
messages
}
}
}
The script checks errors only on importIntegration.
If you adapt it, check errors on createInstance, updateOAuth2Connection, and deployInstance too, so you can report instances that didn't restore.
Restore code-native integrations from source
The restore step imports YAML definitions, which is intended for low-code integrations. For a code-native integration, the TypeScript source code in your version control system is the source of truth. Restore it by building and importing it from source:
npm run build && prism integrations:import
The restore step processes every integration directory in backups/, including code-native integrations.
Use the isCodeNative field in each integration's metadata.json to find code-native integrations and handle them before running the restore.
What the restore doesn't recreate
The restore step sends only the integration definition, the instance name, the customer, and config variable values. It doesn't restore:
- The integration icon, marketplace configuration, or other details stored only in the per-integration
metadata.json - The integration's version history; each restored integration starts with a single published version, and its instances deploy to that version
- An integration's owning customer, so customer-specific integrations are restored as organization integrations
- Instance labels
- Customers, users, alert monitors, execution history, and logs
To restore more, add the relevant fields to the mutations.
For example, importIntegration accepts a customer input, and createInstance accepts labels.