Skip to content
Saleor
Esc
↑↓navigate↵open⌘Jpreview
On this page

Reporting Problems from Apps

Apps can self-report operational problems visible in the Saleor Dashboard. This lets you surface issues — such as misconfiguration, missing credentials, or degraded external services — directly to store administrators without requiring them to inspect logs or external monitoring.

Overview

The Problems API provides two mutations:

  • appProblemCreate — report a problem with a key and message.
  • appProblemDismiss — dismiss problems by IDs or keys.

Problems are visible on the App type via the problems field and are displayed in the Dashboard on the app detail page.

Key behaviors:

  • Problems with the same key are aggregated within a configurable time window (default: 60 minutes).
  • A criticalThreshold can mark a problem as critical once occurrences reach a given count.
  • Each app can have up to 100 problems. When the limit is reached, the oldest problem is evicted automatically.
  • Problems are cascade-deleted when the app is removed.

Creating problems

Use appProblemCreate to report a problem. This mutation is only available to authenticated apps — staff users cannot call it.

mutation {
  appProblemCreate(
    input: {
      message: "Cannot connect to payment gateway: connection timeout"
      key: "payment-gateway-health"
    }
  ) {
    appProblem {
      id
      message
      key
      count
      isCritical
    }
    errors {
      field
      message
      code
    }
  }
}

Input fields

Field Type Required Default Description
message String! Yes — Human-readable description of the problem. Must be at least 3 characters. Messages longer than 2048 characters are truncated.
key String! Yes — Identifier for the type of problem. Must be between 3 and 128 characters. Used for aggregation and dismissal by key.
aggregationPeriod Minute No 60 Time window in minutes for aggregating problems with the same key. Set to 0 to disable aggregation.
criticalThreshold PositiveInt No — When set, the problem is marked as isCritical: true once its count reaches this value.

Choosing a key

The key field identifies the category of problem. Use a descriptive, stable identifier that represents the type of issue rather than a specific occurrence. The same key is used for:

  • Aggregation — repeated calls with the same key within the aggregation window are merged into a single problem with an incrementing count.
  • Dismissal by key — you can dismiss all problems matching a key in one call.

Good key examples:

  • payment-gateway-health
  • warehouse-api-auth
  • shipping-provider-timeout
  • tax-service-config-missing

Aggregation

When you report a problem with the same key as an existing non-dismissed problem, the system checks if the last event (updatedAt) is within the aggregationPeriod. If it is, the problems are merged:

  • count is incremented.
  • message is updated to the new message.
  • updatedAt is reset — restarting the aggregation window.

This is a sliding window measured from the last event, not from the first. As long as events keep arriving within the window, they aggregate into a single problem.

T+0min   appProblemCreate(key: "api-err") → new problem (count=1)
T+30min  appProblemCreate(key: "api-err") → merged   (count=2, window resets)
T+80min  appProblemCreate(key: "api-err") → merged   (count=3, 50 min since last — still within 60 min)
T+200min appProblemCreate(key: "api-err") → new problem (count=1, 120 min since last — exceeded window)

Setting aggregationPeriod: 0 disables aggregation — every call creates a new problem.

Critical thresholds

Use criticalThreshold to control when a problem is marked as critical. The problem’s isCritical field becomes true once count >= criticalThreshold.

Immediate critical

Set criticalThreshold: 1 to mark a problem as critical from the first report:

mutation {
  appProblemCreate(
    input: {
      message: "Payment provider certificate expired"
      key: "payment-cert-expired"
      criticalThreshold: 1
    }
  ) {
    appProblem {
      key
      count
      isCritical
    }
    errors {
      field
      message
      code
    }
  }
}

Result: count: 1, isCritical: true.

Gradual escalation

Set a higher threshold to escalate only after repeated occurrences:

mutation {
  appProblemCreate(
    input: {
      message: "Warehouse API returns 401"
      key: "warehouse-api-health"
      aggregationPeriod: 60
      criticalThreshold: 5
    }
  ) {
    appProblem {
      key
      count
      isCritical
    }
    errors {
      field
      message
      code
    }
  }
}

After 4 calls: count: 4, isCritical: false. After the 5th call: count: 5, isCritical: true.

De-escalation

If a subsequent call sends a higher criticalThreshold than the current count, the problem is de-escalated back to non-critical:

  • After 5 calls with criticalThreshold: 5: count: 5, isCritical: true
  • 6th call with criticalThreshold: 10: count: 6, isCritical: false — threshold raised above current count

Dismissing problems

Both apps and staff users can dismiss problems. The appProblemDismiss mutation uses a structured input — exactly one of byApp, byStaffWithIds, or byStaffWithKeys must be provided.

As an app

App callers use the byApp input to dismiss their own problems. Exactly one of ids or keys must be provided inside byApp.

Dismiss by IDs:

mutation {
  appProblemDismiss(
    input: {
      byApp: {
        ids: ["QXBwUHJvYmxlbTox"]
      }
    }
  ) {
    errors {
      field
      message
      code
    }
  }
}

Dismiss by keys — dismiss all problems matching the given keys:

mutation {
  appProblemDismiss(
    input: {
      byApp: {
        keys: ["warehouse-api-health"]
      }
    }
  ) {
    errors {
      field
      message
      code
    }
  }
}

As a staff user

Staff users with MANAGE_APPS permission can dismiss problems using byStaffWithIds or byStaffWithKeys.

Dismiss by IDs (app is inferred from the problem IDs):

mutation {
  appProblemDismiss(
    input: {
      byStaffWithIds: {
        ids: ["QXBwUHJvYmxlbTox"]
      }
    }
  ) {
    errors {
      field
      message
      code
    }
  }
}

Dismiss by keys (requires app argument to identify the target app):

mutation {
  appProblemDismiss(
    input: {
      byStaffWithKeys: {
        keys: ["warehouse-api-health"]
        app: "QXBwOjE="
      }
    }
  ) {
    errors {
      field
      message
      code
    }
  }
}

Caller restrictions

Input Who can use it ids keys app field
byApp App only Dismiss own problems by ID Dismiss own problems by key Not applicable
byStaffWithIds Staff only Dismiss by ID (app is inferred) — Not applicable
byStaffWithKeys Staff only — Dismiss by key for a given app Required

Staff users need the MANAGE_APPS permission and can only dismiss problems for apps within their permission scope. Each list (ids or keys) is limited to 100 items per call.

Querying problems

Problems are available on the App type. You can query them through any query that returns an App. An optional limit parameter restricts the number of returned problems (must be between 1 and 100):

query {
  app(id: "QXBwOjE=") {
    id
    name
    problems(limit: 10) {
      id
      message
      key
      createdAt
      updatedAt
      count
      isCritical
      dismissed {
        by
        user {
          id
          email
        }
        userEmail
      }
    }
  }
}

Problems are ordered by createdAt descending (newest first).

AppProblem fields

Field Type Description
id ID! Unique identifier.
message String! Human-readable problem description. Updated on aggregation.
key String! Identifier for the type of problem.
count Int! Number of occurrences (incremented on aggregation).
isCritical Boolean! Whether the problem reached the critical threshold.
dismissed AppProblemDismissed Dismissal information. null if the problem has not been dismissed.
createdAt DateTime! When the problem was first created.
updatedAt DateTime! When the problem was last updated (reset on each aggregation).

AppProblemDismissed fields

The dismissed field returns an object with dismissal details when the problem has been dismissed, or null otherwise.

Field Type Description
by AppProblemDismissedByEnum! Whether dismissed by APP or USER.
user User The user who dismissed the problem. null if dismissed by an app or if the user was deleted. Requires MANAGE_STAFF permission.
userEmail String Email of the user who dismissed the problem. Preserved even if the user is deleted. Requires AUTHENTICATED_STAFF_USER.

Clearing problems on recovery

When the underlying issue is resolved, your app should dismiss the related problems so the Dashboard reflects the current state. Use appProblemDismiss with the byApp input and keys argument to clear all problems of a given type:

mutation {
  appProblemDismiss(
    input: {
      byApp: {
        keys: ["payment-gateway-health"]
      }
    }
  ) {
    errors {
      field
      message
      code
    }
  }
}

This pattern works well with health-check loops: report a problem on failure, dismiss it once the service recovers.

Business examples

Payment app: gateway connectivity

A payment app monitors the health of its gateway connection. On each failed health check, it reports a problem. After 5 consecutive failures within the aggregation window, the problem becomes critical.

# On each failed health check:
mutation {
  appProblemCreate(
    input: {
      message: "Payment gateway unreachable: connection timeout after 5s"
      key: "payment-gateway-connectivity"
      aggregationPeriod: 30
      criticalThreshold: 5
    }
  ) {
    appProblem {
      key
      count
      isCritical
    }
    errors {
      field
      message
      code
    }
  }
}

# When the gateway recovers:
mutation {
  appProblemDismiss(
    input: {
      byApp: {
        keys: ["payment-gateway-connectivity"]
      }
    }
  ) {
    errors {
      field
      message
      code
    }
  }
}

Fulfillment app: missing API credentials

A fulfillment app detects that the warehouse API key is missing or invalid during startup. This is a configuration issue that requires immediate attention.

mutation {
  appProblemCreate(
    input: {
      message: "Warehouse API key is missing. Configure it in the app settings."
      key: "warehouse-api-credentials"
      criticalThreshold: 1
    }
  ) {
    appProblem {
      key
      count
      isCritical
    }
    errors {
      field
      message
      code
    }
  }
}

Result: isCritical: true immediately, prompting the store administrator to act.

Tax app: degraded external service

A tax calculation app gets intermittent errors from the tax provider. It reports each failure but only escalates to critical after 10 occurrences, since occasional failures are expected.

mutation {
  appProblemCreate(
    input: {
      message: "Tax provider returned HTTP 503: service temporarily unavailable"
      key: "tax-provider-availability"
      aggregationPeriod: 120
      criticalThreshold: 10
    }
  ) {
    appProblem {
      key
      count
      isCritical
    }
    errors {
      field
      message
      code
    }
  }
}

With aggregationPeriod: 120, failures within a 2-hour sliding window accumulate. If the provider returns 10 errors in that window, the problem becomes critical.

Shipping app: rate-limit exceeded

A shipping rate app detects that it has been rate-limited by the carrier API. It reports the problem and clears it once the rate limit resets.

# When rate-limited:
mutation {
  appProblemCreate(
    input: {
      message: "Carrier API rate limit exceeded. Shipping rates may be unavailable until the limit resets."
      key: "carrier-rate-limit"
      criticalThreshold: 1
      aggregationPeriod: 0
    }
  ) {
    appProblem {
      key
      count
      isCritical
    }
    errors {
      field
      message
      code
    }
  }
}

With aggregationPeriod: 0, each rate-limit event creates a separate problem record — useful when you want a log of individual incidents.

Limits and eviction

Each app can store up to 100 problems. When the limit is reached, creating a new problem automatically evicts the oldest one. This means you do not need to worry about cleanup — old problems are removed to make room for new ones.

If you want finer control, dismiss problems proactively using appProblemDismiss when the underlying issue is resolved.

Permissions

Operation Required permission Notes
appProblemCreate AUTHENTICATED_APP Only the app itself can create its own problems.
appProblemDismiss AUTHENTICATED_APP or MANAGE_APPS Apps dismiss their own. Staff can dismiss for managed apps.
Querying problems AUTHENTICATED_APP or MANAGE_APPS Available on the App type with appropriate permissions.
dismissed.user MANAGE_STAFF Viewing which user dismissed a problem requires staff permission.
dismissed.userEmail AUTHENTICATED_STAFF_USER Only accessible to authenticated staff users; apps receive a permission error.

Last updated on February 19, 2026

Was this page helpful?