Skip to content

Compatibility and limitations

Conformance answers "does Dynoxide behave like DynamoDB on the things it implements". This page answers the other half: what it implements, what it doesn't, and where it knowingly differs.

What's implemented

Every DynamoDB operation with a meaningful local equivalent: item CRUD, Query and Scan, batch reads and writes, transactions, table management, PartiQL, vector search, streams, TTL, and resource tags. GSIs and LSIs are both supported, including UpdateTable adding or removing a GSI with existing rows backfilled into it.

All ten attribute types work (S, N, B, BOOL, NULL, SS, NS, BS, L, M), and the full expression syntax is supported: condition, filter, key-condition, projection and update expressions, with attribute_exists, attribute_not_exists, attribute_type, begins_with, contains and size.

That's every applicable operation: 28 of 28, with SearchVectors joining at 1.0.0. The compatibility summary has the operation-by-operation table, and the live capability matrix compares every emulator feature by feature.

What's not implemented, and why

Thirty-nine DynamoDB operations are absent on purpose. They're cloud-infrastructure features with nothing to emulate locally: backup and point-in-time restore, global tables, Kinesis streaming destinations, table import and export, reserved capacity and account limits, contributor insights, resource policies, and table replicas.

These are "not applicable" rather than "not implemented". There's no partial version of a global table on your laptop. Call one and you get an UnknownOperationException.

Vector search

AWS added vector search to DynamoDB in August 2026: vector indexes declared on CreateTable and adjusted through UpdateTable, a SearchVectors operation, and PartiQL syntax for querying them. Dynoxide implements it as of 1.0.0, on the native build and the browser build together.

CreateTable and UpdateTable take vector index definitions, DescribeTable reports them alongside the GSIs, the item write paths maintain them, and SearchVectors returns the nearest entries to a query vector over COSINE, EUCLIDEAN or DOT_PRODUCT. A search can be scoped by a SearchSchema hash attribute and filtered on INLINE_FILTER attributes. Index maintenance is atomic with the base write, the same as it is for a GSI. The MCP server gains a search_vectors tool with it.

An index added to a live table through UpdateTable walks a creation lifecycle rather than arriving ready, and it walks the same four phases DynamoDB walks, including the trap: the table reports ACTIVE, and then the index reports ACTIVE, some way before a search against it will answer. Poll by retrying the search and treating the refusal as "not yet" rather than waiting on a status. An index created as part of CreateTable is searchable at once, on both.

Parity Suite covers vector search across Tier 2 and Tier 3, and those tests were the largest block Dynoxide skipped before 1.0.0. A skip is scope rather than a wrong answer, so it left the divergence figure alone and lowered coverage instead. Conformance explains why the two are reported apart.

Four things about vector search that will never match AWS

These aren't bugs and they aren't going to be fixed. They follow from how the two systems are built, and each one can make a test pass here and fail against AWS.

Dynoxide finds the genuinely nearest vectors; AWS finds roughly the nearest ones. Dynoxide compares your query against every vector in the index. AWS builds an approximate index that skips most of those comparisons to stay fast on very large tables, and skipping comparisons means it can miss a close match that was really there. Don't pin the exact result list in a test that also runs against AWS. The same trade-off runs the other way on speed: because Dynoxide checks every vector, a local index holding millions of them is slow in a way AWS wouldn't be.

Ties are ordered stably here and arbitrarily there. Dynoxide breaks a tie by primary key, so the same query over the same data gives the same order every time. AWS makes no such promise, and three identical calls against real DynamoDB returned three different orderings. Sort ties yourself if you need an order you can assert on.

The creation lifecycle has AWS's shape and nothing like its length. The phases, the statuses and the refusals all match. The clock doesn't: Dynoxide runs the whole thing in about half a minute, where AWS takes minutes, and how many depends on how much data it has to index. Never sleep(30) and assume the index is ready.

VectorSearchRequestBytes is Dynoxide's own number. AWS bills a search on whatever data it happened to read, and it doesn't reproduce its own figure between identical calls. Dynoxide reports a stable figure sized on the entries it scanned, in the same unit AWS uses. Compare it between Dynoxide searches, where it means something, not against a figure from AWS. The write-side figure, VectorWriteRequestBytes, is captured from real DynamoDB and does match.

The compatibility summary has the lifecycle table and the rest of the detail.

Behavioural differences

Four more things behave differently from real DynamoDB, on top of the vector-search differences above. All four are visible from your test code, so they're worth knowing before you write assertions against them.

ConsistentRead is accepted but changes nothing. SQLite is strongly consistent, so every read already is. You can't reproduce an eventually-consistent read locally, which means a bug that only shows up under eventual consistency won't surface here.

Streams expose a single shard. DescribeStream returns one shard, and ExclusiveStartShardId and Limit are accepted but ignored. Code that walks a multi-shard topology or handles shard splits has no local equivalent to exercise. See Streams.

Transaction-contention errors aren't emulated. TransactionConflictException and TransactionInProgressException never fire, because there's no concurrent contention in a single process. If your retry logic keys off them, it won't be exercised locally.

Number arithmetic uses rust_decimal. It's arbitrary-precision and matches DynamoDB across the ranges that matter, but DynamoDB's implementation is proprietary and the two may diverge at extreme edges.

Legacy pre-2015 parameters

The pre-expression API has partial support. Only AttributeUpdates is actually processed:

Parameter Status
AttributeUpdates (UpdateItem) Partial - PUT, ADD and DELETE actions work, used when UpdateExpression is absent
Expected Accepted, ignored - use ConditionExpression
ScanFilter / QueryFilter Accepted, ignored - use FilterExpression
KeyConditions (Query) Accepted, ignored - use KeyConditionExpression
AttributesToGet Accepted, ignored - use ProjectionExpression
ConditionalOperator Accepted, ignored - use ConditionExpression with AND/OR

"Accepted, ignored" is the trap: a request using one of these succeeds, and the filter simply doesn't apply. Prefer the expression-based API throughout.

Error codes

Errors carry DynamoDB-compatible codes with the com.amazonaws.dynamodb.v20120810# prefix: ResourceNotFoundException, ResourceInUseException, ValidationException, ConditionalCheckFailedException (with the item attached where DynamoDB attaches it), TransactionCanceledException, ItemCollectionSizeLimitExceededException, ProvisionedThroughputExceededException, LimitExceededException, DuplicateItemException, and InternalServerError.

Message text is matched to real DynamoDB wherever the conformance suite covers it, including the 1 validation error detected: envelope on the request-validation families that carry it and bare messages on the families that don't.

Not a production database

Dynoxide is built for local development, testing, and CI. It doesn't emulate capacity management, throttling, replication, or IAM, and it isn't a hosted database. If you need any of that, you need real DynamoDB.