{"title":"Liquid updates","slug":"liquid-updates","url":"https://support.storeconnect.com/articles/liquid-updates","url_markdown":"https://support.storeconnect.com/articles/liquid-updates.md","subtitle":null,"summary":"Write custom data back to Salesforce records from a Liquid controller template using the update tag. Modify product details, customer preferences, order notes, and other custom fields directly from your storefront.","type":"Developer_Documentation","video_url":"","keywords":"liquid update, update tag, write data, custom data fields, liquid controller, salesforce record update, writeback, liquid template, storeconnect liquid","last_modified":"2026-09-15T02:32:04+0000","body_markdown":"The `update` tag writes custom data fields to Salesforce records directly from a Liquid controller template. Use it to save storefront actions back to your records — tracking product views, storing customer preferences, recording form submissions, or updating order details.\n\n:::warning\n**Controller context required.** The `update` tag works only inside a Liquid controller template (in `controllers/\u003ccontroller\u003e/\u003caction\u003e.liquid`). It is a silent no-op outside a controller.\n:::\n\n## When to use update\n\nUse the `update` tag when you need to:\n- Track metrics (view counts, interaction history) on storefront records\n- Save customer preferences from form input\n- Store order metadata or notes from a storefront action\n- Record fulfillment or status changes initiated by a customer\n- Audit or log storefront behavior to custom fields\n\n:::note\nThe `update` tag writes **one field at a time** to **one existing record**. If you need to update multiple records or multiple fields at once, consider a Flow or Apex action instead.\n:::\n\n## When NOT to use update\n\n- **Creating new records** — use a Flow or Apex action to insert records. The `update` tag modifies existing records only.\n- **Standard Salesforce fields** — use a Flow to update fields like `Name`, `Description`, `IsActive`. The `update` tag can only write custom data fields.\n- **Managed-package fields** — these are read-only from Liquid. Use a Flow or Apex.\n- **Unscoped writes** — if your update logic cannot guarantee the record belongs to the current store and customer, do not use `update`. The tag is a silent no-op if preconditions fail — the write might not happen, and you will not know.\n\n## Syntax\n\n\n```liquid\n\n{% update \u003cdrop\u003e, field: \"\u003cfield_name\u003e\", value: \u003cvalue\u003e %}\n```\n\n\n## Parameters\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `\u003cdrop\u003e` | Drop | Yes | The object to update. Must be a Drop instance (e.g. `current_product`, `current_order`, `current_customer`). To update a record from `{% query %}`, cast it first: `\\| cast: '\u003cDropName\u003e'`. |\n| `field:` | string | Yes | The custom field API name (case-insensitive). Example: `view_count__c`, `favorite__c`, `last_purchase_date__c`. |\n| `value:` | any | Yes | The new value. Can be a string, number, boolean, `null`, or Liquid variable. |\n\n## Preconditions for success\n\nAll four of these must be true for the update to succeed:\n\n1. **Inside a controller template** — the tag is in a file at `controllers/\u003ccontroller\u003e/\u003caction\u003e.liquid`. Outside a controller, the tag is a silent no-op.\n2. **Drop object** — the first argument is a Drop (a typed object), not a raw record from `{% query %}`. Convert query results with `| cast: '\u003cDropName\u003e'` before updating.\n3. **Editable custom field** — the field must have a Custom Data Mapping marked **Read/Write**. If the mapping is Read-only, a warning appears in the [web console](web-console) (surfaced by `{% debug %}`), not the browser console, and the write does not happen.\n4. **Custom field only** — the field must be a custom data field from a Custom Data Mapping. Standard fields (`Name`, `IsActive`, `CreatedDate`) and managed-package fields cannot be updated.\n\nIf any precondition fails, the tag is a silent no-op and logs a warning to the web console (surfaced by `{% debug %}`), not the browser console. **There is no error message in the page output.**\n\n## Before you start: Custom Data Mapping setup\n\nBefore you can update a field, it must be mapped in **Custom Data Mapping** and marked editable:\n\n1. Open the relevant Custom Data Mapping (e.g., for Product custom data, open the mapping for your Product object).\n2. Locate the field you want to update.\n3. Ensure the mapping mode is set to **Read/Write** — Read-only mappings cannot be written to.\n4. Save the mapping.\n\nIf the field has no Custom Data Mapping, or the mapping is Read-only, `update` will not work. Check the web console (surfaced by `{% debug %}`) for warnings.\n\n## Examples\n\n### Example 1: Track product view count\n\nStore the number of times a product is viewed in a custom field:\n\n\n```liquid\n\n{% before %}\n  {% assign current_count = current_product.data.view_count__c | default: 0 | plus: 1 %}\n  {% update current_product, field: \"view_count__c\", value: current_count %}\n{% endbefore %}\n```\n\n\n**How it works:**\n1. Read the current view count (default to 0 if the field is blank).\n2. Increment by 1.\n3. Write the new count back to the product.\n\n### Example 2: Save customer display preference\n\nStore a customer's selected display mode from a form:\n\n\n```liquid\n\n{% before %}\n  {% assign display_mode = current_request.params.mode %}\n  \n  {% if display_mode == \"grid\" or display_mode == \"list\" %}\n    {% update current_customer, field: \"preferred_display__c\", value: display_mode %}\n  {% endif %}\n{% endbefore %}\n```\n\n\n**How it works:**\n1. Extract the `mode` query parameter.\n2. Validate that it is one of the allowed values.\n3. Write the preference to the customer record.\n\n### Example 3: Record a form submission timestamp\n\nStore when a customer last submitted a form:\n\n\n```liquid\n\n{% before %}\n  {% assign form_name = current_request.params.form %}\n  \n  {% if form_name != blank %}\n    {% assign now = \"now\" | date: \"%Y-%m-%d %H:%M:%S\" %}\n    {% update current_customer, field: \"last_form_submission__c\", value: now %}\n  {% endif %}\n{% endbefore %}\n```\n\n\n**How it works:**\n1. Extract the form name from the request.\n2. Generate a timestamp in ISO format.\n3. Store the timestamp on the customer record.\n\n### Example 4: Update a queried record\n\nFetch a record via query, cast it, and update a field:\n\n\n```liquid\n\n{% before %}\n  {% assign order_id = current_request.params.id %}\n  \n  {% query 'Order' as orders,\n      s_c__store_id__c: current_store.sfid,\n      sfid: order_id,\n      billtocontactid: current_customer.sfid %}\n  \n  {% if orders.size == 1 %}\n    {% assign order = orders[0] | cast: 'Order' %}\n    {% update order, field: \"storefront_reviewed__c\", value: true %}\n  {% endif %}\n{% endbefore %}\n```\n\n\n**How it works:**\n1. Query for the order by ID, filtered to the current store and customer.\n2. Ensure exactly one match (no unscoped writes).\n3. Cast the record to an Order Drop.\n4. Update the custom field.\n\n### Example 5: Conditional update with validation\n\nUpdate only if the customer meets certain conditions:\n\n\n```liquid\n\n{% before %}\n  {% assign feedback = current_request.params.feedback %}\n  \n  {% if feedback.size \u003e 0 and feedback.size \u003c= 500 %}\n    {% update current_customer, field: \"latest_feedback__c\", value: feedback %}\n  {% else %}\n    {% respond body: '{\"error\": \"Feedback must be 1-500 characters\"}', status: 400, layout: false %}\n  {% endif %}\n{% endbefore %}\n```\n\n\n**How it works:**\n1. Validate the input (length check).\n2. Update only if validation passes.\n3. Return an error response if validation fails.\n\n## Common patterns\n\n### Incrementing counters\n\n\n```liquid\n\n{% assign new_count = current_product.data.count__c | default: 0 | plus: 1 %}\n{% update current_product, field: \"count__c\", value: new_count %}\n```\n\n\n### Storing boolean flags\n\n\n```liquid\n\n{% update current_customer, field: \"opted_in__c\", value: true %}\n```\n\n\n### Storing a null value (clearing a field)\n\n\n```liquid\n\n{% update current_product, field: \"optional_notes__c\", value: null %}\n```\n\n\n### Storing a JSON structure (as a string)\n\n\n```liquid\n\n{%- new Map preferences -%}\n{%- assign preferences = preferences | set_key: \"theme\", \"dark\" | set_key: \"notifications\", true -%}\n{%- assign pref_json = preferences | json -%}\n{% update current_customer, field: \"preferences_json__c\", value: pref_json %}\n```\n\n\n## Important notes\n\n:::warning\n**Only updates existing records.** The `update` tag modifies fields on records that already exist in Salesforce. It does not create records. If the record does not exist, the tag is a silent no-op.\n:::\n\n:::warning\n**Scoping is your responsibility.** Always verify that the record you are updating belongs to the current store and (for customer data) the authenticated customer. Without explicit scoping, you risk updating records that do not belong to this customer. Example scoping pattern:\n\n\n```liquid\n\n{% query 'Product2' as products,\n    s_c__store_id__c: current_store.sfid,\n    sfid: current_request.params.product_id %}\n\n{% if products.size == 1 %}\n  {% assign product = products[0] | cast: 'Product' %}\n  {% update product, field: \"custom__c\", value: 100 %}\n{% endif %}\n```\n\n:::\n\n:::note\n**Update happens in the before phase.** Place `{% update %}` in a `{% before %}` block to ensure the write completes before the response is rendered. Updates in `{% final %}` run after the response is sent to the client and should be used only for non-blocking, fire-and-forget updates.\n:::\n\n:::note\n**No transaction rollback.** If your controller logic fails after an update, the update is not rolled back. Design your controller flow so that updates happen only when the full operation is safe.\n:::\n\n:::warning\n**A failed save is currently silent.** If the write reaches the record but Salesforce rejects it on save (for example a validation-rule failure), the error is swallowed and written only to the server log; it does not surface in the web console or the page. Confirm the value actually persisted (with `{% debug %}` or the [verify-custom-data-in-liquid](verify-custom-data-in-liquid) walkthrough) rather than assuming success.\n:::\n\n## Drop objects available for update\n\nThese global Drops can be passed directly to `update`:\n\n| Drop | Description |\n|------|-------------|\n| `current_product` | The current product. Updates go to the Product record. |\n| `current_customer` | The authenticated customer. Updates go to the Contact record. |\n| `current_order` | The current order. Updates go to the Order record. |\n| `current_cart` | The current cart. Updates go to the Cart (s_c__Cart__c) record. |\n| `current_store` | The current store. Updates go to the Store record. |\n| `current_account` | The current account (on Account-scoped pages). Updates go to the Account record. |\n\nFor records from `{% query %}`, cast them first:\n\n\n```liquid\n\n{% assign product = query_result | cast: 'Product' %}\n{% update product, field: \"custom__c\", value: new_value %}\n```\n\n\n## Debugging updates\n\nIf an update does not work, check these in order:\n\n1. **Is this inside a controller template?** The tag is a silent no-op outside `controllers/\u003ccontroller\u003e/\u003caction\u003e.liquid`.\n2. **Is the first argument a Drop?** If you pass a raw query result, cast it first with `| cast: '\u003cDropName\u003e'`.\n3. **Does the field have a Custom Data Mapping?** Open the mapping and confirm it exists.\n4. **Is the mapping marked Read/Write?** A Read-only mapping will not allow writes. Change it to Read/Write.\n5. **Check the web console.** If a precondition fails, a warning is logged there via `{% debug %}` (not in the browser console or the page output).\n\nTo confirm a field is writable, use the `verify-custom-data-in-liquid` article to test read and write access.\n\n## See also\n\n- [Update - Liquid Tag Reference](update-tag-reference) — tag syntax and parameters\n- [Liquid custom data fields](liquid-custom-data-fields) — how to read and map custom fields\n- [Liquid controllers guide](liquid-controllers-guide) — controller template structure and phases\n- [Verify custom data in Liquid](verify-custom-data-in-liquid) — walkthrough of reading and writing custom data"}