A developer app stores and runs your code in campai. You create an app, add one or more groups, edit their projects, simulate saved code, and publish a version when it is ready. You do not deploy a separate backend for the example in this guide.
If you want to run code on your own server instead, start with Make your first API request. An external REST integration does not need a developer app, and an app's editor is not a place to paste an external integration's API key.
What you’ll have at the end
An unpublished app that runs a review-report job and saves its first summary in the app database.
- Create app
- Add group
- Save code
- Simulate
Before you start
- Access to the developer app editor
- A test organization
- No API key or external hosting needed for this example
- App Database access enabled
On this page
Use an organization owner account. Open the intended organization in campai, then:
- Open More → Developers and select Apps.
- Choose Create app.
- Enter a clear Name, such as
Developer guide verification, and a short Description. - Choose Create app to open the app's Overview.
The name can contain up to 48 characters; the description can contain up to 240. An app starts without a published version. The Overview separates the draft's existence from its publication history and runtime usage.
The app navigation contains Overview, Logs, Settings, and Edit. Use these pages inside Developers; the separate organization-level Apps page is not the developer project editor.
Choose Edit, then Add group. Select Schedule and name the group Schedule verification. Leave the schedule configuration at its default for this manual test and choose Create group.
The project tree now contains the group and its index.ts server entry file. Enable Settings → Database access, return to Edit, and replace the generated file with this complete review-report job:
Example
export async function main() {
const report = { checkedAt: new Date().toISOString(), contacts: 0, missingEmail: 0, missingAddress: 0 }
const entries = storage.list<{ issues: string[] }>({ prefix: ['contact-review'] }, { limit: 501, batchSize: 100 })
for await (const entry of entries) {
report.contacts += 1
if (report.contacts > 500) throw new Error('More than 500 review items: add paginated reporting before replacing the report')
if (entry.value.issues.includes('Add an email address')) report.missingEmail += 1
if (entry.value.issues.includes('Add a postal address')) report.missingAddress += 1
}
await storage.set(['contact-review-report'], report)
console.log(report)
}This function counts your app’s contact-review items and saves a report. A new app has an empty queue, so the first run saves zero totals. The record-change guide adds useful checks to that queue. The job needs only app Database access; it does not read or change organization records. Creating this group and running it manually does not establish that automatic scheduling is active; see Run an app on a schedule before relying on a recurring job.
If you edit the file, choose Save changes before testing. Saving updates the draft. It does not publish the app or update a previously published version.
- Select the group's index.ts so the intended group is active.
- Choose the group's Simulate action.
- Wait for Validate app and Build app to finish.
- In Run simulation, choose Start simulation. A Schedule group does not require input.
- Confirm
contacts: 0,missingEmail: 0, andmissingAddress: 0in a new app, alongside a success result. Run it again to replace the same report.
The validation/build stages prepare the program. Start simulation executes it. A successful build alone does not prove the function has run. The result includes runtime usage and links console entries back to source locations.
If validation fails, open the reported file and correct its types or imports. If the build succeeds but execution fails, inspect the runtime error and permissions. A database denial means Database access needs to be enabled; organization record permissions are unrelated.
Use a Form when campai should render fields and pass submitted values to your server function. Use a Widget when you need a custom React interface and server actions. Use Record changes for the supported record-change triggers. A Schedule group contains a scheduled task's configuration and no-input server entry point.
Each group has its own server project. A widget additionally has a client project. Start with one group that proves your workflow before adding other triggers to the same app; publishing operates on the app and its groups together.
Continue with Use the built-in app objects for complete api, storage, and widget examples, then Choose the right app group and Understand client and server projects.
Leave this example unpublished while learning the editor. The simulation executes code in the app runtime; it is not a promise that every external effect is rolled back. Introduce organization writes, secrets, and outbound calls only after you understand server access and simulation behavior.
When the intended behavior is verified, follow Publish and share an app. A shareable form or widget also needs a deliberate access scope; successful owner simulation does not prove that a public visitor or contact can open it.
Check your result
- Saved draft
- Appears in the project tree.
- Simulation
- A new app saves and logs zero review counts.
- Still unpublished
- No public release was required to test the function.