Homepage

InvalidWriteTarget

Last edit: Aug 16, 2026

InvalidWriteTarget

Severity: error | Type: LiquidHtml

Some writes go into a container rather than replacing it. This check reports one whose target cannot accept it, which raises at runtime.

Two rules, and three tags spell both:

Write Needs
{% assign h['k'] = v %}, {% hash_assign h['k'] = v %}, {% function h['k'] = 'partial' %} a Hash with a key, or an Array with a numeric index
{% assign a << v %}, {% function a << 'partial' %} an Array

The two rules are not the same rule. A subscript write accepts a Hash and refuses a scalar; << accepts an Array and refuses a Hash.

A dot target is a key: {% assign h.k = v %} writes the key k, so an Array refuses it exactly as it refuses h['k'].

{% hash_assign %} is deprecated — prefer {% assign %}, which reaches the same runtime setter and accepts more target spellings. See DeprecatedTag.

How the target's type is known

From the documented return type of whatever assigned it, and from the tag that assigned it:

  • {% assign x = 1 %}number; "hello"string; trueboolean; (1..5)range
  • {% assign x = list | split: "," %}array, from the last filter in the chain
  • {% assign x = {"a": 1} %} or {% parse_json x %}{}{% endparse_json %}object
  • {% graphql x = 'query' %}object; {% capture x %}string

The type is tracked per scope: a write inside an {% if %} branch is not a fact after it, and a {% for %} variable does not inherit the type of the name it shadows.

Nothing is reported when the type is unknown — a {% render %} argument, a filter the documentation does not carry a return type for, a variable assigned in another file. Silence is not approval.

Examples

✗ Incorrect Code Example (Avoid using this):


{% assign counter = 0 %}
{% assign counter['key'] = "value" %}

counter is a number — the runtime raises counter is 0, expected Hash or Array.


{% assign items = "a,b,c" | split: "," %}
{% assign items['key'] = "value" %}

items is an Array, so it wants an index — the runtime raises items is an Array, expected index, key was provided.


{% parse_json data %}{}{% endparse_json %}
{% assign data << "value" %}

data is a Hash — the runtime raises data is {}, expected Array. << appends to an Array.

✓ Correct Code Example (Use this instead):


{% parse_json data %}{}{% endparse_json %}
{% assign data['key'] = "value" %}
{% assign data.other = "value" %}


{% assign items = "a,b,c" | split: "," %}
{% assign items[0] = "value" %}
{% assign items << "value" %}

Writing into a container does not replace it, so a Hash stays a Hash and an Array stays an Array for every later write.

What is not reported

  • A nested subscript{% assign x[0]['k'] = v %}. The runtime checks the intermediate value, which requires knowing the type of x[0]; the first subscript is still checked.
  • An append through a subscript{% assign x['k'] << v %} — for the same reason: the runtime checks the value at the subscript, not the container.
  • A subscript that only resolves at runtime{% assign x[y] = v %} on an Array, since neither Array rule can be ruled out.

Configuration

The default configuration for this check:

InvalidWriteTarget:
  enabled: true
  severity: error

Disabling This Check

Disabling this check is not recommended — every case it reports raises at runtime.

Resources

Questions?

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

contact us