> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ditto.live/llms.txt
> Use this file to discover all available pages before exploring further.

# Strict Mode

> Available in v4.11 and later, strict mode helps Ditto enforce structure and CRDT type safety in your collections.

## Introduction

With strict mode enabled (the default for all Ditto 4.x SDKs), all fields are treated as a [register](/key-concepts/syncing-data#registers) by default.
When enabled, every field in a document must match the collection definition exactly, including its CRDT type (e.g., [map](/key-concepts/syncing-data#maps), [register](/key-concepts/syncing-data#registers), [counter](/key-concepts/syncing-data#counters)).

Disabling strict mode enables new functionality: when set to `false`, **collection definitions are no longer required when using multiple CRDT types.**\
SELECT queries will return and display all fields when strict mode is disabled. This matches the behavior of the legacy query language, objects in INSERT and UPDATE statements are treated as maps. When a field has multiple possible CRDT types, the most recently updated type is chosen.

When strict mode is disabled, Ditto will infer the CRDT type based on the document's shape:

* **Objects** → treated as **CRDT maps**
* **Scalars and arrays** → treated as **Registers**
* **Counters and attachments** → inferred from operations

<Warning>
  **Important for Cross-Peer Synchronization**

  When peers have different `DQL_STRICT_MODE` settings:

  * Data WILL sync between peers, but behavior changes
  * When strict mode is `true` (SDK 4.10 and earlier), nested objects default to REGISTER type
  * For consistent behavior, either use matching strict mode settings across all peers OR explicitly define MAP types in your collection definitions in 4.10 and below.

  See [Cross-Peer Synchronization](#cross-peer-synchronization) for detailed examples.
</Warning>

## Strict Mode Behavior

<Info>
  Strict mode is enabled (set to `true`) by default in SDK 4.x, but will be set to `false` by default in SDK 5.0 and later.
  If you are using SDK 4.11+, ensure you have configured strict mode by setting `DQL_STRICT_MODE=false`, before starting your sync.
</Info>

| **Feature**            | **`SDK <4.10: DQL_STRICT_MODE=true`**    | **`SDK 4.11+: DQL_STRICT_MODE=false`** |
| ---------------------- | ---------------------------------------- | -------------------------------------- |
| Nested MAPs            | Requires explicit collection definitions | Automatically inferred                 |
| Collection Definitions | Required to use non-register CRDT types  | Only required to use register objects  |
| Legacy Compatibility   | Difficult, due to collection definitions | Supported out of the box               |
| Default object type    | REGISTER (whole object replacement)      | MAP (field-level merging)              |
| Nested field updates   | Replaces entire object                   | Merges individual fields               |

## HTTP Usage

To use the HTTP API with strict mode disabled, use the `/api/v5/store/execute` endpoint.

### Endpoint Compatibility

| **Endpoint**            | **Supports `DQL_STRICT_MODE=false`** | **Use When**                           |
| ----------------------- | ------------------------------------ | -------------------------------------- |
| `/api/v4/store/execute` | ❌ No                                 | All peers have `DQL_STRICT_MODE=true`  |
| `/api/v5/store/execute` | ✅ Yes                                | All peers have `DQL_STRICT_MODE=false` |

The v5 and v4 HTTP APIs [are compatible](/sdk/latest/release-notes/versioning#synchronization-compatibility), so you can use the v5 endpoint and changes will sync to v4 clients without issues.

**Example HTTP Request:**

```bash theme={null}
# Use v5 endpoint when DQL_STRICT_MODE=false sets `metadata` to MAP type
curl -X POST 'https://my_server.dittolive.app/<my_app_id>/api/v5/store/execute' \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "UPDATE orders SET metadata.updatedAt = :date WHERE _id = :id",
    "args": {"date": "2025-05-28", "id": "order-1"}
  }'
```

[Read more about the HTTP API](/cloud/http-api/getting-started).

## SDK Usage

First, disable strict mode before calling `startSync` or creating your DQL subscriptions, observers, or `execute` statements.

```swift theme={null}
await ditto.store.execute("ALTER SYSTEM SET DQL_STRICT_MODE = false")
ditto.startSync()
```

### Subscriptions

Subscriptions behave the same regardless of whether `DQL_STRICT_MODE` is enabled or disabled.

```swift theme={null}
ditto.sync.registerSubscription("SELECT * from orders WHERE _id.restaurantId = :restaurantId", args: [restaurantId: "01234"])
```

### Examples

With `DQL_STRICT_MODE=false`, objects are treated as maps.  In the following
examples, `items` is an object so it is treated as a map, with further nested objects, which are also treated as maps.
This nested structure is common in document databases.

```javascript theme={null}
let doc = {
  "_id": "my-id",
  "regionId": "01234",
  "items": {
     "shake": {...},
     "fries": {...},
  }
}
```

We can insert the document into the database using DQL.

```sql theme={null}
INSERT INTO orders DOCUMENTS (:doc)
ON ID CONFLICT DO UPDATE
```

With `DQL_STRICT_MODE=false`, Ditto infers the CRDT type based on the document's shape.\
No collection definition is required.

<CodeGroup>
  ```swift DQL_STRICT_MODE=false theme={null}
  // When strict mode is set to `false`, Ditto infers the CRDT type based on the
  // document's shape.
  try await ditto.store.execute("ALTER SYSTEM SET DQL_STRICT_MODE = false")
  ditto.startSync()

  // Therefore we don't need to specify the collection definition in our query.
  ditto.store.execute("SELECT * FROM orders WHERE _id = 'my-id'")
  {
    "_id": "my-id",
    "regionId": "01234",
    "items": {
       "shake": {...},
       "fries": {...},
    }
  }
  ```

  ```swift DQL_STRICT_MODE=true theme={null}
  // When strict mode is set to `true`, Ditto treats objects as registers, which means that
  // every non-register field's type must be specified in the collection definition.
  try await ditto.store.execute("ALTER SYSTEM SET DQL_STRICT_MODE = true")
  ditto.startSync()

  // In this case, the items field is a map, so it must be defined as such in the
  // collection definition.
  ditto.store.execute("SELECT * FROM COLLECTION orders (items MAP) WHERE _id = 'my-id'")
  {
    "_id": "my-id",
    "regionId": "01234",
    "items": {
       "shake": {...},
       "fries": {...},
    }
  }
  ```
</CodeGroup>

* When you define an object like `items: {"shake": ..., "fries": ...}` , **Ditto treats that as a MAP when strict mode is disabled**.
* Whether you use Query Builder or DQL, this structure is preserved and behaves the same on both insert and read.
* This is useful to know if you're working with dynamic key-value structures inside documents.

#### Updating Nested Maps

In 4.11 and above, use `UPDATE` statement to set nested MAPs.

**Using `UPDATE`**

<CodeGroup>
  ```sql 4.11+  theme={null}
  UPDATE orders 
  SET 
    metadata.updatedAt = '2025-05-28',
    metadata.updatedBy = '67c0faa40054d13a000c614a'
  WHERE _id = 'my-id'
  ```

  ```sql <4.10 theme={null}
  -- Arrow (->) functions are deprecated.  
  -- Arrow (->) will be removed in v5. 
  UPDATE COLLECTION orders (items MAP)
  SET metadata -> (
  	updatedAt -> '2025-05-28',
  	updatedBy -> '67c0faa40054d13a000c614a'
  ) 
  WHERE _id = 'my-id'
  ```
</CodeGroup>

#### Updating Nested Maps with Dynamic Keys

Use `INSERT` and `ON ID CONFLICT DO UPDATE` to update nested maps with dynamic
UUIDs. This is the same behavior as using `upsert` in the legacy query builder.

<CodeGroup>
  ```sql 4.11+ theme={null}
  INSERT INTO orders DOCUMENTS (
    _id: 'my-id', 
    items: {
      '7c6a163e-3233-46db-a8ba-f604c4b8f':  { 'basePrice': 200 }, 
      '7c6a163e-3233-46db-a8ba-f604c4b8fdde':  { 'basePrice': 100 } 
    }
  ) ON ID CONFLICT DO UPDATE
  ```

  ```sql <4.10 theme={null}
  INSERT INTO orders (items MAP) DOCUMENTS (
    _id: 'my-id', 
    items: {
      '7c6a163e-3233-46db-a8ba-f604c4b8f': { 'basePrice': 200 }, 
      '7c6a163e-3233-46db-a8ba-f604c4b8fdde':{ 'basePrice': 100 }
    }
  ) ON ID CONFLICT DO UPDATE
  ```
</CodeGroup>

#### Deleting Nested Values

In 4.11 and above with `DQL_STRICT_MODE=false`, use the `UNSET` statement.

<CodeGroup>
  ```sql 4.11+  theme={null}
  UPDATE orders 
  UNSET items.abc 
  WHERE _id = 'my-id'
  ```

  ```sql <4.10 theme={null}
  -- Arrow (->) and tombstone functions are deprecated.  
  -- Arrow (->) will be removed in v5. 

  UPDATE COLLECTION orders (items MAP)
  SET items -> (
  	abc -> tombstone() 
  ) 
  WHERE _id = 'my-id'
  ```
</CodeGroup>

### Register Objects

A **`REGISTER`** is a data type in Ditto that stores a single scalar value and
uses last-write-wins merge strategy to atomically handle conflicts, rather than maps which use add-wins to create a merged object.

With `DQL_STRICT_MODE=false`, if you want a register object (JSON-like object) data type in DQL, it must be
specified explicitly.

Key characteristics of registers:

* Stores primitive types (string, boolean) or JSON-like objects
* Last-write-wins conflict resolution ensures consistent values across peers

```sql theme={null}
UPDATE COLLECTION orders (updatedAt REGISTER)  
SET updatedAt = {
	"datetime": "2025-02-28", 
	"authorId": "67c0faa40054d13a000c614a"
} 
WHERE _id = 'my-id'

SELECT * FROM COLLECTION orders (updatedAt REGISTER) WHERE _id = 'my-id'
{
  "_id": "my-id"
  "regionId": "01234",
  "items": {
    "shake": {...},
    "fries": {...},
    "burger": {...}
  },
  "updatedAt": {
	  "datetime": "2025-02-28",
	  "updatedBy": "67c0faa40054d13a000c614a" 
  }
}
```

For more information on using types and definitions, see [DQL > Types and Definitions](/dql/types-and-definitions).

### Last Write Wins

With `DQL_STRICT_MODE=false`, if there is no collection definition provided,
writing objects will update a map, even if a register already exists for that field in Ditto.

This is called "last write wins" behavior, and it means that the last
operation to write to a field will overwrite any previous values, regardless of the type of the field.

```sql theme={null}
-- Set a register
UPDATE orders 
SET items = 'a_string_value' 
WHERE _id = 'my-id' 

-- Implicitly infers (items MAP) even though there is a register on disk
-- as items is an object 
UPDATE menus 
SET items.burger = {...} 
WHERE _id = 'my-id' 

-- When querying, Ditto will return the map
-- as it was updated most recently
SELECT * FROM menus WHERE _id = 'my-id'
{
  "_id": "my-id"
  "regionId": "01234",
  "items": {
    "shake": {...},
    "fries": {...},
    "burger": {...}
  }
}
```

If you do not supply the collection definition, a register can be treated as a nested map which can lead to unexpected behavior.
It is recommended that you always supply a collection definition if you want to force Ditto to use a register.

## Cross-Peer Synchronization

### Version Compatibility

| **Scenario**                                  | **v4.10 and earlier**           | **v4.11+**                                       |
| --------------------------------------------- | ------------------------------- | ------------------------------------------------ |
| All peers same setting                        | Requires collection definitions | Works with or without definitions                |
| Mixed settings, no definitions                | Nested updates may fail         | Objects default to REGISTER on strict=true peers |
| Mixed settings, with explicit MAP definitions | Works correctly                 | Works correctly                                  |

### Mixing Peers with Different Settings

You can mix peers with different strict mode settings, but understanding the behavior is crucial for proper data synchronization:

#### Matched Settings (Recommended)

When all peers use the same `DQL_STRICT_MODE` setting, behavior is predictable:

* With `false`: Objects are treated as MAPs by default, nested updates work as expected
* With `true`: Collection definitions are required, types must be explicitly defined

#### Mismatched Settings (Requires Careful Handling, Avoid Unless Necessary)

When peers have different `DQL_STRICT_MODE` settings:

1. **Data DOES sync** between peers regardless of settings differences
2. **Default type behavior changes**:
   * Peer with `DQL_STRICT_MODE=true` treats undefined objects as REGISTERs
   * Peer with `DQL_STRICT_MODE=false` treats objects as MAPs
3. **This affects nested field updates significantly**

**Example of the Issue:**

```sql theme={null}
-- Peer A (DQL_STRICT_MODE=false): Updates nested fields
ALTER SYSTEM SET DQL_STRICT_MODE=false

UPDATE cars
SET items.sub1 = 'foo', 
    items.sub2 = 'bar'
WHERE _id.location = '1234'

-- Peer B (DQL_STRICT_MODE=true): WITHOUT collection definition
SELECT * from cars WHERE _id.location='1234'
{
  "_id": {"location": "1234"}
}

-- Result: The entire cars.items object is missing
-- 'foo' and 'bar' values appear not to sync because the object
-- is treated as a REGISTER, not a MAP
```

**Solution Options:**

1. **Option 1: Match strict mode settings across all peers** (simplest)

```swift theme={null}
// All peers use the same setting
await ditto.store.execute("ALTER SYSTEM SET DQL_STRICT_MODE = false")
```

2. **Option 2: Explicitly define MAP types in collection definitions**

```sql theme={null}
-- When peers have different settings, explicitly define MAPs in 4.10 clients
-- Peer B (DQL_STRICT_MODE=true): WITH collection definition 
SELECT COLLECTION cars (items MAP)
WHERE _id.location = '1234'

{
  "_id": {"location": "1234"},
  "items": {
    "sub1": 'foo'
    "sub2": 'bar'
  }
}
```

## Troubleshooting Nested Field Sync Issues

### Common Symptom: "Nested fields are not syncing"

This is often caused by mismatched strict mode settings between peers without explicit MAP definitions.
In this case, the fields have actually all been synchronized correctly, but when querying the data is not displayed as expected.

#### Diagnostic Steps

1. **Check strict mode on all peers:**

```sql theme={null}
-- Run on each peer (4.11+) to check current setting
SHOW DQL_STRICT_MODE
```

2. **Verify your update query:**

```sql theme={null}
-- Example that might "fail" with mismatched settings
UPDATE orders 
SET metadata.updatedAt = '2025-05-28',
    metadata.updatedBy = 'user123'
WHERE _id = 'order-1'
```

3. **Check how the data appears on each peer:**

```sql theme={null}
-- Peer with DQL_STRICT_MODE=false sees nested updates
SELECT * FROM orders WHERE _id = 'order-1'
-- Returns: metadata: {updatedAt: '2025-05-28', updatedBy: 'user123'}

-- Peer with DQL_STRICT_MODE=true might see entire object replacement
SELECT * FROM orders WHERE _id = 'order-1'  
-- Returns: metadata as a single REGISTER value
```

#### Real-World Example Fix

**Problem:** Updating nested fields:

```sql theme={null}
-- v4.11+ or v5 client, DQL_STRICT_MODE=false
UPDATE orders
SET metadata.updatedAt = '2025-05-28',
	metadata.updatedBy = 'user123'
	table = '12'
WHERE _id ='order-1'


-- v4.10 client, DQL_STRICT_MODE=true
SELECT * FROM orders WHERE _id = 'order-1'
-- Returns: table: '12'
-- Result: 'table' appears, but 'metadata.updatedAt' and 'metadata.updatedBy' do not
```

**Root Cause:** The client has `DQL_STRICT_MODE=true` while the updating peer has `DQL_STRICT_MODE=false`. Without a MAP definition, `metadata` is treated as a REGISTER.

**Solutions:**

```sql theme={null}
-- Solution 1: Add MAP definition on 4.10 client, DQL_STRICT_MODE=true
SELECT * FROM COLLECTION orders (metadata MAP) WHERE _id = 'order-1'

-- Solution 2: Ensure both peers have same strict mode
ALTER SYSTEM SET DQL_STRICT_MODE = false
SELECT * FROM orders WHERE _id = 'order-1'
```
