A group defines what starts your code and which inputs it receives. An app can contain several groups, each with a separate server project. The group type is therefore an execution contract, not just a folder label.
Use this guide after creating a developer app. Create and configure groups through Edit → Add group. These editor operations are part of the signed-in admin application; they are not public REST endpoints for external API integrations.
What you’ll have at the end
A group choice that matches the trigger, interface, and access your app needs.
- What starts the work?
- Choose group
- Choose client access
- Grant server permissions
Before you start
- A defined task and intended audience
- An unpublished developer app
On this page
| Group | Trigger or interface | Server entry | Client project |
|---|---|---|---|
| Form | A submitted form rendered by campai | main(result) |
No custom client project; configure the form fields |
| Widget | An action called by your custom interface | Named exported functions, such as getReviewSummary() |
React index.tsx plus index.css |
| Record changes | A configured supported record event | main(action, record) |
None |
| Schedule | Scheduled task configuration or a manual simulation | main() |
None |
The editor creates a suitable index.ts for the chosen group. Do not paste a widget's named-action entry point into a Schedule group and expect it to be called: that group expects main.
A Form group is useful when the interaction is “fill in these fields, then submit.” You configure the form definition, and campai renders it. The server receives the field values directly in its argument, keyed by each field's configured Name or generated ID; there is no extra data wrapper in the server entry point.
Return a message result for a confirmation screen, or the supported HTML result when you need that output format. The form's return contract is different from a widget's arbitrary action result. Use Build a form that runs app code for a complete example.
A developer Form does not automatically create a membership application, contact, or invoice. Its server function must perform any intended business operation with the required permissions. campai's membership application forms are a separate product workflow; see Build a custom membership form when that is your goal.
A Widget group has two projects: server index.ts and client Widget/index.tsx, with index.css for styling. The client can call a named server export through the supplied action bridge.
Use it for an interface that needs local state, custom controls, or several actions rather than one form submission. Keep secrets and privileged operations on the server. Every value supplied by the browser remains untrusted even when the interface validates it.
The review dashboard calls getReviewSummary on the server and displays contact-data checks stored by another group in the same app. Follow Create a custom widget.
The current implementation supports created, updated, and deleted events for contacts, postings, debtors, and creditors. Select the exact event types in the group's settings. There is no general “all database changes” trigger and no booking or event-calendar trigger in this list.
Use the full event string, such as contacts.updated, when branching in code. The second argument is the record supplied by that event. It is not a list of changed fields. Design processing to tolerate repeated events and avoid a loop where your write immediately triggers the same app again.
See React to record changes for input inspection and safe testing.
A Schedule group has a cron configuration and a main() function without arguments. The editor offers schedule fields and the simulator can run the saved function manually.
Before relying on automatic execution, verify a published run in your environment and confirm its time-zone behavior. The reviewed source establishes group configuration and manual execution; these alone do not establish a working recurring dispatcher. Run an app on a schedule separates those checks.
Forms and widgets have a Scope controlling who can open or use them. Server access controls what the app's code can do. Granting a visitor access to a form does not mean the visitor should receive the app's API credential, and restricting the form does not validate every submitted value.
Keep groups together when they share a purpose and an appropriate permission boundary. Separate unrelated workflows when sharing app-level server permissions would give one group unnecessary capabilities. Before publishing, review every group's access and trigger configuration, not only the group you most recently edited.
Check your result
- Custom interface
- Use a Widget client with server actions.
- Defined submission
- Use a Form and its server main function.
- Unsupported event
- Do not invent an event type; use reconciliation.