---
url: "https://vueda.dev/v3/core-concepts/drf-ecosystem-deviations.md"
title: "DRF Ecosystem Compatibility Boundaries"
---

# DRF Ecosystem Compatibility Boundaries

VUEDA's server builds on DRF, django-filter, drf-flex-fields2, and drf-writable-nested. Its defaults change some of their behavior. A client written for plain DRF meets different parameter names, stricter validation, and different response bodies. This page lists each departure briefly and links the page that describes it.

## Query Parameter Names

DRF's [DRF: SearchFilter](https://www.django-rest-framework.org/api-guide/filtering/#searchfilter) and [DRF: OrderingFilter](https://www.django-rest-framework.org/api-guide/filtering/#orderingfilter) read `search` and `ordering` by default, and drf-flex-fields2 reads `expand`, `fields`, and `omit`. VUEDA sets short names for these and for pagination and column totals: `s`, `o`, `p`, `ps`, `e`, `f`, `om`, and `ct`. These are the [Wire Query Parameters](/v3/reference/glossary.md#wire-query-parameters). The client holds each name as a constant, so a rename on the server needs the same change on the client. [Configuration Surface and Defaults](/v3/core-concepts/configuration-surface-and-defaults.md#wire-query-parameter-namespace) lists each name with its server setting and client constant.

## Default Filter Backends

DRF configures no filter backend by default. VUEDA's `REST_FRAMEWORK` defaults run three on every `list` endpoint, in this order:

1. [VuedaOrderingFilter](/v3/reference/api/py/vueda.core.filters/VuedaOrderingFilter.md) extends `OrderingFilter`. It applies a view's nulls placement to requested ordering, and it accepts every field in the view's default ordering as an `o` value.
2. [VuedaSearchFilterBackend](/v3/reference/api/py/vueda.core.filters/VuedaSearchFilterBackend.md) extends `SearchFilter` with the search prefixes `#` (trigram similarity), `~` (word similarity), and `V:` (ranked search). It runs after ordering so that ordering does not reorder ranked results. A search field that reaches through a many-valued relation matches inside a subquery, built from the model's default manager or the viewset's `get_search_queryset()`. `SearchFilter` instead copies the viewset's queryset, filters and aggregates included. [Search through a many-valued relation](/v3/core-concepts/filtering-and-ordering-semantics.md#search-through-a-many-valued-relation) describes the rule.
3. django-filter's [`DjangoFilterBackend`](https://django-filter.readthedocs.io/en/stable/guide/rest_framework.html) applies the viewset's `filterset_class`.

A viewset that sets its own `filter_backends` replaces this list. [Filtering and Ordering Semantics](/v3/core-concepts/filtering-and-ordering-semantics.md) describes each backend's rules.

## Unknown Input Returns 400

DRF ignores query parameters that a view does not read and request body keys that a serializer does not declare. VUEDA answers both with a `400` validation error that names the valid choices:

- [NoExtraFieldsForViewSetMixin](/v3/reference/api/py/vueda.core.viewsets/NoExtraFieldsForViewSetMixin.md), part of [VuedaViewSet](/v3/reference/api/py/vueda.core.viewsets/VuedaViewSet.md) and [VuedaReadOnlyViewSet](/v3/reference/api/py/vueda.core.viewsets/VuedaReadOnlyViewSet.md), rejects any query key that `list` or `retrieve` does not accept. It also rejects `f` and `e` values that name no field or expand. [Filtering and Ordering Semantics](/v3/core-concepts/filtering-and-ordering-semantics.md) describes these [validation rules](/v3/reference/glossary.md#query-parameter-validation).
- [NoExtraFieldsSerializerMixin](/v3/reference/api/py/vueda.core.serializers/NoExtraFieldsSerializerMixin.md), part of [VuedaSerializer](/v3/reference/api/py/vueda.core.serializers/VuedaSerializer.md), rejects top-level body keys that the serializer does not declare. It checks only the view's own serializer class, so keys inside a nested payload are not checked. [Error and Validation Contract](/v3/core-concepts/error-and-validation-contract.md) describes the error body.

A typo or a stale client gets an error that names the valid keys. An integration that adds its own query parameters or body keys fails until the viewset or serializer declares them.

## Response Bodies and Authentication

VUEDA's defaults change these responses and the accepted credentials:

- **List pagination.** [VUEDAPageNumberPagination](/v3/reference/api/py/vueda.core.pagination/VUEDAPageNumberPagination.md) returns `results`, `columnTotals`, `perPage`, `totalPages`, and `totalRecords`. DRF's [DRF: PageNumberPagination](https://www.django-rest-framework.org/api-guide/pagination/#pagenumberpagination) returns `count`, `next`, `previous`, and `results`. [Expose Aggregates in `list` Responses](/v3/guides/list-column-totals.md) describes `columnTotals`.
- **Error bodies.** [core.exceptions.debug\_stack\_exception\_handler](/v3/reference/api/py/vueda.core.exceptions.md#debug_stack_exception_handler) moves a list of errors under `non_field_errors`, and returns a JSON `500` for exceptions that DRF does not handle. It adds `serverStack` to every error body, except the `409` of [Warning Confirmation](/v3/reference/glossary.md#warning-confirmation). [Error and Validation Contract](/v3/core-concepts/error-and-validation-contract.md) describes these shapes.
- **Durations.** When a `timedelta` reaches [VuedaJSONRenderer](/v3/reference/api/py/vueda.core.renderers/VuedaJSONRenderer.md), the renderer writes it as a number of seconds. DRF's renderer writes a string. This affects values that bypass serializer fields, such as a duration column total. A serializer `DurationField` still returns its string form.
- **Authentication.** DRF's [DRF: SessionAuthentication](https://www.django-rest-framework.org/api-guide/authentication/#sessionauthentication) is the only authentication class. A client sends the session cookie, and writes also need the CSRF token.

## Registration Gates Model Info

In plain DRF, a viewset and a router entry give a model its REST API. VUEDA's [Model Info](/v3/reference/glossary.md#model-info) endpoints also require the model's [Canonical Registration](/v3/reference/glossary.md#canonical-registration) through [info.registration](/v3/reference/api/py/vueda.info.registration.md). A model with a viewset but no registration has a REST API but no model info, which the default UI needs to build its views. [Canonical Registration and Model Discovery](/v3/core-concepts/canonical-registration-and-discovery.md) describes registration.

## Flex Fields with Nested Writes

`VuedaSerializer` combines drf-flex-fields2 expansion with drf-writable-nested writes. [Field and Expand Semantics](/v3/core-concepts/field-and-expand-semantics.md) describes [Expand](/v3/reference/glossary.md#expand) and [Sparse Fields](/v3/reference/glossary.md#sparse-fields). [Nested Write Compatibility](/v3/core-concepts/nested-write-compatibility.md) describes what changes when the two packages share one serializer.

## Composite Primary Keys

`VuedaSerializer` maps Django's [Django: CompositePrimaryKey](https://docs.djangoproject.com/en/6.1/ref/models/fields/#django.db.models.CompositePrimaryKey) to [CompositePrimaryKeyField](/v3/reference/api/py/vueda.core.serializers.fields/CompositePrimaryKeyField.md) without a field declaration. [`VuedaViewSet.get_object`](/v3/reference/api/py/vueda.core.viewsets/VuedaViewSet.md#get_object) reads the key from the URL as a JSON list. [Set Up CRUD for a Composite Primary Key Model](/v3/guides/composite-primary-keys.md) gives the URL format and how to pass the key to [Django: reverse](https://docs.djangoproject.com/en/6.1/ref/urlresolvers/#django.urls.reverse).
