Introduction

With strict mode enabled, all fields are treated as a register by default. When enabled, every field in a document must match the collection definition exactly — including its CRDT type (e.g., map, register, counter). Disabling strict mode enables new functionality: when set to false, collection definitions are no longer required. SELECT queries will return and display all fields by default. 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 types, the most recently updated type is chosen.
Important for Cross-Peer SynchronizationWhen peers have different DQL_STRICT_MODE settings:
  • Data WILL sync between peers, but behavior changes
  • When strict mode is true (4.10 and below), 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 for detailed examples.

Compatibility

FeatureSDK <4.10: DQL_STRICT_MODE=trueSDK 4.11+: DQL_STRICT_MODE=false
Nested MAPs❌ Requires explicit definitions✅ Supported (automatic inference)
Collection Definitions❌ Required✅ Optional
Legacy Compatibility❌ Not supported✅ Supported
Default object typeREGISTER (whole object replacement)MAP (field-level merging)
Nested field updates without definitions⚠️ Replaces entire object✅ Merges individual fields
Strict mode is enabled (set to true) in v4, but will be set to false in v5.

HTTP Usage

Critical for Cross-Peer Sync with HTTP APIWhen using HTTP API with SDK peers:
  • If SDK peers have DQL_STRICT_MODE=false, you MUST use the /store/v5/execute endpoint
  • The v4 endpoint (/store/v4/execute) does not support DQL_STRICT_MODE=false
  • Mismatched API versions can cause nested field sync issues similar to mismatched strict mode settings
To use the HTTP API with strict mode disabled, use the /store/v5/execute endpoint. This endpoint allows you to execute DQL statements without needing to define collection types and properly handles DQL_STRICT_MODE=false behavior. Endpoint Compatibility:
EndpointSupports strict_mode=falseUse When
/store/v4/execute❌ NoAll SDK peers have strict_mode=true
/store/v5/execute✅ YesAny peers have strict_mode=false
v5 and v4 API are compatible, so you can use /store/v5/execute and changes will sync to v4 clients without issues. Example HTTP Request:
# Use v5 endpoint when strict_mode=false sets `metadata` to MAP type
curl -X POST https://api.ditto.live/store/v5/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.

SDK Usage

First, disable strict mode before calling startSync or creating your DQL subscriptions, observers, or execute statements.
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.
ditto.sync.registerSubscription("SELECT * from orders WHERE _id.restaurantId = :restaurantId", args: [restaurantId: "01234"])

Example

With DQL_STRICT_MODE=false, objects are treated as maps. In the following examples, items is a map: each key points to some object. This structure is common in NoSQL/document-style databases.
let doc = {
  "_id": "my-id",
  "regionId": "01234",
  "items": {
     "shake": {...},
     "fries": {...},
  }
}
Insert the document into the database using DQL.
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.
SELECT * FROM orders WHERE _id = 'my-id'
{
  "_id": "my-id",
  "regionId": "01234",
  "items": {
     "shake": {...},
     "fries": {...},
  }
}
  • 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
UPDATE orders 
SET 
  metadata.updatedAt = '2025-05-28',
  metadata.updatedBy = '67c0faa40054d13a000c614a'
WHERE _id = 'my-id'

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.
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

Deleting Nested Values

In 4.11 and above with DQL_STRICT_MODE=false, use the UNSET statement.
UPDATE orders 
UNSET items.abc 
WHERE _id = 'my-id'

Register Maps

A REGISTER is a data type in Ditto that stores a single scalar value and uses last-write-wins merge strategy for handling conflicts. With DQL_STRICT_MODE=false, if you want a REGISTER JSON object data type in DQL, it must be specified explicitly. Key characteristics of REGISTER:
  • Stores primitive types (string, boolean) or JSON objects
  • Last-write-wins conflict resolution ensures consistent values across peers
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.

How it works

When DQL_STRICT_MODE=false, Ditto is more flexible and will infer the CRDT type based on the document’s shape, meaning that collection definitions are no longer required:
  • Objects → treated as CRDT maps
  • Scalars and arrays → treated as Registers
  • Counters and attachments → inferred from operations
When strict mode is set to true, Ditto infers the CRDT type based on the document’s shape. By default, objects treated as registers, which means that every field’s type must be specified in the collection definition. In this case, the items field is a map, so it must be defined as such in the collection definition.

Last Write Wins

With DQL_STRICT_MODE=false, if there is no collection definition provided, objects will be implicitly updating the MAP after a dot update operation, even if a register exists on disk. 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.
// 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. 
UPDATE menus 
SET items.burger = {...} 
WHERE _id = 'my-id' 

// Last write wins -- MAP
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 data loss. This means it is recommended that you always supply a collection definition if you want to force Ditto to use a register. ❌ Do not do this
UPDATE orders 
UNSET updatedAt.datetime = "2025-04-28"
WHERE _id = 'my-id'

SELECT * FROM orders WHERE _id = 'my-id'
{
  "_id": "my-id"
  "regionId": "01234",
  "items": {
    "shake": {...},
    "fries": {...},
    "burger": {...}
  },
  "updatedAt": { ## implicit map because of Last Write Wins behavior
	"datetime": "2025-04-28" 
  }
}

✅ Do this instead:
UPDATE COLLECTION orders (updatedAt REGISTER)  
SET updatedAt = {
	"datetime": "2025-05-01", 
	"authorId": "13aa600aa40c61407c0f054d"
} 
WHERE _id = 'my-id'

SELECT * FROM orders (updatedAt REGISTER) WHERE _id = 'my-id'

{
  "_id": "my-id"
  "regionId": "01234",
  "items": {
    "shake": {...},
    "fries": {...},
    "burger": {...}
  },
  "updatedAt": { # register
	  "datetime": "2025-05-01",
	  "updatedBy": "13aa600aa40c61407c0f054d" 
  }
}

Cross-Peer Synchronization

Mixing Peers with Different Settings

You can mix peers with different strict mode settings, but understanding the behavior is crucial for proper data synchronization: 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)

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:
-- Peer A (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 (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)
// All peers use the same setting
await ditto.store.execute("ALTER SYSTEM SET DQL_STRICT_MODE = false")
  1. Option 2: Explicitly define MAP types in collection definitions
-- When peers have different settings, explicitly define MAPs in 4.10 clients
-- Peer B (strict_mode=true): WITH collection definition 
SELECT COLLECTION cars (items MAP)
WHERE _id.location = '1234'

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

Version Compatibility

Scenariov4.10 and earlierv4.11+
All peers same settingRequires collection definitionsWorks with or without definitions
Mixed settings, no definitionsNested updates may failObjects default to REGISTER on strict=true peers
Mixed settings, with MAP definitionsWorks correctlyWorks correctly

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.

Diagnostic Steps

  1. Check strict mode on all peers:
-- Run on each peer to check current setting
SELECT * FROM ditto_system_info WHERE key = 'DQL_STRICT_MODE'
  1. Verify your update query:
-- Example that might "fail" with mismatched settings
UPDATE orders 
SET metadata.updatedAt = '2025-05-28',
    metadata.updatedBy = 'user123'
WHERE _id = 'order-1'
  1. Check how the data appears on each peer:
-- Peer with strict_mode=false sees nested updates
SELECT * FROM orders WHERE _id = 'order-1'
-- Returns: metadata: {updatedAt: '2025-05-28', updatedBy: 'user123'}

-- Peer with 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 fieldse:
-- v4.11+ or v5 client, strict_mode=false
UPDATE orders
SET metadata.updatedAt = '2025-05-28',
	metadata.updatedBy = 'user123'
	table = '12'
WHERE _id ='order-1'


-- v4.10 client, 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 strict_mode=true while the updating peer has strict_mode=false. Without a MAP definition, metadata is treated as a REGISTER. Solutions:
-- Solution 1: Add MAP definition on 4.10 client, 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'