Homepage

RollbackOutsideTransaction

Last edit: Aug 23, 2026

RollbackOutsideTransaction

Severity: error | Type: LiquidHtml

{% rollback %} only has meaning inside {% transaction %}. Reached anywhere else, the platform raises rollback performed outside of transaction — so this is a guaranteed runtime error, not a style preference.

Why the offense lands on the caller

A partial cannot know whether it is running inside a transaction: the same file is correct under one call site and broken under another.


{% comment %}fine — the caller opens the transaction{% endcomment %}
{% transaction %}{% function _ = 'commands/order/place' %}{% endtransaction %}

{% comment %}raises — the same partial, no transaction{% endcomment %}
{% function _ = 'commands/order/place' %}

The transaction state is only known at the root of a render tree, so that is where this check reports. A rollback written in a partial is never reported where it is written. Instead the check follows {% render %}, {% include %}, {% theme_render_rc %}, {% function %} and {% background %} calls from the files whose state it does know, and reports on the call site, naming the chain it followed:


Rendering 'order/wrapper' reaches a {% rollback %} that is not inside a {% transaction %} block
(order/wrapper → order/place). At runtime the platform raises "rollback performed outside of transaction".

Which files have a known state

File type State at the top of the file Why
Page, Layout, Email, ApiCall, Sms no transaction Nothing wraps the render — a bare rollback here always raises
Migration inside a transaction Migrations run inside one already, so a bare {% rollback %} in a migration is valid and is not reported
Partial (views/partials/, lib/) unknown The caller decides — this is why the check descends render trees
FormConfiguration, Authorization unknown A form can be submitted programmatically from a GraphQL mutation, so its callbacks and policies may run inside the caller's transaction

Two tags interrupt the transaction

{% background %} never inherits a transaction. A background job is only queued after the transaction that scheduled it commits, so it always runs outside one. A rollback under a {% background %} block, or in a partial a {% background %} schedules, is reported from any file — including a partial and including code already wrapped in {% transaction %}.

{% content_for %} bodies are never reported. The body runs where the matching {% yield %} is, which may be in another file, so where it is written proves nothing about its transaction state.

Examples

✗ Incorrect Code Example (Avoid using this):

A rollback with no transaction around it:


{% comment %}app/views/pages/orders/create.liquid{% endcomment %}
{% liquid
  function order = 'commands/order/create', total: 100
  unless order.valid
    rollback
  endunless
%}

A partial that rolls back, called without a transaction:


{% comment %}app/views/pages/orders/create.liquid{% endcomment %}
{% function order = 'commands/order/place', total: 100 %}


{% comment %}app/lib/commands/order/place.liquid{% endcomment %}
{% liquid
  function order = 'modules/core/commands/execute', mutation_name: 'order/create', object: object
  unless order.valid
    rollback
  endunless
%}

A rollback inside a background job:


{% transaction %}
  {% background %}
    {% comment %}the job runs after this transaction commits, so it is never inside one{% endcomment %}
    {% rollback %}
  {% endbackground %}
{% endtransaction %}

✓ Correct Code Example (Use this instead):

Wrap the work that must roll back:


{% liquid
  transaction
    function order = 'commands/order/create', total: 100
    unless order.valid
      rollback
    endunless
  endtransaction
%}

Or open the transaction at the call site, so the partial can stay reusable:


{% comment %}app/views/pages/orders/create.liquid{% endcomment %}
{% transaction %}
  {% function order = 'commands/order/place', total: 100 %}
{% endtransaction %}

Give a background job its own transaction:


{% background %}
  {% transaction %}
    {% function order = 'commands/order/place', total: 100 %}
  {% endtransaction %}
{% endbackground %}

Configuration

The default configuration for this check:

RollbackOutsideTransaction:
  enabled: true
  severity: error

Disabling This Check

There should be no reason to disable this check. Every offense it reports is code that raises when it runs. It is deliberately conservative: a partial's rollback is only reported once a caller proves it runs outside a transaction, a partial named by a variable is never followed, and file types whose transaction state cannot be determined are left alone — so a silence is not a guarantee that a rollback is correctly placed.

Resources

Questions?

We are always happy to help with any questions you may have.

contact us