- Updated `tibi-project-setup` skill to clarify project initialization goals and steps. - Improved `tibi-ssr-caching` skill to detail SSR architecture, responsibilities, and caching mechanisms. - Introduced `website-solution-architecture` skill for translating website requirements into coherent solutions. - Refined `AGENTS.md` to provide a structured roadmap for project development phases. - Added `ADMIN_ASSET_VERSION` to `api/config.yml.env` for asset versioning. - Updated SSR request flow and cache invalidation logic in `api/hooks/ssr/AGENTS.md`. - Removed obsolete `esbuild.config.admin.js` and integrated asset versioning into the main `esbuild.config.js`. - Adjusted `api/collections/content.yml` to utilize asset versioning for admin scripts.
7.6 KiB
name, description
| name | description |
|---|---|
| tibi-actions-and-forms | Build endpoint-style website features with tibi-server actions. Covers when to use actions instead of collections, action hook flow, validation, permissions, CORS, mail/webhook patterns, and frontend integration for forms. |
tibi-actions-and-forms
When to use this skill
Use this skill when:
- Building contact forms, newsletter forms, quote requests, callbacks, or booking requests
- Adding endpoint-like backend logic without CRUD storage
- Replacing old collection hacks that only existed to accept POST requests
- Designing frontend form submissions against tibi-server actions
- Deciding whether a feature should be an action, a collection, or both
Goal
The goal is to teach an LLM how to build website workflows that behave like real endpoints.
A form feature is complete only when all of these are coherent:
- action config
- hook flow
- validation
- permissions and CORS
- optional persistence or side effects
- frontend submission and error handling
Source of truth
Use these sources when implementing or reviewing action-based features:
tibi-server/docs/19-actions.mdtibi-server/docs/06-hooks.mdapi/config.ymlapi/hooks/frontend/src/lib/api.tsfrontend/src/App.svelteor the relevant frontend form surface
Core decision: action or collection?
Use an action when the feature is primarily an endpoint or workflow.
Typical action cases:
- contact form
- newsletter signup
- quote request
- callback request
- webhook receiver
- utility endpoint
- AI-assisted helper endpoint
Use a collection when the feature is primarily stored content or stored records with CRUD semantics.
Typical collection cases:
- products
- team members
- events
- testimonials
- persisted inquiries that editors must browse/edit in admin
Use action + collection when you need a public workflow plus internal persistence.
Example:
- a contact form submits to an action
- the action validates, sends mail, and optionally creates an
inquiriesentry for staff follow-up
Do not fake endpoint logic with empty collections unless there is a very specific reason.
Routing model
Actions are exposed under:
POST /api/v1/_/:project/_actions/:action
GET /api/v1/_/:project/_actions/:action
The _actions prefix is part of the contract. Frontend form code should treat actions as explicit API endpoints, not as collection writes.
Where actions are configured
Actions are declared in api/config.yml under actions: and typically point to files under:
api/actions/for YAML configsapi/hooks/<action-name>/for hook files
Typical config shape:
actions:
- !include actions/contact-form.yml
name: contact-form
meta:
label: { de: "Kontaktformular", en: "Contact Form" }
permissions:
public:
methods:
post: true
hooks:
post:
bind:
type: javascript
file: hooks/contact-form/post_bind.js
validate:
type: javascript
file: hooks/contact-form/post_validate.js
handle:
type: javascript
file: hooks/contact-form/post_handle.js
return:
type: javascript
file: hooks/contact-form/post_return.js
Action lifecycle
POST flow
bind → validate → handle → return
Use the steps deliberately:
bind: normalize the request body, derive helper data, prepare contextvalidate: enforce required fields, anti-spam checks, shape checks, consent checkshandle: execute the business logicreturn: normalize the response payload for the frontend
GET flow
handle → return
GET actions are useful for utility endpoints, signed links, status endpoints, or controlled data retrieval that is not a collection read.
Recommended website patterns
Contact form
Recommended behavior:
- public
POSTaction - validate required fields, email format, consent, and anti-spam signal
- send mail or queue message handling
- optionally persist a normalized inquiry record
- return a small stable payload for the frontend
Newsletter signup
Recommended behavior:
- public
POSTaction - validate email and consent
- call external provider or create local opt-in entry
- keep provider-specific logic in the action, not in the frontend
Quote or booking request
Recommended behavior:
- public
POSTaction - transform raw form data into a normalized structure in
bind - validate business rules in
validate - persist or forward the request in
handle
Webhook receiver
Recommended behavior:
- restricted
POSTaction - verify secret/signature before any state change
- keep webhook-specific logic isolated from public website forms
Validation rules
Validation belongs server-side even when the frontend already validates.
Always validate:
- required fields
- string lengths
- email/phone formats when applicable
- consent flags
- expected enums or modes
- anti-spam or rate-limit conditions
Do not trust the frontend form shape.
Permissions and CORS
Actions use the same permission model as collections.
For public website forms:
- keep only the needed methods public
- avoid opening
GETunless the use case needs it - use action-level CORS only when the frontend origin truly differs from the project default
Public form access should be narrow, explicit, and auditable.
Persistence strategy
Not every form submission belongs in a collection.
Choose persistence deliberately:
- no persistence: mail, webhook, or third-party API only
- minimal persistence: store the normalized request for internal staff
- full persistence: store and manage lifecycle in a dedicated collection
If editors must browse, triage, export, or annotate the data in Nova, add a dedicated collection instead of overloading the action itself.
Frontend integration
Frontend forms should submit to actions through the normal API layer.
Keep the frontend responsible for:
- collecting input
- disabled/loading state
- optimistic or conservative UX
- success and error messages
Keep the backend responsible for:
- real validation
- side effects
- persistence
- normalization of response shape
Response design
Return a small stable payload that the frontend can rely on.
Typical response examples:
{ "ok": true, "message": "Danke für Ihre Nachricht." }
{ "ok": true, "nextStep": "confirm-email" }
Avoid leaking internal implementation details or raw provider responses to the frontend.
Anti-patterns
- Creating empty fake collections just to receive POST requests
- Moving validation only into the browser
- Sending third-party API credentials from the frontend
- Returning unstable error shapes
- Mixing public forms and internal admin workflows into one hook without boundaries
- Persisting everything by default without a real editorial or operational need
Verification checklist
After adding an action-based workflow, verify all of these:
- The action is declared in
api/config.yml. - The hook chain exists and has the intended steps.
- Invalid submissions fail with useful status and message.
- Valid submissions trigger the intended side effects.
- Public permissions are no broader than necessary.
- The frontend handles success and failure predictably.
yarn validatestays clean.
What an LLM should inspect first
When asked to add a website form or endpoint on this starter, inspect in this order:
tibi-server/docs/19-actions.mdapi/config.yml- related hooks in
api/hooks/ - the frontend form/API surface
- whether persistence belongs in a collection too
This prevents the common mistake of starting with a fake collection when the feature is really an action.