API Explorer

API Explorer is a Postman & Swagger UI alternative built into every ServiceStack v6+ App that lets you explore, discover & call your APIs with an Auto UI dynamically generated from your APIs typed C# classes.

It's built from the ground up with multiple levels of customizations, supporting both declarative & programmatic models for customizing each properties Input UI control, each APIs form grid layout whilst also providing the ability to provide rich interactive HTML Components to document each of your APIs & their Types.

This video provides a quick overview of API Explorer's v1 featureset:

DEMO

A Live demo is available at /ui in all ServiceStack v6+ Apps, e.g: vue-vite-api.jamstacks.net/ui

The entire API Explorer UI is driven by the rich metadata around your APIs typed Service Contracts and AppHost's registered plugins.

  • The Sidebar - Displaying a list of APIs each user has access to
  • The API tab - Providing dynamic form to call & inspect your APIs
  • The Details tab - Containing a complete description of your APIs & its dependent types
  • The Code tab - Letting API consumers browse API Service contracts in their preferred language (11 languages supported)

Lets learn about each feature with screenshots 📷

If your AppHost has the ServiceStack AuthFeature plugin registered, the home page will display a Sign In dialog based on the its configured Auth Providers.

This is what you'll see in a new Vue Vite project which has Credentials Auth, JWT as well as Facebook, Google and Microsoft OAuth providers registered in Configure.Auth.cs

Plugins.Add(new AuthFeature(() => new CustomUserSession(),
    new IAuthProvider[] {
        new JwtAuthProvider(appSettings) {
            AuthKeyBase64 = appSettings.GetString("AuthKeyBase64"),
        },
        new CredentialsAuthProvider(appSettings),
        new FacebookAuthProvider(appSettings),
        new GoogleAuthProvider(appSettings),
        new MicrosoftGraphAuthProvider(appSettings),
    })
{
    IncludeDefaultLogin = false
});

Integrated Sign In

Where it will dynamically render the Sign Up form with the App's enabled Auth capabilities.

Custom Auth Providers can provide their own Form Layout by overriding the FormLayout, e.g. the above Credentials UI is creatable with:

public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
    public CustomCredentialsAuthProvider()
    {
        FormLayout = new() {
            Input.For<Authenticate>(x => x.UserName, c =>
            {
                c.Label = "Email address";
                c.Required = true;
            }),
            Input.For<Authenticate>(x => x.Password, c =>
            {
                c.Type = "Password";
                c.Required = true;
            }),
            Input.For<Authenticate>(x => x.RememberMe),
        };
    }
    //...
}

Where the Input utility can be used to configure most HTML Form Input control properties that automatically configures to use the desired Input control for each property Type.

You can Sign In with any of the users in the Vue Vite Sign In page configured in Configure.AuthRepository.cs, i.e:

Username Password Role
admin@email.com p@55wOrd Admin
manager@email.com p@55wOrd Manager
employee@email.com p@55wOrd Employee

If signed in with the Admin User and the Admin Users plugin is configured:

Plugins.Add(new AdminUsersFeature());

It also displays Admin UI links that only Admin Users have access to.

If you'd like to, you can add personalized links for users in different roles, e.g. this is what's used to populate the above UI for Admins:

appHost.AddToAppMetadata(meta =>
{
    meta.Plugins.Auth.RoleLinks[RoleNames.Admin] = new List<LinkInfo>
    {
        new() { Href = "../admin-ui", Label = "Dashboard", Icon = Svg.ImageSvg(Svg.Create(Svg.Body.Home)) },
        new() { Href = "../admin-ui/users", Label = "Manage Users", Icon = Svg.ImageSvg(Svg.GetImage(Svg.Icons.Users, "currentColor")) },
    };
});

Once signed in, API Explorer expands to include all the protected APIs the signed in user has access to, identifiable with the padlock icon.

Disable API Explorer

INFO

API Explorer is powered by the rich API metadata provided by the MetadataFeature and can be customized through the UiFeature plugin. Removing either plugin disables API Explorer.

Plugins.RemoveAll(x => x is UiFeature);

API Tab

After selecting an API to use from the left-hand menu, you will be greeted with a way to call APIs through an Auto UI generated based on the Request DTO schema. Submitting the form returns API results with:

  • Body displaying a syntax highlighted JSON response
  • Raw showing raw JSON output in a textarea
  • Preview tab displaying results in a human-friendly view

INFO

The Raw response forces a CamelCase response since the API Explorer interface needs consistent casing outside your applications default TextCase. Those using SnakeCase or PascalCase will see a different response outside of API Explorer.

Control types are based on the property types in your DTOs.

UI Input Data Types
<select> Enum, Custom Values
<input type=number> Numeric Types
<input type=date> DateTime, DateTimeOffset, DateOnly
<input type=time> TimeSpan, TimeOnly
<input type=checkbox> Boolean
<input type=text> default

Where the CreateBooking Request DTO defined in Bookings.cs:

[Tag("bookings"), Description("Create a new Booking")]
[Route("/bookings", "POST")]
[ValidateHasRole("Employee")]
[AutoApply(Behavior.AuditCreate)]
public class CreateBooking : ICreateDb<Booking>, IReturn<IdResponse>
{
    [Description("Name this Booking is for"), ValidateNotEmpty]
    public string Name { get; set; }
    public RoomType RoomType { get; set; }
    [ValidateGreaterThan(0)]
    public int RoomNumber { get; set; }
    [ValidateGreaterThan(0)]
    public decimal Cost { get; set; }
    public DateTime BookingStartDate { get; set; }
    public DateTime? BookingEndDate { get; set; }
    [Input(Type = "textarea")]
    public string? Notes { get; set; }
}

Generates the following UI:

This also shows how we can use the [Input] attribute to further customize the Input UI control for each property as a declarative alternative to using Input.For<T>() above that the Notes property utilizes to change to use a textarea control instead.

API Form also supports auto binding Argument Exceptions or Fluent & Declarative Validation rules where any validation errors will be contextually displayed next to the invalid property. Here's both the resulting invalid UI & the Error Response DTO that generated it:

Contextual validation errors is used where possible, otherwise an Error Summary notification is displayed along with the API Response body containing the full API error information.

JSON Form

Another useful API form feature is being able to call APIs with a JSON request payload which maintains a 2-way sync with the Form's UI allowing you to quickly cycle between input modes to quickly construct your API request.

Real-time JSON validation is also displayed for added assistance, warning you whenever the JSON is malformed.

Details Tab

API Explorer also provides a place for users to find out more about your API through documentation generated by metadata about in your API and optionally custom HTML modules to give additional context.

This is where API consumers would go to learn about each API where it displays all relevant information about the API at a glance. For CreateBooking it shows that:

  • POST is the APIs preferred HTTP Method
  • List its user-defined and pre-defined routes
  • It's a protected API limited to Authenticated Users with the Employee role
  • It's categorized in the bookings tag group
  • It's an AutoQuery CRUD API implementing ICreateDb<Booking> indicating it creates entries in the Booking RDBMS Table
  • It returns an IdResponse which we can intuitively infer returns the new Booking Id for successfully created Bookings

This API Definition was generated from the CreateBooking DTO shows that the Required column used to document the APIs required properties is required for all properties except for the nullable Value and Reference Types when #nullable is enabled.

public class CreateBooking : ICreateDb<Booking>, IReturn<IdResponse>
{
    [Description("Name this Booking is for"), ValidateNotEmpty]
    public string Name { get; set; }
    public RoomType RoomType { get; set; }
    [ValidateGreaterThan(0)]
    public int RoomNumber { get; set; }
    [ValidateGreaterThan(0)]
    public decimal Cost { get; set; }
    public DateTime BookingStartDate { get; set; }
    public DateTime? BookingEndDate { get; set; }
    [Input(Type = "textarea")]
    public string? Notes { get; set; }
}

INFO

Importantly Required annotations are only documentative, your API still has to validate required reference types like string using your preferred validation method, e.g. Using the [ValidateNotEmpty] declarative attribute, Fluent Validation or manual verification in your Service C# implementation and throwing ArgumentException for invalid properties

All this data is inferred from your services, with the ability to present data from additional metadata attributes such as:

Attribute name Description
[Description] Class and properties text only description.
[Notes] Class only text and HTML description.
[Tag] Class only categorization of services, a way to group services.
[Input] Properties only presentation data for input fields.

If services require authentication using Authenticate or validation checking for role or permission, services will be shown with a padlock (🔒) signifying requiring authentication.

Request and response names are links to show C# generated code representations of your DTOs and dependent types. Text metadata such as [Description] will also flow down into the generated code as comments for additional context.

Code Tab

The Code tab allows developers consuming your APIs from different programming backgrounds explore each APIs in their preferred programming language - currently supporting 11 different languages.

It includes the necessary steps to call your APIs from client Apps, following the same pattern for each language:

  1. Copy your API DTOs
  2. Copy and install the package containing ServiceStack's generic JSON Service Client
  3. Copy the initial source code pre-configured to call the API they want

At which point without any code-gen or build tools, they'll end up with an Typed API configured to your APIs endpoint. E.g. this is what it looks like to Python developers utilizing our most recently supported language:

That they can follow to quickly incorporate your API into their existing Apps, in addition Python, C# or F# developers could also copy this source code to their Jupyter Notebooks for an instant typed visual REPL to explore your APIs.

This is driven by the services that power the Add ServiceStack Reference feature. This growing list of support languages shows example code using the specific API as well as required ServiceStack client libraries to use, and how to update the APIs DTOs.

Responsive Design

API Explorer's responsive layout works well in Smart Phones and Tablets letting you comfortably browse and call APIs on the go:

API Customizations

To become the preferred solution to document APIs, API Explorer was designed from scratch to support multiple customization levels, from being able to customize each properties Input control, its Form Grid Layout and further annotating each API or Type with declarative attributes & rich markup.

API Annotations

Whilst the capability of adding rich API Docs is essential when needed, we expect plain C# attributes will often be used to document APIs where [Description] can be used to provide a short summary on a Type and its Properties whilst richer HTML markup can be added to any Type using [Notes] as done in Bookings.cs:

[Tag("bookings"), Description("Find Bookings")]
[Notes("Find out how to quickly create a <a class='svg-external' target='_blank' href='https://youtu.be/rSFiikDjGos'>C# Bookings App from Scratch</a>")]
[Route("/bookings", "GET")]
[Route("/bookings/{Id}", "GET")]
[AutoApply(Behavior.AuditQuery)]
public class QueryBookings : QueryDb<Booking> 
{
    public int? Id { get; set; }
}

[Description("Booking Details")]
[Notes("Captures a Persons Name & Room Booking information")]
public class Booking : AuditBase
{
    [AutoIncrement]
    public int Id { get; set; }
    //...
}

Where it generates clean API docs displayed in a human-friendly table layout containing properties of its Request DTO Type and inherited base class properties, starting with the APIs Request DTO followed by all its referenced dependent types - resulting in the details page containing a complete snapshot of all types used in the API:

API Docs

We can further enhance API Explorer with our own custom Vue.js Components by adding them to your Host projects local /modules/ui/docs folder which the Blazor WASM project template utilizes to showcase some customization examples:

Where you can enhance any of your APIs or DTOs with rich API docs by adding Vue Components to /modules/ui/docs/*.mjs which gets included together with API Explorers own components in its single file download. API Explorer is built using Vue.js which is a popular JavaScript framework that's optimal for progressive enhancement that your components can also take advantage of to enhance it with rich dynamic UIs.

For auto registration of components the *.mjs should match the API doc component which needs to be named {Type}Docs.

Here's a simple CreateBookingDocs.mjs component example used to generate API Docs for the CreateBooking API which just references server AppMetadata to access server API info from the /metadata/app.json endpoint:

import { inject } from "vue"

export const CreateBookingDocs = {
    template:`
    <div class="text-center my-3">
        <div class="flex justify-center">
            <svg class="w-10 h-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
                <path fill="#CFD8DC" d="M5 38V14h38v24c0 2.2-1.8 4-4 4H9c-2.2 0-4-1.8-4-4z"/><path fill="#F44336" d="M43 10v6H5v-6c0-2.2 1.8-4 4-4h30c2.2 0 4 1.8 4 4z"/>
                <g fill="#B71C1C"><circle cx="33" cy="10" r="3"/><circle cx="15" cy="10" r="3"/></g>
                <g fill="#B0BEC5"><path d="M33 3c-1.1 0-2 .9-2 2v5c0 1.1.9 2 2 2s2-.9 2-2V5c0-1.1-.9-2-2-2z"/><path d="M15 3c-1.1 0-2 .9-2 2v5c0 1.1.9 2 2 2s2-.9 2-2V5c0-1.1-.9-2-2-2z"/></g>
                <g fill="#90A4AE"><path d="M13 20h4v4h-4z"/><path d="M19 20h4v4h-4z"/><path d="M25 20h4v4h-4z"/><path d="M31 20h4v4h-4z"/><path d="M13 26h4v4h-4z"/><path d="M19 26h4v4h-4z"/><path d="M25 26h4v4h-4z"/><path d="M31 26h4v4h-4z"/><path d="M13 32h4v4h-4z"/><path d="M19 32h4v4h-4z"/><path d="M25 32h4v4h-4z"/><path d="M31 32h4v4h-4z"/></g>
            </svg>
            <h2 class="text-3xl ml-3 mb-3">Create Bookings API</h2>
        </div>
        <div class="text-gray-500 text-lg">
            <p>
                Create a new room Booking for our {‎{serviceName}‎} hotels.
            </p>
            <p>
                Here are some
                <a class="svg-external text-blue-800" target="_blank"
                    href="https://edition.cnn.com/travel/article/scoring-best-hotel-rooms/index.html">
                    good tips on making room reservations
                </a>
            </p>
        </div>
    </div>
    `,
    setup() {
        const server = inject('server')
        return { serviceName: server.app.serviceName }
    }
}

Dynamic Components

QueryTodos is a more advanced example that generates a dynamic UI shared by all TODO APIs that generate its reactive Mini Navigation UI users can use to cycle through all TODO API docs with a v-href="{ op }" custom directive:

Where instead of registering a single component, it exports a install(app) function to register API Doc components for all TODO CRUD APIs, all registered with the same TodosDocs component:

import { inject, computed } from "vue"
import { humanize } from "@servicestack/client"

export function install(app) {
    const apis = {
        QueryTodos:  'Query Todos, returns all Todos by default',
        CreateTodo:  'Create a Todo',
        UpdateTodo:  'Update a Todo',
        DeleteTodo:  'Delete Todo by Id',
        DeleteTodos: 'Delete multiple Todos by Ids',
    }
    const apiNames = Object.keys(apis)
    const TodosDocs = {
        template:`
        <div class="mx-auto max-w-screen-md text-center py-8">
            <h2 class="text-center text-3xl">{‎{humanize(op.request.name)}‎}</h2>
            <p class="text-gray-500 text-lg my-3">{‎{apis[op.request.name]}‎}</p>
            <div class="flex justify-center text-left">
                <table>
                    <caption class="mt-3 text-lg font-normal">Other Todo APIs</caption>
                    <tr v-for="(info,name) in otherApis">
                        <th class="text-right font-medium pr-3">
                            <a v-href="{ op:name }" class="text-blue-800">{‎{humanize(name)}‎}</a>
                        </th>
                        <td class="text-gray-500">{‎{info}‎}</td>
                    </tr>
                </table>
            </div>
        </div>`,
        setup() {
            const store = inject('store')
            const op = computed(() => store.op)
            const otherApis = computed(() => apiNames.filter(x => x !== store.op.request.name)
                 .reduce((acc,x) => { acc[x] = apis[x]; return acc }, {}))
            return { 
                op,
                apis,
                otherApis,
                humanize,
            }
        }
    }
    const components = apiNames.reduce((acc, x) => { acc[x + 'Docs'] = TodosDocs; return acc }, {})
    app.components(components)
}

Built-in App functionality

JavaScript Libraries

Your custom components can utilize built in libraries embedded in ServiceStack.dll where they will have access to the latest Vue 3 reactive fx, @servicestack/client client library and Vue 3 Tailwind Component library which they can import by package name, e.g:

import { ref } from "vue"
import { useClient } from "@servicestack/vue"
import { humanify } from "@servicestack/client"

Static Analysis

As all package dependencies are written in TypeScript you can install them as dev dependencies to get static analysis from its TypeScript definitions at dev time:

npm install -D vue
npm install -D @servicestack/client
npm install -D @servicestack/vue

Your components can access your Apps Typed DTOs directly from the ES6 Module DTO endpoint at /types/mjs, e.g:

import { QueryCoupons } from "/types/mjs"

App functionality

Your components access to most App functionality via the injected dependencies for functionality defined in API Explorer's app.mjs:

const app = inject('app')                  // App for customizing Vue App, register components, providers, plugins, etc
const client = inject('client')            // JsonServiceClient for API Calls
const server = inject('server')            // AppMetadata (metadata for your Server App and APIs)
const store = inject('store')              // API Explorer's Reactive object model
const routes = inject('routes')            // usePageRoutes() Reactive store to manage its SPA routing
const breakpoints = inject('breakpoints')  // useBreakpoints() Reactive store to Tailwind responsive breakpoints

Most of which creates instance of common library features in core.mjs that are documented at api.locode.dev/modules/explorer.html.

You're also not limited with what's in API Explorer, with full access to JavaScript Modules you can import external 3rd Party packages the same way you import built-in packages.

Built-in API Docs

ServiceStack's own built-in APIs uses custom API Doc components itself to document its APIs, e.g. /ui/docs/RegisterDocs.mjs

export const RegisterDocs = {
    template:`
      <div class="max-w-screen-md mx-auto text-center">
          <h2 class="text-2xl font-medium mb-3">Register API</h2>
          <p class="text-gray-500">
            Public API users can use to create a new User Account, can be added to your AppHost with:
          </p>
          <pre class="my-3"><code v-highlightjs="'Plugins.Add(new RegistrationFeature());'"></code></pre>
      </div>`
}

Generates docs for the built-in Register API that includes C# Syntax highlighting using the pre-configured highlightjs directive:

Whilst /ui/docs/AuthenticateDocs.mjs demonstrates a more advanced example in generating a responsive dynamic tab layout containing multiple relevant ServiceStack Auth YouTube videos:

Live Reload

When your App is run with dotnet watch it takes advantage ASP .NET Core's built-in file watcher to enable an instant live reload developer UX when contributing API Docs.

Which results in being more productive then using C# attributes as changes are immediately visible without a restart.

Customizing API Explorer

You can override each built-in Component in API Explorer by maintaining local customized versions in /wwwroot/modules/ui where each API can be documented by adding Custom API Docs to /docs/*.mjs, whilst existing components can be overridden in /components/*.mjs and custom UI added to custom.*

wwwroot/modules/ui
docs
*.mjs
components
*.mjs
custom.js
custom.css
custom.html

The custom.html and custom.js allows for further customization by including custom scripts and HTML fragments at the bottom of API Explorer which will let you modify API Explorer after it's loaded.

Override built-in Components

The built-in UIs also lets you override existing components by adding custom versions in /js/components, e.g:

const Brand = {
    template:`
    <div class="flex items-center flex-shrink-0 max-w-sidebar">
        <a title="My App" v-href="{ $page:'' }"
           class="text-2xl whitespace-nowrap overflow-x-hidden flex items-center">
           <svg xmlns="http://www.w3.org/2000/svg" class="w-8 h-8 ml-1 mr-2" viewBox="0 0 24 24">
               <path d="M23.834 8.101a13.912 13.912 0 0 1-13.643 11.72a10.105 10.105 0 0 1-1.994-.12a6.111 6.111 0 0 1-5.082-5.761a5.934 5.934 0 0 1 11.867-.084c.025.983-.401 1.846-1.277 1.871c-.936 0-1.374-.668-1.374-1.567v-2.5a1.531 1.531 0 0 0-1.52-1.533H8.715a3.648 3.648 0 1 0 2.695 6.08l.073-.11l.074.121a2.58 2.58 0 0 0 2.2 1.048a2.909 2.909 0 0 0 2.695-3.04a7.912 7.912 0 0 0-.217-1.933a7.404 7.404 0 0 0-14.64 1.603a7.497 7.497 0 0 0 7.308 7.405s.549.05 1.167.035a15.803 15.803 0 0 0 8.475-2.528c.036-.025.072.025.048.061a12.44 12.44 0 0 1-9.69 3.963a8.744 8.744 0 0 1-8.9-8.972a9.049 9.049 0 0 1 3.635-7.247a8.863 8.863 0 0 1 5.229-1.726h2.813a7.915 7.915 0 0 0 5.839-2.578a.11.11 0 0 1 .059-.034a.112.112 0 0 1 .12.053a.113.113 0 0 1 .015.067a7.934 7.934 0 0 1-1.227 3.549a.107.107 0 0 0-.014.06a.11.11 0 0 0 .073.095a.109.109 0 0 0 .062.004a8.505 8.505 0 0 0 5.913-4.876a.155.155 0 0 1 .055-.053a.15.15 0 0 1 .147 0a.153.153 0 0 1 .054.053A10.779 10.779 0 0 1 23.834 8.1zM8.895 11.628a2.188 2.188 0 1 0 2.188 2.188v-2.042a.158.158 0 0 0-.15-.15z" fill="#5C2D91"/>
            </svg>
           My App
        </a>
    </div>
    `
}
export default Brand

Which override's the built-in Brand component to replace the Logo on the top-right of API Explorer, Locode and the Admin UI with the custom version below:

Although a less invasive option if you just want to use your own logo is to configure the UiFeature plugin to override the default BrandIcon as the other Jamstack templates do in their Configure.AppHost.cs:

ConfigurePlugin<UiFeature>(feature => {
    feature.Info.BrandIcon.Uri = "/assets/img/logo.svg";
    feature.Info.BrandIcon.Cls = "inline-block w-8 h-8 mr-2";
});

Custom Form Layouts

Generated forms default to a two column layout, but this can be controlled using FormLayout for a specific operation. The appHost.ConfigureOperation<T> method can be used to change the layout and order of the form used in API Explorer.

For example, a CreateCustomer operation by default has the following properties.

[Route("/customers", "POST")]
public class CreateCustomers
    : IReturn<IdResponse>, IPost, ICreateDb<Customers>
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Company { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Country { get; set; }
    public string PostalCode { get; set; }
    public string Phone { get; set; }
    public string Fax { get; set; }
    public string Email { get; set; }
    public long? SupportRepId { get; set; }
}

Default Form UI

And is presented in API Explorer using the following generated form by default.

Custom Form Layout

Customizing this layout using ConfigureOperation, we can control the placement and other attributed of each InputInfo. When overriding the FormLayout, it is in the structure of Rows, then columns in the nested list. So grouping controls like City, State and PostalCode in the same row allows us to control the presentation.

appHost.ConfigureOperation<CreateCustomers>(operation => operation.FormLayout = new() {
    Input.For<CreateCustomers>(x => x.FirstName,    c => c.FieldsPerRow(2)),
    Input.For<CreateCustomers>(x => x.LastName,     c => c.FieldsPerRow(2)),

    Input.For<CreateCustomers>(x => x.Email),
    Input.For<CreateCustomers>(x => x.Company),
    Input.For<CreateCustomers>(x => x.Address),

    Input.For<CreateCustomers>(x => x.City,         c => c.FieldsPerRow(3)),
    Input.For<CreateCustomers>(x => x.State,        c => c.FieldsPerRow(3)),
    Input.For<CreateCustomers>(x => x.PostalCode,   c => c.FieldsPerRow(3)),
    
    Input.For<CreateCustomers>(x => x.Country),
    
    Input.For<CreateCustomers>(x => x.Phone,        c => c.FieldsPerRow(2)),
    Input.For<CreateCustomers>(x => x.Fax,          c => c.FieldsPerRow(2)),

    Input.For<CreateCustomers>(x => x.SupportRepId),
});

Gives us the updated layout in API Explorer.

Custom Input Controls

Each input field can be customized with client side visual and behavioural changes by using InputInfo when customizing FormLayout.

Input.For<CreateCustomers>(x => x.Email, info => {
    info.Label = "Personal Email Address";
    info.Placeholder = "me@email.com";
    info.Type = "email";
}) 

Now our label and placeholder changes are visible and trying to submit a value without an @ we get a client side warning.

Values for InputInfo are merged with the [Input] attribute that can be used on Request DTO class properties. This allows you to keep the default layout while still controlling Input options directly on your Request DTO class.

public class CreateCustomers : IReturn<IdResponse>, IPost, ICreateDb<Customers>
{
    [Input(Placeholder = "me@email.com", Type = "email", Label = "Personal Email Address")]
    public string Email { get; set; }
}

Register Form Layout

The built-in RegistrationFeature also uses a custom Form layout to mask its password fields:

appHost.ConfigureOperation<Register>(op => op.FormLayout = new()
{
    Input.For<Register>(x => x.DisplayName,     x => x.Help = "Your first and last name"),
    Input.For<Register>(x => x.Email,           x => x.Type = Input.Types.Email),
    Input.For<Register>(x => x.Password,        x => x.Type = Input.Types.Password),
    Input.For<Register>(x => x.ConfirmPassword, x => x.Type = Input.Types.Password),
});

Which overrides the default Auto UI Form to use this custom layout: