Skip to content

Kittox FAQ

This page collects frequently asked questions about the Kittox framework. If you need an article covering a particular subject, or if you think you have something to contribute, let us know by posting in the issues section of this repository.

General

What is Kittox?

Kittox is a Delphi framework for building data-driven web applications. You define your data models, views, and layouts in declarative YAML files, and Kittox generates a complete web application with server-rendered HTML, HTMX for dynamic updates, and AlpineJS for client-side interactivity.

What Delphi versions are supported?

Kittox supports RAD Studio / Delphi 10.4 Sydney and later (11 Alexandria, 12 Athens, 13). The framework package projects are built per version, under Packages/D10_4, Packages/D11, Packages/D12, and Packages/D13. Your own application targets Win64 with a single .dproj per deployment mode (see New Project Wizard).

Which databases does Kittox support?

Kittox is database-agnostic through pluggable adapters: FireDAC (recommended, supports all major databases), DBExpress, and ADO/OLEDB. Configure the adapter in Config.yaml under Databases.

Can I use multiple databases in the same application?

Yes. Define multiple entries under Databases in Config.yaml and use DatabaseName on models or DatabaseRouter for dynamic routing. See Multiple Databases.

Application structure

What is the minimum set of files for a Kittox application?

A minimal application needs:

  • A Delphi project file (.dpr) calling TKStart.Start
  • UseKitto.pas referencing Kitto.Html.All and any project-specific units
  • Home/Metadata/Config.yaml with at least Server/Port and a Databases entry
  • At least one Model (.yaml in Home/Metadata/Models/)
  • At least one View (.yaml in Home/Metadata/Views/)
  • A Home View defining the main layout

What is the Home directory?

Home/ is the runtime root for each application. The %HOME_PATH% macro resolves to it. It contains Metadata/ (YAML files), Resources/ (static assets), and optionally ReportTemplates/.

How do I run my application?

Compile and run the Delphi project. It starts a built-in HTTP server on the port defined in Config.yaml (Server/Port, default 8080). Open a browser at http://localhost:<port>.

Models and Views

What is the difference between a Model and a View?

A Model describes the data structure (fields, types, relationships, validation rules) and maps to a database table. A View describes the UI: which model to display, which fields to show, how to arrange them, and which controller to use.

How do I create a reference (foreign key) field?

In your model YAML, add a field with type Reference pointing to the referenced model:

yaml
Fields:
  Customer: Reference(Customer)

This creates a lookup combo in the UI. See Referenced Fields.

How do I add a detail table (master-detail)?

Add a DetailTables section to your view's MainTable:

yaml
MainTable:
  Model: Order
  DetailTables:
    Table:
      Model: OrderItem

The detail table appears as a tab in the form editor.

How do I make a field read-only, hidden, or required?

Set the corresponding property on the model field or view field:

yaml
Fields:
  CreatedDate:
    IsReadOnly: True
  InternalCode:
    IsVisible: False
  CustomerName:
    IsRequired: True

View-level settings override model-level settings.

Business rules

What is a business rule in Kittox?

A rule is a piece of business logic, written in Delphi, that Kittox applies during data entry. Rules can validate data (and block the operation with an error message), compute or set field values, react to a field change, or enforce cross-field/cross-record integrity before writing to the database. They are the place where the application's business logic lives, declaratively attached to the data model. See Business Rules for the full reference.

How do I attach a rule to a model or a field?

Add a Rules: subnode and list the rule by name. You can attach rules at four levels: model, model field, view table, and view-table field. Field-level rules can be stacked and fire in declaration order:

yaml
ModelName: Iscrizione
Rules:
  IscrizioneCheck:            # model-level: always applied
Fields:
  CodiceFiscale: String(16) not null
    Rules:
      ForceUpperCase:         # predefined (client-side)
      CheckFormatoCodiceFiscale:   # custom (server-side)

A model-level rule is always applied; a view-level rule is applied only when editing through that view, in addition to the model rules. A view-table-field rule overrides the model-field rule of the same type.

How do I write my own rule in Delphi?

Create a class descending from TKRuleImpl (unit Kitto.Rules), override the lifecycle method(s) you need, and register it:

pascal
type
  TCheckDuplicateInvitations = class(TKRuleImpl)
  public
    procedure BeforeAdd(const ARecord: TKRecord); override;
  end;

procedure TCheckDuplicateInvitations.BeforeAdd(const ARecord: TKRecord);
begin
  if ARecord.Store.Count('INVITEE_ID',
       ARecord.FieldByName('INVITEE_ID').Value) > 1 then
    RaiseError(_('Cannot invite the same girl twice.'));
end;

initialization
  TKRuleImplRegistry.Instance.RegisterClass(
    TCheckDuplicateInvitations.GetClassId, TCheckDuplicateInvitations);
finalization
  TKRuleImplRegistry.Instance.UnregisterClass(
    TCheckDuplicateInvitations.GetClassId);

The YAML name is the class name without its leading T (or TK): TCheckDuplicateInvitationsCheckDuplicateInvitations:. The unit must be reachable from UseKitto.pas so its initialization runs, and the app must be recompiled for the rule to take effect.

When exactly does a rule fire?

TKRuleImpl exposes hooks for each moment of the editing lifecycle: NewRecord, EditRecord, DuplicateRecord, BeforeFieldChange / AfterFieldChange, BeforeAdd / BeforeUpdate / BeforeAddOrUpdate, BeforeDelete, and the matching After* methods. The Before*/After* write hooks run inside the database transaction. Override only what you need. See the lifecycle table.

How do I stop an operation and show an error to the user?

Call RaiseError('message') inside any rule method. It raises an EKValidationError, aborts the operation, and rolls back the transaction if already started. Build messages with Field.AsString so values are locale-formatted.

Are there rules I can use without writing Delphi?

Yes. Predefined client-side rules: ForceUpperCase, ForceLowerCase, ForceCamelCaps, MinValue, MaxValue, MaxLength. Predefined server-side rule: EnforceRange (parameters From/To). See Predefined rules.

Can a rule read parameters from YAML?

Yes. Use Rule.Value for a single inline value (e.g. CalcDescrizione: {Cognome} {Nome}), or Rule.GetString/GetInteger/GetBoolean('Param', default) for named sub-parameters (e.g. GenerateNewId: with a CharSize: child). See Rule parameters.

Controllers

What controllers are available?

ControllerDescription
ListData grid with paging, sorting, filtering
FormRecord edit/add/view form
BorderPanel5-region layout (North, West, Center, East, South)
TabPanelTabbed container for sub-views
TilePanelTile/card layout for menu items
TreePanelTree navigation menu
HtmlPanelStatic or dynamic HTML content
ChartPanelChart.js-based data visualization
TemplateDataPanelCustom HTML template with data binding
GroupingListList with row grouping
CalendarPanelCalendar view
StatusBarStatus bar with text
ToolBarNavigation toolbar

See Panel-Based Controllers for the full reference.

How do I add export/download buttons to a list?

Add a ToolViews section under the view table's Controller:

yaml
Controller: List
  ToolViews:
    DownloadCSV:
      DisplayLabel: Download CSV
      Controller: ExportCSVTool
        RequireSelection: False
    DownloadExcel:
      DisplayLabel: Download Excel
      Controller: ExportExcelTool
        ClientFileName: Report.xls
        TemplateFileName: %APP_PATH%ReportTemplates\Template.xlt

See Tool Controllers for all available tools.

How do I add filters to a list?

Add a Filters section under the controller. See How to Filter Data for examples.

yaml
Controller: List
  Filters:
    DisplayLabel: Search
    Items:
      FreeSearch: Name
        ExpressionTemplate: UPPER({Q}) LIKE UPPER('%{value}%')
      DynaList: Category
        ExpressionTemplate: T.CATEGORY_ID = {value}
        CommandText: SELECT ID, NAME FROM CATEGORIES ORDER BY NAME

How do I open a form in add mode directly?

Use a standalone Form controller with Operation: Add:

yaml
Controller: Form
  Operation: Add

Supported operations: Add, Edit, View, Dup.

Routing and request handling

How does Kittox map a URL to code?

Every kx/... endpoint is handled by an attribute-decorated handler class discovered via RTTI. A class declares a base path with [TKXPath('/kx/view/{ViewName}')], and its methods declare a sub-path plus an HTTP verb — [TKXGET], [TKXPOST], [TKXPUT], [TKXDELETE], [TKXPATCH], [TKXANY]. URL and query segments bind to parameters with [TKXPathParam] / [TKXQueryParam]. A shared request-filter chain (error handling → JWT hydration → navigation guard → authorization) wraps every dispatch. See Attribute-Based Routing and Routing and Request Flow.

How do I add my own URL handler?

Declare a handler class and register it in the unit's initialization — the same "descend + register" idiom used for custom rules:

pascal
type
  [TKXPath('/kx/myfeature')]
  TMyHandler = class
  public
    [TKXPath('/ping')] [TKXGET]
    procedure Ping([TKXPathParam('ViewName')] const AViewName: string);
  end;

initialization
  TKXResourceRegistry.Instance.RegisterResource(TMyHandler);

The unit must be reachable from UseKitto.pas so its initialization runs, and the app recompiled.

How do I change one endpoint without forking the framework?

Subclass the framework handler (e.g. TKXViewHandlerBase or the REST TKXApiHandlerBase), override the method(s) or hook(s) you want, and register the subclass with TKXResourceRegistry.RegisterOverride(TMyHandler). It replaces the default handler for the same base path. See overriding an endpoint.

What do [TKXAnonymous] and [TKXNavigable] mean?

[TKXAnonymous] exempts an endpoint from the authentication gate (login, token, public pages). [TKXNavigable] marks an endpoint reachable by a top-level browser navigation (address bar, opened link); fragment endpoints without it are bounced to the app root by the navigation guard, so a pasted kx/... URL never leaks a bare HTML fragment.

Configuration

How do I configure authentication?

Set the Auth section in Config.yaml. Kittox supports database-based, text file, and OS-based authentication:

yaml
Auth: DB
  ReadUserCommandText: |
    SELECT USER_NAME, PASSWORD_HASH, EMAIL
    FROM USERS WHERE USER_NAME = '{UserName}'

See Authentication.

What is Auth: JWT and when should I use it?

Auth: JWT wraps another authenticator as Inner and issues a self-contained signed JSON Web Token in an HttpOnly cookie that replaces the opaque session-id cookie. Use it when you want stateless authentication, no server-side session-id table lookup, and the user profile (UserName, DisplayName, environment, language, roles) embedded in the credential itself. It is also the architectural point of entry for upcoming external identity providers (OIDC, SAML).

yaml
Auth: JWT
  Inner: DB
    ReadUserCommandText: |
      SELECT USER_NAME, PASSWORD_HASH FROM USERS WHERE USER_NAME = :USER_NAME
  SigningAlgorithm: HS256
  TokenLifetime: 3600
  SlidingThreshold: 600

Add Kitto.Auth.JWT to the uses clause of UseKitto.pas and register the signing key once in the unit's initialization block (TKJWTSigningKeyRegistry.Instance.RegisterProvider). Apps that stay on Auth: DB | TextFile | custom are unaffected and do not pull in the JOSE third-party library. See JWT Authenticator for the full reference.

No. The server-side TKWebSession is still required for non-serializable state (open controllers, in-memory master-detail stores, gnugettext instance). The JWT replaces only the credential / session-id correlator: the engine reads the sid claim from the token to bind the request to its session, instead of reading an opaque session-id cookie. The session itself, with its in-memory state, stays.

Because the token is signed and carries the full profile (user, ACL, database, language), it is self-sufficient: if the referenced session no longer exists — after a server restart, or on a different node — the engine re-hydrates a session from the token's claims instead of raising "session lost". A valid token therefore keeps working across restarts, and a REST client sending the token in Authorization: Bearer reuses one session per token (not one per request). The sid is read from the Bearer header as well as the cookie.

Where do I store the JWT signing key?

Three options, in order of preference for production:

  1. Programmatic provider registered in UseKitto.pasTKJWTSigningKeyRegistry.Instance.RegisterProvider(AppName, AnonFn) in the unit's initialization block. The anonymous function returns a TKJWTSigningKey and can load the bytes from a vault, secret manager, environment variable, or platform store. This is the recommended approach because the same key is shared by all .dpr flavors of the app (Standalone, ISAPI, Desktop, Apache) without needing per-process configuration.
  2. YAML Auth/SigningKey: env:VAR_NAME — read from an environment variable at first JWT operation. Used as fallback when no provider is registered.
  3. YAML Auth/SigningKey: file:/path/to/key — read raw bytes from a file (PEM for RS* / ES*).

Inline literal in YAML is supported but should only be used for development and demo apps.

How do I set up access control?

Use the AccessControl section in Config.yaml with SQL commands that read permissions and roles:

yaml
AccessControl: DB
  ReadPermissionsCommandText: |
    SELECT RESOURCE_URI, ACCESS_MODES
    FROM PERMISSIONS WHERE ROLE_ID IN ({RoleList})

See Access Control.

How do I enable logging?

Add a Log section to Config.yaml:

yaml
Log:
  Level: 5
  TextFile:
    IsEnabled: True
    FileName: %APP_PATH%log.txt

REST API

Does Kittox expose a REST/JSON API?

Yes. A Kittox app can serve its data views as a REST/JSON web service under /api/v4/{ViewName} with real HTTP verbs, sharing the same models, business rules and ACL as the HTML GUI. It is opt-in: add Kitto.Web.Rest to UseKitto.pas (the same pattern as Auth: JWT or Kitto.Web.Enterprise); browser-only apps link no REST code. See REST Server and REST API (JSON).

What endpoints are available?

GET /api/v4/{View} (list — envelope {data, total} with paging/sort/filters), GET /api/v4/{View}/{id} (read one), POST /api/v4/{View} (create), PUT / PATCH /api/v4/{View}/{id} (full / partial update), DELETE /api/v4/{View}/{id} (delete). Errors come back as JSON {error, code} with a real HTTP status.

How do I control what the REST API is allowed to do?

On the Model: IsReadOnly: True blocks every write (→ 405), and PreventAdding / PreventEditing / PreventDeleting block a single operation. The view Controller's PreventAdding/… flags are GUI-only and do not affect REST — REST authorization lives on the Model and in the rules (plus the usual ACL → 403, business-rule violations → 422).

How does a REST client authenticate?

POST /api/v4/token with a JSON body {"username","password","database"?} returns a Bearer JWT; send it back on each call as Authorization: Bearer <token> (no cookie needed). Requires Auth: JWT. The browser SPA keeps using its cookie; both schemes coexist. See REST API — Authentication.

KIDE (visual editor)

What is KIDE?

KIDE is the visual IDE for designing Kittox applications. It provides a tree editor for YAML metadata with context-aware popup menus, database reverse engineering, and syntax verification. See KIDE Introduction.

How does KIDE know which properties a node supports?

KIDE uses RTTI-based discovery: each framework class is annotated with custom Delphi attributes (YamlNode, YamlSubNode, YamlContainer, YamlChildType) that describe its YAML properties. When you right-click a node, KIDE reads these annotations to build the popup menu dynamically. See KIDE YAML Attributes.

How do I add KIDE support for a custom controller?

  1. Add {$RTTI EXPLICIT PROPERTIES([vcPublic])} before your class
  2. Add EF.YAML.Attributes to the uses clause
  3. Annotate each config property with [YamlNode('Path', 'Default', 'Description')]

KIDE will automatically discover the properties via RTTI. See KIDE YAML Attributes — How to annotate.

Deployment

How do I deploy a Kittox application?

Compile the application as a VCL executable or Windows service. Deploy the executable, the Home/ directory with all metadata and resources, and any required database drivers. The application includes a built-in HTTP server — no external web server is needed. See Deployment.

Can I run behind a reverse proxy?

Yes. Configure your reverse proxy (nginx, Apache, IIS) to forward requests to the Kittox built-in server port. Set Server/Proxy in Config.yaml if the external URL differs from the internal one. See Proxy.

Troubleshooting

My application starts but the browser shows a blank page

Check that:

  • The Home/ directory is in the correct location (same directory as the executable, or configured via command line)
  • Config.yaml exists and has a valid Server/Port
  • The Home View is defined and the Controller value matches a registered controller

Fields are not showing in the form

Ensure the fields are defined in the View's MainTable/Fields section. If you omit the Fields section, all model fields are included by default. If you define Fields, only listed fields are shown.

The filter panel is not visible

Make sure the Filters section is under the Controller node (not at the view level) and that Items contains at least one filter definition with a valid ExpressionTemplate.

Released under Apache License, Version 2.0.