Loading...
Loading...
Define server-side JavaScript functions for Steedos objects using YAML. Functions are callable via REST API or from buttons/triggers. Covers function definition (.function.yml files in main/default/functions/), inline script with ctx/objects/global context, REST API exposure, calling from amis_button schemas, and examples for CRUD operations, data processing, and external API integration.
npx skill4agent add steedos/steedos-platform object-functions.function.yml.function.ymlsteedos-packages/
└── my-package/
└── main/default/
└── functions/
├── approve_order.function.yml
├── cancel_order.function.yml
└── sync_to_erp.function.yml# functions/orders_approve_order.function.yml
name: orders_approve_order
objectApiName: orders
description: Approve an order and update status
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const { _ } = global;
const record = await objects.orders.findOne(input.id);
if (!record) {
throw new Error('Order not found');
}
if (record.status !== 'submitted') {
throw new Error('Only submitted orders can be approved');
}
await objects.orders.directUpdate(input.id, {
status: 'approved',
approved_at: new Date(),
approved_by: ctx.userId
});
return { message: 'Order approved successfully' };| Property | Type | Required | Description |
|---|---|---|---|
| string | Yes | ⚠️ MUST start with |
| string | Yes | Associated object API name |
| string | No | Human-readable description |
| boolean | Yes | Enable/disable function |
| boolean | Yes | Expose as REST API endpoint |
| boolean | No | Lock from editing |
| string | Yes | Inline JavaScript code (YAML block scalar ` |
// ctx - Execution context
ctx.input // Function input parameters (from API body or caller)
ctx.userId // Current user ID
ctx.spaceId // Current workspace ID
ctx.broker // Moleculer service broker
ctx.getUser(userId, spaceId) // Get user details
// objects - Object API access
objects.orders.findOne(id)
objects.orders.find({ filters, fields, top, skip, sort })
objects.orders.insert(doc)
objects.orders.update(id, doc)
objects.orders.directUpdate(id, doc) // Bypass triggers
objects.orders.directInsert(doc) // Bypass triggers
objects.orders.delete(id)
objects.orders.count({ filters })
// global - Utility libraries
global._ // lodash
global.moment // moment.js (date library)
global.validator // validator.js
global.filters // filter utilitiesis_rest: truePOST /api/v6/functions/{objectApiName}/{shortFunctionName}
GET /api/v6/functions/{objectApiName}/{shortFunctionName}objectApiName_orders_approve_orderordersPOST /api/v6/functions/orders/approve_order
POST /api/v6/functions/leads/convert_lead# functions/orders_submit_order.function.yml
name: orders_submit_order
objectApiName: orders
description: Submit order for approval
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const record = await objects.orders.findOne(input.id);
if (!record) {
throw new Error('Order not found');
}
if (record.status !== 'draft') {
throw new Error('Only draft orders can be submitted');
}
if (!record.customer) {
throw new Error('Customer is required');
}
await objects.orders.directUpdate(input.id, {
status: 'submitted',
submitted_at: new Date(),
submitted_by: ctx.userId
});
return { message: 'Order submitted for approval' };# functions/km_updates_adopt_update.function.yml
name: km_updates_adopt_update
objectApiName: km_updates
description: Adopt a knowledge management update into materials
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const { _ } = global;
const update = await objects.km_updates.findOne(input.id);
if (!update) {
throw new Error('Update record not found');
}
// Find related material
const material = await objects.materials.findOne(update.material_id);
if (!material) {
throw new Error('Related material not found');
}
// Copy fields from update to material
const updateData = {
name: update.name,
description: update.description,
category: update.category,
status: 'active',
updated_from: input.id,
adopted_at: new Date(),
adopted_by: ctx.userId
};
await objects.materials.directUpdate(material._id, updateData);
// Mark update as adopted
await objects.km_updates.directUpdate(input.id, {
status: 'adopted',
adopted_at: new Date()
});
return { message: 'Update adopted successfully', materialId: material._id };# functions/km_updates_trash_record.function.yml
name: km_updates_trash_record
objectApiName: km_updates
description: Mark record as trashed instead of deleting
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const record = await objects.km_updates.findOne(input.id);
if (!record) {
throw new Error('Record not found');
}
await objects.km_updates.directUpdate(input.id, {
is_deleted: true,
deleted_at: new Date(),
deleted_by: ctx.userId
});
return { message: 'Record moved to trash' };# functions/orders_sync_to_erp.function.yml
name: orders_sync_to_erp
objectApiName: orders
description: Sync order to external ERP system
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const order = await objects.orders.findOne(input.id);
if (!order) {
throw new Error('Order not found');
}
const customer = await objects.customers.findOne(order.customer);
const fetch = require('node-fetch');
const response = await fetch(process.env.ERP_API_URL + '/orders', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.ERP_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
order_number: order.order_number,
customer_name: customer?.name,
amount: order.total_amount,
items: order.line_items
})
});
const result = await response.json();
await objects.orders.directUpdate(input.id, {
erp_sync_id: result.id,
erp_sync_status: 'synced',
erp_synced_at: new Date()
});
return { message: 'Synced to ERP', erpId: result.id };# functions/orders_batch_approve.function.yml
name: orders_batch_approve
objectApiName: orders
description: Approve multiple orders at once
isEnabled: true
is_rest: true
locked: false
script: |-
const { input } = ctx;
const { _ } = global;
const ids = input.ids;
if (!ids || !_.isArray(ids) || ids.length === 0) {
throw new Error('No records selected');
}
let successCount = 0;
let failCount = 0;
const errors = [];
for (const id of ids) {
try {
const order = await objects.orders.findOne(id);
if (order && order.status === 'submitted') {
await objects.orders.directUpdate(id, {
status: 'approved',
approved_at: new Date(),
approved_by: ctx.userId
});
successCount++;
} else {
failCount++;
errors.push({ id, reason: 'Not in submitted status' });
}
} catch (e) {
failCount++;
errors.push({ id, reason: e.message });
}
}
return {
message: `Approved ${successCount} orders, ${failCount} failed`,
successCount,
failCount,
errors
};is_rest: trueamis_button# In a .button.yml amis_schema:
amis_schema: |-
{
"type": "service",
"body": {
"type": "button",
"label": "Approve",
"onEvent": {
"click": {
"actions": [
{
"actionType": "ajax",
"api": {
"url": "/api/v6/functions/orders/approve_order",
"method": "post",
"requestAdaptor": "api.data = { id: api.body.recordId }",
"messages": { "success": "Approved" }
}
}
]
}
}
}
}directUpdatedirectInsertinputmessagethrow new Error('message')ctx.userIdctx.getUser()const { _ } = global;