Skip to main content

Conditional saves for MENA portals: prevent lost edits

Use ETags, atomic version checks and accessible conflict recovery to protect edits in Arabic-English business portals.

Updated 9 min readMarkdown
Part of seriesBuilder Guides
Conditional saves for MENA portals: prevent lost edits

A dispatcher opens a service ticket, changes its appointment window and saves. Meanwhile, a colleague has corrected the customer contact. If the first browser submits an old copy of the whole record, a successful save can quietly erase the correction.

For Arabic-English operations portals, the problem becomes harder to notice when two users see different translations of the same underlying record. The interface can look current while the edit is based on an obsolete version. This guide proposes a conditional-save contract for MENA SaaS teams: detect stale edits at the write boundary, preserve the user's work and require a deliberate review before resubmission.

The example is a service-ticket editor, not a collaborative document system or a payment authorisation flow. The design recommendations below need testing against your own persistence layer and API.

Cover: original SultanByte artwork showing two editing sessions approaching a version-checked save.

Define what the user is editing

Start with one canonical editing resource. In this proposed design, its JSON contains stable field identifiers and stored values. Arabic and English labels belong to the interface; switching language does not silently choose a different ticket, customer workspace or deployment.

For a product with separate Saudi and UAE deployments, bind the editor to the verified workspace and its assigned deployment. A user who can access both should see which workspace owns the ticket before saving. Do not route an edit according to interface language or a country selector intended only for display.

Keep the originally loaded document, the user's proposed changes and the original validator together. A background refresh must not replace just the validator while leaving the old form values in place. That would give an obsolete edit the appearance of being based on current data.

Decide the conflict boundary with the product owner. A single version for the entire ticket is conservative: any relevant edit can require review. Separate versions for independent subresources can reduce unnecessary conflicts, but only when their business rules really are independent. An appointment date and its assigned technician may need a joint check even if they occupy separate form sections.

Require a validator on every save

HTTP Semantics, RFC 9110, defines If-Match as a precondition evaluated against the selected representation. It uses strong entity-tag comparison. A weak tag, prefixed with W/, is unsuitable for this check; If-Match: * checks existence rather than whether the user edited a particular version.

For this API design, return a strong ETag with the canonical editing response. Treat the value as opaque and send it back unchanged with the save. Bind it to that resource and representation; do not reuse one validator across different response bodies merely because they describe the same database row.

If your API translates stored content, redacts fields by role or changes representation format, review the validator scheme for those variants. A row counter alone is insufficient when representation changes can occur without advancing that counter. A simpler initial contract is one documented editing representation, with all relevant changes advancing its version.

RFC 6585 defines 428 Precondition Required for servers that require conditional requests. Its response should explain how to resubmit, and caches must not store it. The status is optional in HTTP; adopting it is an explicit API decision, not protection supplied automatically by the protocol.

Use 428 for a missing required precondition in this design. Reject malformed validators separately. When an authorised save presents an obsolete validator, use 412 Precondition Failed and leave the resource unchanged. RFC 9110 permits a success response in certain already-applied cases; this design uses a stricter conflict response unless a separate operation receipt proves the earlier outcome.

Put the comparison inside the write boundary

A handler that reads the version, compares it in application code and then performs an unconditional update still has a race. Another writer can change the record between the comparison and the update.

Use a conditional database mutation or an equivalent storage operation that checks and changes the version atomically. For the proposed ticket service, the mutation must match the verified tenant, ticket identifier and expected version, then advance the version with the changed values. Every writer, including imports, administrative tools and background automation, must follow the same discipline.

PostgreSQL 18's isolation documentation explains that under Read Committed, an update encountering a concurrent updater waits and then re-evaluates its condition against the updated row. That behaviour can support a version predicate. It does not turn a preliminary read followed by an unconditional write into a safe conditional save.

Check the affected-row result. No matching row must not become a success response. Determine the safe error outcome without revealing a record the caller cannot access. Where mutable permissions or related records participate in the decision, protect those dependencies too; a version column on the ticket does not lock unrelated policy tables.

For a storage-specific example, Microsoft documents optimistic concurrency in Azure Blob Storage: a write with an outdated If-Match value returns 412, while a matching write updates the blob and its ETag. Microsoft also states that without an explicit concurrency strategy, the default is last writer wins. This is Blob Storage behaviour, not a promise that every Azure-backed application already protects its forms.

Conditional-save workflow: retain the loaded version, authorise and compare at the write boundary, then either save or preserve the draft for review. Missing preconditions return 428; stale validators return 412.

Infographic: original SultanByte engineering framework, November 2026. Informed by RFC 9110, RFC 6585, OWASP and PostgreSQL 18. It shows a proposed save contract, not measured production results.

A version is not permission

OWASP recommends validating permissions on every request. Apply that to the save, the refresh used for conflict review and any operation-status endpoint. Possessing a valid ETag must not grant access.

In the proposed portal, a user may lose membership while a form is open. Reject their subsequent save according to the access policy, even if the record has not changed. Do not return current confidential values inside a helpful conflict response after permission has disappeared.

Keep tenancy and authorisation checks distinct from stale-edit detection. SultanByte's tenant-isolation review covers the wider boundary. Here, the release requirement is narrower: a current validator cannot bypass access checks, and an authorised user cannot bypass version checks.

Make conflict recovery preserve the work

A 412 should not erase the form. Keep the proposed changes available within the approved session and show a persistent explanation: another change was saved, and this edit has not been applied. Do not store sensitive drafts indefinitely in browser storage just to make recovery convenient.

Offer a review view with the loaded values, current authorised values and proposed values. Fetch the current resource through the normal access path. Label those versions explicitly; “old” and “new” become confusing when both the server and user have made changes.

For Arabic and English, identify fields by stable IDs and render their labels in the chosen language. Keep record references and mixed-direction text readable. On narrow screens, stack the versions for each field instead of squeezing a three-column comparison into a phone viewport. Review must work with a keyboard, not only through colour-coded highlighting.

W3C's status-message guidance explains how results and errors that do not take focus can be exposed to assistive technology. Announce that the save needs review, while leaving a persistent route to the details. A disappearing toast is a poor place to put the only explanation of unsaved work.

After review, form a new proposed edit against the newly loaded version. If another writer intervenes again, show another conflict. Never handle 412 by automatically fetching a fresh ETag and resending the old payload: that bypasses the review this contract exists to require.

Separate a stale edit from an unknown outcome

A lost response is different from a rejected precondition. The server may have committed the save before the browser disconnected. Repeating the request with its original validator can then produce a conflict even though the first attempt succeeded.

For this design, track a save operation separately from the resource version. Bind its identifier to the verified tenant, actor and immutable request payload. Store its successful receipt with the mutation in the same transaction, and authorise receipt retrieval. Define how long that recovery record remains available and what the UI does after it expires.

A receipt answers whether this operation committed. The version answers whether a new edit is based on the expected resource state. Neither should stand in for the other. If the service cannot establish an outcome, show that uncertainty rather than announcing failure and inviting an unbounded resubmission.

Also separate downstream effects from the ticket write. If saving triggers a notification, a successful row update does not prove that notification was delivered. Give delivery its own durable tracking and duplicate protection rather than repeating the edit to resend the message.

Release with an adversarial save checklist

Use synthetic tickets and test the real client, API and database together. These are proposed acceptance cases, not reported benchmark results:

  • Open the same ticket in two sessions. Save one edit, then submit the other with its original validator. The second must not erase the first.
  • Remove the precondition and verify the documented 428 response. Submit a stale validator and verify 412 with no mutation.
  • Race two saves against the same starting version. Verify the committed record, version and receipts, not merely the HTTP responses.
  • Revoke access after loading. Neither saving nor conflict recovery may reveal newly restricted data.
  • Switch Arabic and English while editing. Preserve the ticket, workspace, pending values and original validator together.
  • Lose the response after commit. Recover the existing operation without creating another edit or duplicate notification.
  • Update through an import or administrative tool. Confirm that browser edits loaded before that change become stale.

For a CTO, the acceptance decision is whether every writer participates and whether users can recover without losing work. For the engineering team, the evidence is a version check enforced where the mutation commits, plus a usable conflict path. Start with one important editor and prove those cases before applying the same contract across the portal.

About the author

  • I have lead the Engineering for multiple startups in UAE. I also have my own agency qualascend.com.

No comments yet. Be the first to comment.