Filters You Register Once and Cannot Forget
The rules every query obeys, including the endpoint added next year
Some rules must hold for every record an application hands out, whoever asks and whichever screen is asking. A deleted row stays hidden. A customer sees only their own data. Written once per endpoint, rules like these hold until the one endpoint that forgets them, and that endpoint is the leak.
The basics named filters per entity: one record, one builder. A global filter is a builder registered once, against an interface, that the pipeline runs for every entity with that shape. No entity opts in and no developer has to remember it. The next two parts are built on one each: archiving and free-text search.
Universal or Regira? Putting a rule in the data layer instead of in every endpoint is the pattern, and plain EF Core has a query filter on the DbContext for it. Registering one builder against an interface so that it runs for every entity of that shape, and the built-in set UseDefaults() brings, are Regira Entities.
Where a global filter runs is what makes it reliable. It sits in the query pipeline, ahead of the entity's own filter, so it applies to lists, to /search totals, to Count and to direct service-layer calls from your own code: everything that reads through the pipeline. The one way past it is to skip the pipeline altogether, with raw SQL or a query written on the DbContext directly, and nobody does that by accident.
The rules you already have
UseDefaults(), the one-liner from the basics, registers the built-in set: the Id/Ids/Exclude filters, the archived-row handling and the free-text Q filter that the next two parts explain, and timestamp ranges. The sample shows "registered once, works everywhere" without a line of your own, because the base SearchObject fields work on both entities:
curl "$URL/vehicles?ids=1&ids=3" # TRK-001, TRK-003
curl "$URL/vehicles?exclude=2&exclude=4" # TRK-001, TRK-003, VAN-002
curl "$URL/products?ids=2" # Dash cam
Nobody wrote an id filter for Vehicle or Product. The global builder saw a search object with ids in it and applied the predicate, for any entity.
Adding your own: the tenant filter
The classic custom global filter is row-level tenancy: one customer must never see another's rows. Mark tenant-owned entities with an interface (IHasTenantId), write one builder against it that compares each row's tenant with the caller's, and register it next to the defaults:
options.UseDefaults();
options.AddGlobalFilterQueryBuilder<FilterHasTenantQueryBuilder>(); // yours: every IHasTenantId
From here on, tenant isolation is a property of the data layer rather than a discipline every endpoint has to keep up, and a new entity joins by implementing IHasTenantId. The builder itself (one Where in a GlobalFilteredQueryBuilderBase<IHasTenantId, int>), the claims plumbing and the honest list of gotchas (raw SQL, background jobs, attachment links) are in One Database, Many Customers, No Leaks; the open-source Fleet back end runs the same filter in production.
When the key is not an int
Global filters are generic over the entity's key type, and the defaults come in the int variant. Registering an entity with another key — a string, a Guid — adds the matching variant of every default global filter for you, so ids, exclude, ?archived=, the date ranges and ?q= behave the same whatever the key; there is nothing to register. Your own global filter needs no variant either as long as it reads no search-object field: registered int-keyed, it still runs for a string-keyed entity, only without a search object, which is all a rule like the tenant filter needs.
Why not a Where in every endpoint? A rule that lives in the pipeline is provable: one registration, one place to read it. A rule repeated per endpoint is only hoped for, and it has to be repeated again for Count, for the /search total and for every service call your own code makes.
Next, the built-in global filter that changes what DELETE means: Delete Without Losing Anything.
Runnable: the series' API sample answers every curl above — grab it and dotnet run.
Docs: Regira Entities — global filter query builders and defaults.