AutoQuery Schema

If your App has AutoQuery APIs, open /auto in any .NET 8+ ServiceStack App and you already have an admin application: a searchable list of your data models, and behind each one a working CRUD app with a results grid, paging, sorting, filters, saved preferences, Create and Edit forms, reference lookups and guarded Delete actions.

No frontend project, no generated source files, no scaffolding step. Every action it offers is one the signed-in user is authorized to perform, because the page is assembled at runtime from your APIs and the current session.

That changes what a data UI costs. A back-office screen that would have been a sprint of grid, form, validation, lookup and permission work is now the thing you get before deciding whether a bespoke UI is worth building.

Model-level, not API-level

Where an API Schema describes how one /api/{RequestDto} endpoint can be rendered and executed, an AutoQuery Schema describes the whole data capability: its Query API, returned model and every authorized Create, Update, Patch, Delete or Save API.

The integration is deliberately small - fetch that one document and give it to the generic component:

<AutoQuerySchema :schema="schema" />

These routes belong to the existing Metadata feature, so there's nothing to install.

The schema envelope

/auto/Booking.json combines a model and every API available for working with it:

{
  "name": "Booking",
  "title": "Booking",
  "primaryKey": "Id",
  "model":  { "type": "object", "properties": {} },
  "query":  { "$id": "/api/QueryBookings", "method": "GET",    "operation": "Query",  "properties": {} },
  "create": { "$id": "/api/CreateBooking", "method": "POST",   "operation": "Create", "properties": {} },
  "update": { "$id": "/api/UpdateBooking", "method": "PATCH",  "operation": "Patch",  "properties": {} },
  "delete": { "$id": "/api/DeleteBooking", "method": "DELETE", "operation": "Delete", "properties": {} }
}
Key Description
name The data model type name
title From [Description], or the humanized model name
primaryKey Which property identifies a row, so UIs can deep link to one
model The writable model schema
viewModel Only present when the query returns a different shape
query The Query API - required; without access there is no schema
create update delete save Present only when an authorized API exists

Each of those API entries is a full API Schema, so the same generic form components render them.

INFO

viewModel matters when a query joins, projects or enriches stored data. Rows returned by IQueryDb<From, Into> are the Into type, whilst Create/Update/Delete still write through the From type - so grids display the returned view whilst forms use the writable model.

A full AutoQuery grid with no frontend code

The results grid calls the schema's Query API and provides:

  • Server-side paging and per-column sorting
  • AutoQuery's typed filter conventions
  • Multiple filters and sort expressions
  • Selectable visible columns and configurable page sizes
  • Formatted values from [Intl] and [Format] metadata
  • Persistent per-model preferences
  • Responsive light/dark modes

Query state is kept in the URL, so a filtered view can be bookmarked, refreshed or shared:

That URL is not a screenshot of transient client state - it's a durable link back to the same server-side query.

Schema-driven Create, Edit and Delete

Each form preserves the exact behavior of its API - required values and validation constraints shown before submission, server validation errors bound back to their fields, enums and allowable values as selections, editable nested objects and collections, multipart uploads for file properties, and HTTP methods and payloads taken from the schema.

Patch APIs receive only changed values. When a user clears an existing field the UI adds ServiceStack's reset instruction so the server can distinguish "set this to empty" from "leave this field unchanged" - the kind of edge case custom CRUD UIs frequently get wrong, solved once for every model.

References become live lookup UIs

Foreign keys are often where generated CRUD tools stop feeling like real applications. Asking a user to remember that "Customer 1042" is "Acme Inc." is technically accurate and practically unusable.

AutoQuery Schemas preserve reference metadata from [Ref], [References], [ForeignKey] and related attributes. The renderer displays a lookup control that resolves the current label and opens a full searchable picker for the referenced model - with the same paging, sorting, filters and column preferences as the main grid.

Referenced model schemas are loaded on demand from /auto/{ReferencedModel}.json, so the parent schema stays compact.

Authorization shapes the App

Generated UIs are only useful when they preserve the application's security model. The catalog, model schema and CRUD UI are all generated for the current authenticated session:

  • A model is only listed when its Query API can be accessed.
  • Query access is required - without it, requesting the schema returns 401 or 403.
  • Create appears only when the user can call the Create API.
  • Rows become editable only when Update or Patch is available.
  • Delete appears only when the selected Delete API is authorized.
  • Each action can carry different roles, permissions, claims, scopes or API-key requirements.

This is more precise than a single "admin page" permission. A support user may have read access, an operator may create and edit, and an administrator may also delete - all from the same generated UI.

Conventions for real AutoQuery APIs

ServiceStack derives the most useful CRUD surface from whichever API shapes are available:

Convention Behavior
Query API Required - it's what grants access to the schema at all
Update IPatchDb<T> is preferred over IUpdateDb<T> when both exist
Delete A single-row delete is preferred over a bulk delete
Availability Operations are included only when they exist and are authorized
Primary key Discovered from [PrimaryKey], [AutoIncrement], Id or {Model}Id
View models Represented separately when they differ from the write model

Where this fits alongside Locode

/auto Locode Custom UI
Best for Embedding data UIs in your own App A complete standalone admin App Product surfaces users live in
Loads One model's schema at a time The App's full metadata Whatever you build
Customization Compose the Vue/React components yourself Locode's customization model Total
Runs where Built-in page or inside your App Built-in page Your App

They aren't competing and neither is going away. Locode remains the fuller standalone back-office experience. /auto is the schema-driven equivalent that scales to very large API surfaces and - the part that matters most - can be taken apart: the grid, the forms, the lookups and the field inputs are components you can drop into your own application's navigation and design system.

Use AutoQuerySchema in your own Apps

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { AutoQuerySchema } from '@servicestack/vue'

const schema = ref()

onMounted(async () => {
    schema.value = await fetch('/auto/Booking.json').then(r => r.json())
})
</script>

<template>
  <AutoQuerySchema v-if="schema" :schema="schema" />
</template>

@servicestack/react ships the same components with the same names:

import { useEffect, useState } from 'react'
import { AutoQuerySchema } from '@servicestack/react'

export default function Bookings() {
    const [schema, setSchema] = useState(null)

    useEffect(() => {
        fetch('/auto/Booking.json').then(r => r.json()).then(setSchema)
    }, [])

    return schema ? <AutoQuerySchema schema={schema} /> : null
}

For more specialized experiences, both libraries expose the lower-level components independently:

Component Purpose
AutoQuerySchema The complete CRUD App for one model
SchemaResults A schema-powered query grid
SchemaGrid / SortableColumn Building blocks for a custom results view
SchemaInput Individual generated fields
SchemaLookup Reference pickers
JsonSchemaForm Arbitrary nested JSON Schema forms

From database to App in minutes

[Tag("Bookings")]
[Route("/bookings", "GET")]
public class QueryBookings : QueryDb<Booking> { }

[ValidateHasRole("Employee")]
[AutoApply(Behavior.AuditCreate)]
public class CreateBooking : ICreateDb<Booking>, IReturn<IdResponse>
{
    [ValidateNotEmpty]
    public string Name { get; set; } = "";

    public RoomType RoomType { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
}

Once the AutoQuery CRUD APIs exist, /auto/Booking is immediately useful. Add descriptions, validation, references and input metadata to refine both the APIs and their generated experience.

The structured foundation for AI-operated data

Because each write operation carries its own API Schema, AI Chat can render an editable preview and approval form whenever a Model proposes an AutoQuery Create, Update, Patch, Delete or Save - for every model, with no per-API Chat component to write.

This supports experiences such as:

  • "Show overdue invoices over $5,000, ordered by customer."
  • "Find tomorrow's bookings and move this one to the available conference room."
  • "Create a follow-up task for every high-priority support case assigned to me."
  • "Find products below their reorder threshold and prepare updates for approval."

The grid UI and AI Assistant are two clients over the same typed capability layer: one starts with visual exploration, the other with natural language.

Configuration

The /auto routes belong to the Metadata feature and can be disabled independently of /schema:

services.AddServiceStack(typeof(MyServices).Assembly, options => {
    var metadata = options.Plugins.OfType<MetadataFeature>().First();
    metadata.DisableAutoQuerySchema = true;
});

IsAutoQuerySchemaEnabled reports whether they're registered. An App can therefore keep its API workbench whilst withholding the AutoQuery data UIs, or the reverse.

Schemas can be augmented before they're returned, which applies everywhere the schema is used:

services.ConfigurePlugin<MetadataFeature>(feature => {
    feature.OnAutoQuerySchema = (dataModel, schema) => {
        if (dataModel == typeof(Booking))
            schema["title"] = "Room Bookings";
    };
});

Get Started

Nothing to install. Run your App and open:

To embed the components in your own App:

npm install @servicestack/vue

npm install @servicestack/react

To get more out of the generated UIs, improve the APIs rather than the UI - [Description], validation attributes, [Input], [Ref] and [Intl]/[Format] all show up in the forms and grids.

  • API Schema - the per-API contract each CRUD entry above contains
  • AutoQuery RDBMS - the APIs these UIs are generated from
  • Locode - the standalone metadata-driven admin App