Mass operations

Mass operations let you execute large batches of Crystallize mutations by uploading a JSON file that lists every action you want to run. Each entry in the file is called an operation. Operations are consumed by the mass-operations runner as part of a BulkTask and executed with the same validation and side effects as if you had called the corresponding GraphQL mutation yourself.

We ship first-class support in the Crystallize CLI. The CLI validates your file, uploads it through a presigned request, creates the bulk task, and can wait for completion while streaming logs.When to Reach for Mass Operations

  • Seeding or migrating a tenant (shapes, pieces, customers, orders, subscription contracts, etc.).
  • Replaying or fixing data outside the API rate limits.
  • Coordinating multi-step changes (e.g., create a piece, then a shape that references it, then publish the shape).
  • Updating product content at scale (components, stock levels, publish requests).

High-Level Flow

  1. Prepare the JSON file that describes every operation you need.
  2. Validate and upload the file through the generatePresignedUploadRequest mutation using the MASS_OPERATIONS upload type.
  3. Register the file by calling createMassOperationBulkTask with the returned storage key.
  4. Start the bulk task immediately by passing autoStart: true, or later through startMassOperationBulkTask.
  5. Monitor the task with the bulkTask query and inspect detailed per-operation logs with the operationLogs query.

Each task runs asynchronously in the standalone mass-operations-runner. Operations inside a task execute sequentially; success or failure is recorded per operation in the log stream.Mass Operation File Format

Mass operation files are strict JSON documents validated against @crystallize/schema/mass-operation (currently version 0.0.1).

{
  "version": "0.0.1",
  "operations": [
    {
      "intent": "piece/upsert",
      "identifier": "rating-system",
      "name": "Rating System",
      "components": [
        {
          "id": "name",
          "type": "singleLine",
          "singleLine": { "required": true }
        }
      ]
    }
  ]
}

Required Top-Level Fields

  • version: Schema version of the file. Only 0.0.1 is accepted today.
  • operations: Ordered array of operation objects. Operations are processed in order.

Operation Structure

Every operation:

  • Must contain an intent of the form domain/action (optionally with extra path segments).
  • Accepts exactly the same payload shape as the matching GraphQL input. You can copy an input you already use in the API and paste it into the JSON file.
  • May include helper identifiers (e.g., identifier, id, language) exactly as in GraphQL.
  • Can reference entities created earlier in the file using shared identifiers (e.g., upsert a customer, then reference it from an order).

If an operation fails validation or execution, the runner records the failure in the log and continues with the next operation. No implicit rollback is performed.Supported Intents

Intent

Description

piece/create, piece/update, piece/upsert

Manage pieces.

shape/create, shape/update, shape/upsert

Manage shapes.

item/updateComponent/item

Update a component on an item by item ID.

item/updateComponent/sku

Update a component on a product variant (SKU).

item/publish

Send publish requests for items.

product/variant/stock/modify

Adjust variant stock levels.

customer/create, customer/update, customer/upsert

Manage customers.

customer/group/create, customer/group/update, customer/group/upsert

Manage customer groups.

order/register, order/update, order/upsert

Manage orders.

subscription-contract/create, subscription-contract/update, subscription-contract/upsert

Manage subscription contracts.

New intents are added over time. The @crystallize/schema/mass-operation package is the source of truth.Richer Example

Below is a shortened example that upserts a customer, registers a new order that references the customer, and updates an item component:

{
  "version": "0.0.1",
  "operations": [
    {
      "intent": "customer/upsert",
      "identifier": "customer-for-order-123",
      "firstName": "John",
      "lastName": "Doe",
      "type": "individual"
    },
    {
      "intent": "order/register",
      "customer": {
        "identifier": "customer-for-order-123",
        "type": "individual"
      },
      "additionalInformation": "Please deliver between 9am-5pm",
      "cart": [
        {
          "sku": "SP-RED-001",
          "name": "Sample Product",
          "productId": "67e5d2d12d31ee752710a74b",
          "quantity": 2,
          "price": {
            "currency": "USD",
            "gross": 1000,
            "net": 800,
            "tax": { "name": "VAT", "percent": 20 }
          }
        }
      ]
    },
    {
      "intent": "item/updateComponent/item",
      "itemId": "632958a35dfc2c90cbbad20d",
      "language": "en",
      "component": {
        "componentId": "title",
        "singleLine": { "text": "Mass operation updated title" }
      }
    }
  ]
}

Uploading the File

  1. Request a presigned upload:
mutation PresignMassOp($filename: String!, $contentType: String!) {
  generatePresignedUploadRequest(
    input: {
      type: MASS_OPERATIONS
      filename: $filename
      contentType: $contentType
    }
  ) {
    url
    fields
    key
  }
}
  1. Upload the JSON file to the returned url using the provided form fields. The response includes a key that identifies the stored file in the mass-operations bucket.
  2. Register the bulk task:
mutation CreateMassOp($key: String!, $autoStart: Boolean) {
  createMassOperationBulkTask(input: { key: $key, autoStart: $autoStart }) {
    id
    key
    status
    createdAt
  }
}
    • If autoStart is true, the API immediately dispatches the mass-operations-runner task.
    • If autoStart is omitted or false, call startMassOperationBulkTask(id: ID!) later when you are ready.

Monitoring Progress

  • Bulk task status: Query bulkTask(id: ID!) or bulkTasks(filter: { type: massOperation }) to retrieve task status. The lifecycle is pending → started → complete or error.
  • Execution logs: Use operationLogs(filter: { operationId: "<bulkTaskId>" }) to stream detailed logs. Each log entry stores the original input payload, the command executed, the result, and a status (success, partial, failure) with a statusCode (e.g., 200, 206, 400, 500).
  • Outputs: Successful operations include the command output (IDs, payloads, etc.), making it easy to chain follow-up actions.

If the runner detects invalid JSON or schema violations, it records the issues and marks the task as error without attempting the remaining operations.

Using the Crystallize CLI

The Crystallize CLI provides turnkey helpers around the workflow:

  • ~/crystallize mass-operation dump-content-model <tenant> <file>: Generate a starter mass operation file containing the current shapes and pieces.
  • ~/crystallize mass-operation run <tenant> <file>: Validate against the schema, request the presigned upload, push the file, create the bulk task (with autoStart), and wait for completion while tailing logs.
  • ~/crystallize mass-operation execute-mutations <tenant> <file>: Run client-side GraphQL mutations that complement mass operations (e.g., to script additional steps).

All commands support --no-interactive for CI usage and reuse stored credentials. The CLI performs JSON validation locally before uploading, so you catch schema issues early.Best Practices & Tips

  • Validate early: Keep files under version control and lint them locally; the CLI validation mirrors the server schema.
  • Chunk large jobs: Split very large migrations into multiple tasks to simplify retries.
  • Reuse identifiers: Prefer identifier-based references so subsequent operations can find the entities created earlier in the file.
  • Idempotency with upsert: Use upsert intents when you want to rerun the same file safely.
  • Monitor logs: Always review operationLogs for failures or partial successes; follow-up actions can be scripted based on the emitted output.
  • Security: Presigned URLs are short-lived. Upload immediately after requesting them and keep the file size within your tenant’s upload limits.

Mass operations are powerful but operate with elevated privileges (the runner acts as a tenant admin). Treat the files as infrastructure artefacts, review them like code, and automate verification through the CLI wherever possible.