-
-
Notifications
You must be signed in to change notification settings - Fork 118
Custom Actions
Tested ✓ - All examples on this page were verified against a running Daptin instance.
Custom actions let you define business logic that executes when called via API. Actions can send notifications, make HTTP requests, create/update records, or chain multiple operations together.
Create a file named schema_myapp.yaml in your Daptin directory:
Actions:
- Name: greet_user
Label: Greet User
OnType: user_account
InstanceOptional: true
InFields:
- Name: name
ColumnName: name
ColumnType: label
IsNullable: false
OutFields:
- Type: client.notify
Method: ACTIONRESPONSE
Attributes:
type: success
title: Greeting
message: "~name"Restart Daptin to load the schema file.
curl -X POST http://localhost:6336/action/user_account/greet_user \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"attributes": {"name": "World"}}'Response:
[
{
"ResponseType": "client.notify",
"Attributes": {
"type": "success",
"title": "Greeting",
"message": "World"
}
}
]Place schema_*.yaml, schema_*.json, or schema_*.toml files in your Daptin directory. Actions are loaded on startup.
Set schema folder:
export DAPTIN_SCHEMA_FOLDER=/path/to/schemasCreate actions via the API. Requires server restart to take effect.
# Get the world_id for the target table
WORLD_ID=$(curl -s http://localhost:6336/api/world?page%5Bsize%5D=100 \
-H "Authorization: Bearer $TOKEN" | \
jq -r '.data[] | select(.attributes.table_name == "user_account") | .id')
# Create the action
curl -X POST http://localhost:6336/api/action \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/vnd.api+json" \
-d '{
"data": {
"type": "action",
"attributes": {
"action_name": "my_action",
"label": "My Action",
"instance_optional": 1,
"action_schema": "{...JSON schema...}"
},
"relationships": {
"world_id": {
"data": {"type": "world", "id": "'$WORLD_ID'"}
}
}
}
}'
# IMPORTANT: Set instance_optional separately (API quirk)
curl -X PATCH http://localhost:6336/api/action/$ACTION_ID \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/vnd.api+json" \
-d '{
"data": {
"type": "action",
"id": "'$ACTION_ID'",
"attributes": {"instance_optional": 1}
}
}'
# Restart server to load the actionImportant: After creating or modifying actions via API, you must restart Daptin for changes to take effect.
Every action has this JSON structure (stored in action_schema column):
{
"Name": "action_name",
"Label": "Human Readable Name",
"OnType": "table_name",
"InstanceOptional": true,
"RequestSubjectRelations": null,
"ReferenceId": "00000000-0000-0000-0000-000000000000",
"InFields": [...],
"OutFields": [...],
"Validations": null,
"Conformations": null
}| Property | Type | Description |
|---|---|---|
Name |
string | Unique identifier (used in API URL) |
Label |
string | Display name for UI |
OnType |
string | Table this action operates on |
InstanceOptional |
boolean |
true = no record needed, false = requires record ID |
InFields |
array | Input parameters (see below) |
OutFields |
array | Operations to execute (see below) |
Define what parameters the action accepts.
{
"Name": "field_name",
"ColumnName": "field_name",
"ColumnDescription": "Help text for this field",
"ColumnType": "label",
"IsPrimaryKey": false,
"IsAutoIncrement": false,
"IsIndexed": false,
"IsUnique": false,
"IsNullable": false,
"Permission": 0,
"IsForeignKey": false,
"ExcludeFromApi": false,
"ForeignKeyData": {"DataSource": "", "Namespace": "", "KeyName": ""},
"DataType": "",
"DefaultValue": "",
"Options": null
}| ColumnType | Description | Example Use |
|---|---|---|
label |
Short text | Names, titles |
content |
Long text | Descriptions, messages |
email |
Email address | User email |
password |
Password (masked) | Login credentials |
url |
URL | Webhook endpoints |
file.* |
File upload (base64) | Attachments |
truefalse |
Boolean | Flags, options |
datetime |
Date/time | Scheduled dates |
For quick testing, you can use minimal fields:
{
"Name": "message",
"ColumnName": "message",
"ColumnType": "label",
"IsNullable": false
}OutFields define what the action does. Each OutField executes an operation.
{
"Type": "performer_name",
"Method": "EXECUTE",
"Reference": "",
"LogToConsole": false,
"SkipInResponse": false,
"Condition": "",
"Attributes": {...},
"ContinueOnError": false
}| Method | Description | Type Value |
|---|---|---|
ACTIONRESPONSE |
Return data directly to client | Response type (client.notify, client.redirect, etc.) |
EXECUTE |
Run a registered performer | Performer name ($network.request, jwt.token, etc.) |
POST |
Create a new record | Table name |
PATCH |
Update existing record | Table name |
GET |
Retrieve records | Table name |
DELETE |
Delete a record | Table name |
GET_BY_ID |
Get specific record | Table name |
Return data directly to the client.
Tested ✓ - Show a notification message.
{
"Type": "client.notify",
"Method": "ACTIONRESPONSE",
"Attributes": {
"type": "success",
"title": "Done",
"message": "Operation completed"
}
}Notification types: success, error, warning, info
Tested ✓ - Navigate to a URL.
{
"Type": "client.redirect",
"Method": "ACTIONRESPONSE",
"Attributes": {
"location": "/dashboard",
"delay": 2000,
"window": "self"
}
}Return a file for download.
{
"Type": "client.file.download",
"Method": "ACTIONRESPONSE",
"Attributes": {
"content": "base64-encoded-content",
"name": "report.pdf",
"contentType": "application/pdf"
}
}Store a value (for frontend localStorage).
{
"Type": "client.store.set",
"Method": "ACTIONRESPONSE",
"Attributes": {
"key": "token",
"value": "jwt-token-here"
}
}Registered handlers that perform operations.
Tested ✓ - Make HTTP requests to external APIs.
OutFields:
- Type: $network.request
Method: EXECUTE
Attributes:
Url: "https://httpbin.org/post"
Method: "POST"
Headers:
Content-Type: "application/json"
Authorization: "Bearer ~api_token"
Body:
name: "~name"
email: "~email"Response:
{
"ResponseType": "$network.request",
"Attributes": {
"__type": "$network.response",
"body": {...},
"headers": {...}
}
}| Performer | Purpose |
|---|---|
$network.request |
HTTP requests to external APIs |
$transaction |
Database transaction wrapper |
jwt.token |
Generate JWT authentication token |
otp.generate |
Generate OTP for 2FA |
otp.login.verify |
Verify OTP code |
mail.send |
Send email via SMTP |
aws.mail.send |
Send email via AWS SES |
cloudstore.file.upload |
Upload to cloud storage |
cloudstore.folder.create |
Create folder in cloud storage |
cloudstore.path.move |
Move/rename in cloud storage |
cloudstore.site.create |
Create a subsite |
site.file.list |
List site files |
site.file.get |
Get site file content |
site.file.delete |
Delete site file |
site.storage.sync |
Sync site storage |
column.storage.sync |
Sync column storage |
password.reset.begin |
Start password reset flow |
password.reset.verify |
Complete password reset |
self.tls.generate |
Generate self-signed certificate |
acme.tls.generate |
Generate Let's Encrypt certificate |
integration.install |
Install OpenAPI integration |
template.render |
Render template |
response.create |
Create custom response |
random.generate |
Generate random value |
generate.random.data |
Generate random data |
command.execute |
Execute system command |
oauth.login.response |
Handle OAuth callback |
oauth.profile.exchange |
Exchange OAuth profile |
oauth.token |
Generate OAuth token |
oauth.client.redirect |
Redirect for OAuth |
mail.servers.sync |
Sync mail servers |
cloud_store.files.import |
Import files from cloud storage |
world.delete |
Delete a table |
world.column.delete |
Delete a column |
world.column.rename |
Rename a column |
__become_admin |
Become administrator |
__restart |
Restart server |
__enable_graphql |
Enable GraphQL |
__data_export |
Export table data |
__data_import |
Import table data |
__csv_data_export |
Export as CSV |
__upload_csv_file_to_entity |
Import CSV data |
__upload_xlsx_file_to_entity |
Import Excel data |
__download_cms_config |
Download CMS configuration |
Use special prefixes to substitute values dynamically.
Tested ✓ - Use ~field_name to insert the value of an input field.
InFields:
- Name: greeting_name
ColumnType: label
OutFields:
- Type: client.notify
Method: ACTIONRESPONSE
Attributes:
message: "~greeting_name"When called with {"attributes": {"greeting_name": "Claude"}}, the message becomes "Claude".
Use $.column_name to get values from the target record (instance actions only).
OutFields:
- Type: $network.request
Method: EXECUTE
Attributes:
Body:
order_id: "$.reference_id"
total: "$.total"
customer_email: "$.email"Use !expression for dynamic JavaScript evaluation.
OutFields:
- Type: $network.request
Method: EXECUTE
Attributes:
Url: "!subject.webhook_url"
Body:
calculated: "!subject.price * subject.quantity"
timestamp: "!new Date().toISOString()"The subject object contains the target record's fields.
Tested ✓ - Actions can have multiple OutFields that execute sequentially.
Actions:
- Name: notify_and_redirect
Label: Notify and Redirect
OnType: user_account
InstanceOptional: true
InFields:
- Name: message
ColumnType: label
OutFields:
# First: Show notification
- Type: client.notify
Method: ACTIONRESPONSE
Attributes:
type: success
title: Notice
message: "~message"
# Second: Redirect
- Type: client.redirect
Method: ACTIONRESPONSE
Attributes:
location: "/dashboard"
delay: 1000Response includes both:
[
{"ResponseType": "client.notify", "Attributes": {...}},
{"ResponseType": "client.redirect", "Attributes": {...}}
]Use the Condition field to conditionally execute an OutField.
OutFields:
- Type: otp.generate
Method: EXECUTE
Condition: "!mobile != null && mobile != undefined && mobile != ''"
Attributes:
mobile: "~mobile"The condition is a JavaScript expression. If it evaluates to false, the OutField is skipped.
OutFields:
- Type: user_account
Method: POST
Reference: "new_user"
SkipInResponse: true
Attributes:
email: "~email"
name: "~name"
password: "~password"OutFields:
- Type: order
Method: PATCH
Attributes:
reference_id: "$.reference_id"
status: "shipped"OutFields:
- Type: user_account
Method: GET
Attributes:
query: '[{"column":"email","operator":"is","value":"~email"}]'Validate input fields before processing.
Validations:
- ColumnName: email
Tags: email
- ColumnName: password
Tags: required,min=8
- ColumnName: password_confirm
Tags: eqfield=password| Tag | Description |
|---|---|
required |
Field must not be empty |
email |
Must be valid email format |
min=N |
Minimum length/value |
max=N |
Maximum length/value |
eqfield=X |
Must equal another field |
url |
Must be valid URL |
Transform input values before processing.
Conformations:
- ColumnName: email
Tags: email,lowercase
- ColumnName: name
Tags: trim
- ColumnName: phone
Tags: trim| Tag | Description |
|---|---|
trim |
Remove leading/trailing whitespace |
lowercase |
Convert to lowercase |
uppercase |
Convert to uppercase |
email |
Normalize email format |
Operates on the table, no specific record needed.
POST /action/user_account/signupRequires a specific record ID in the URL.
POST /action/order/abc-123-def/mark_shippedThe target record is available via $.column_name in OutFields.
By default, if an OutField fails, execution stops. Set ContinueOnError: true to continue.
OutFields:
- Type: $network.request
Method: EXECUTE
ContinueOnError: true
Attributes:
Url: "https://optional-service.com/notify"Failed actions return error notifications:
[
{
"ResponseType": "client.notify",
"Attributes": {
"type": "error",
"title": "failed",
"message": "Validation failed: email is required"
}
}
]Actions:
- Name: signup_with_webhook
Label: Sign Up with Webhook
OnType: user_account
InstanceOptional: true
InFields:
- Name: name
ColumnName: name
ColumnType: label
IsNullable: false
- Name: email
ColumnName: email
ColumnType: email
IsNullable: false
- Name: password
ColumnName: password
ColumnType: password
IsNullable: false
Validations:
- ColumnName: email
Tags: required,email
- ColumnName: password
Tags: required,min=8
Conformations:
- ColumnName: email
Tags: lowercase,trim
- ColumnName: name
Tags: trim
OutFields:
# 1. Create user account
- Type: user_account
Method: POST
Reference: new_user
SkipInResponse: true
Attributes:
email: "~email"
name: "~name"
password: "~password"
# 2. Send webhook notification
- Type: $network.request
Method: EXECUTE
ContinueOnError: true
Attributes:
Url: "https://hooks.example.com/new-user"
Method: POST
Headers:
Content-Type: "application/json"
Body:
event: "user.created"
email: "~email"
name: "~name"
# 3. Show success message
- Type: client.notify
Method: ACTIONRESPONSE
Attributes:
type: success
title: Welcome
message: "Account created for ~name"
# 4. Redirect to login
- Type: client.redirect
Method: ACTIONRESPONSE
Attributes:
location: "/auth/signin"
delay: 2000See Actions-Overview for permission configuration.
Key points:
- Actions use the same permission system as tables
- Both entity permission AND action permission must allow execution
- Schema actions can declare
Permissiondirectly; startup sync writes it toaction.permission - Action usergroup membership is configured on
TableName: actionthroughDefaultGroups, not inside each action definition
Cause: Action cache not refreshed. Solution: Restart Daptin server.
Cause: Action has InstanceOptional: false but no record ID in URL.
Solutions:
- Call with record ID:
/action/entity/{record_id}/action_name - Set
instance_optional: 1via PATCH and restart
Cause: Field name mismatch between InFields and OutFields.
Solution: Ensure Name and ColumnName in InFields match what you reference in OutFields.
Cause: All OutFields have SkipInResponse: true or conditions failed.
Solution: Add at least one OutField without SkipInResponse or check conditions.
- Actions-Overview - Built-in actions and permissions
- State-Machines - Trigger actions on state changes
- Task-Scheduling - Run actions on schedule
- Integrations - OpenAPI integrations for external APIs
- Home
- Installation
- First-Admin-Setup ⭐ NEW
- Common-Errors 🔧 NEW
- Getting-Started-Guide
- Configuration
- Database-Setup
- Walkthrough-Product-Catalog
- Walkthrough-WebSocket-Real-Time ✨ NEW
- Walkthrough-YJS-Collaborative-Editing ✨ NEW
- Actions-Overview
- Action-Permission-Schema-Sync-Technical-KT
- User-Actions
- Admin-Actions
- Data-Actions
- Cloud-Actions
- Email-Actions
- Certificate-Actions
- Custom-Actions
- GraphQL-API ✓ NEW
- State-Machines
- Task-Scheduling ✓ NEW
- Template-Rendering ✓ NEW
- Data-Exchange
- Integrations