In Cotomy, a single endpoint, especially in CRUD-heavy business systems, is treated as one operational screen boundary. This continues from Working with CotomyElement . For those screens, Cotomy expects the endpoint-level behavior to be coordinated by CotomyPageController.
Each class involved in CRUD operations (forms, API forms, entity-aware forms) requires explicit initialization. In Cotomy’s design, that initialization kick and lifecycle coordination are also responsibilities of the page controller.
By centralizing screen entry, initialization, and CRUD orchestration in the page controller, Cotomy standardizes how these screens behave. That consistency keeps large systems maintainable as they grow.
This guide shows how that screen-level orchestration model works with Cotomy forms in a practical CRUD example under the principle:
one screen = one endpoint boundary.
For reference details, see: CotomyPageController , CotomyApiForm , and CotomyEntityFillApiForm .
Before PageController: Choosing the Right Form Type
Before discussing why the page controller comes first, it helps to see how Cotomy form classes are typically used at the screen level.
Case A: Search Conditions (Query Screen)
Use CotomyQueryForm when the goal is URL navigation via query parameters.
import { CotomyElement, CotomyQueryForm } from "cotomy";
const form = CotomyElement.byId<CotomyQueryForm>(
"user-search-form", class extends CotomyQueryForm {})!.initialize();
This form always uses GET, serializes input values into the query string, and navigates through location.href. It does not use an entity lifecycle.
This is appropriate for list filters and search condition panels.
Case B: Simple API Submit (Non-Entity Form)
Use CotomyApiForm when submitting data to an API without entity identity switching.
import { CotomyElement, CotomyApiForm } from "cotomy";
const form = CotomyElement.byId<CotomyApiForm>(
"feedback-form", class extends CotomyApiForm {})!.initialize();
This form submits FormData through fetch, exposes apiFailed and submitFailed events, and does not auto-switch POST and PUT. It is not bound to entity identity or CRUD lifecycle.
This form type is useful for smaller operational units inside a screen:
for example, a search panel that should not trigger full GET navigation, a modal or side panel that selects options and posts results, or a feedback form that is not part of the main CRUD contract. The same applies to any POST operation that is operational but not part of the primary endpoint CRUD contract.
In other words, use CotomyApiForm when the submission is operational, but not identity-bound and not the primary endpoint contract of the screen.
Case C: Entity CRUD Form (Create + Edit in One Screen)
Use CotomyEntityFillApiForm for endpoint-bound CRUD screens.
import { CotomyElement, CotomyEntityFillApiForm } from "cotomy";
const form = CotomyElement.byId<CotomyEntityFillApiForm>(
"user-edit-form", class extends CotomyEntityFillApiForm {})!.initialize();
It automatically switches POST to PUT based on entityKey, performs a GET on load when entityKey exists, and calls fillAsync() to project data into inputs. That keeps create and edit under one endpoint contract.
This is the typical choice for business CRUD screens.
All of these form types share one structural requirement: they must be initialized.
Whether you bind to an existing