JavaScript Environment
Functions execute JavaScript in serverless containers. This page covers the execution environment, available objects, and best practices.
Available Objects
Core Objects
| Object | Description | Available In |
|---|---|---|
item | The current record being created, updated, or deleted | Trigger functions |
me | Current authenticated user | All functions (when authenticated) |
api | Information about the API | All functions |
secrets | Configured secrets (key/value pairs) | All functions |
req | Incoming HTTP request details | Trigger & Invocable functions |
res | Response object for custom responses | Invocable functions |
collectionName | Name of the collection being processed | Trigger functions |
validationErrors | Array for validation errors (returns 400 if non-empty) | All functions |
The item Object
For trigger functions, item contains the record being processed:
// Access item properties
log('Processing:', item.name);
log('Item ID:', item.id);
// Modify the item (for validation/transformation)
item.slug = item.title.toLowerCase().replace(/\s+/g, '-');
item.updatedAt = new Date().toISOString();
The me Object
Information about the current user:
| Property | Description |
|---|---|
id | User ID (GUID) |
email | User email |
name | User display name |
roleIds | Array of role IDs assigned to the user |
log('Request by:', me.name);
log('User email:', me.email);
log('User roles:', me.roleIds);
The api Object
Information about the current API:
| Property | Description |
|---|---|
id | API ID (GUID) |
name | API name |
tenantId | Tenant ID (GUID) |
log('Running in API:', api.name);
log('Tenant:', api.tenantId);
The req Object
Request details (trigger and invocable functions):
| Property | Description |
|---|---|
method | HTTP method (GET, POST, PUT, PATCH, DELETE) |
headers | Request headers (filtered for security) |
query | Query parameters |
body | Raw body string |
bodyJson | Parsed JSON body |
// Access request data
const method = req.method;
const contentType = req.headers['content-type'];
const page = req.query.page || 1;
const payload = req.bodyJson;
The res Object (Invocable Only)
Set custom responses in invocable functions:
| Property | Description |
|---|---|
status | HTTP status code (default: 200) |
headers | Response headers |
bodyJson | JSON response body |
body | Plain text response body |
// Return custom response
res = {
status: 201,
headers: { 'X-Custom-Header': 'value' },
bodyJson: { success: true, id: newItem.id }
};
Real-Time Channels
Functions can manage private channels (channels with : in the name) using the built-in HTTP methods:
// Add client to a private channel with send permission
await post('/_rt/join', {
connectionId: req.bodyJson.connectionId,
channel: 'room:123',
readOnly: false // Client can send messages
});
// Add client as read-only listener
await post('/_rt/join', {
connectionId: req.bodyJson.connectionId,
channel: 'announcements:system',
readOnly: true // Client can only receive
});
// Send message to any channel
await post('/_rt/send', {
channel: 'announcements:system',
body: { type: 'notification', text: 'New update available' }
});
Private channels (with :) can only be joined/messaged by functions. The readOnly parameter controls whether the client can send messages after joining.
See Real Time for channel types and client-side usage.
Built-in HTTP Methods
Functions have built-in async methods for API calls:
| Method | Description |
|---|---|
get(url) | GET request to API endpoint |
post(url, data) | POST request |
put(url, data) | PUT request |
patch(url, data) | PATCH request |
del(url) | DELETE request |
// Get all active products
const products = await get('products?filter=status eq "active"');
// Create a new order
const order = await post('orders', {
customerId: item.customerId,
total: item.total
});
// Update a record
await patch(`customers/${item.customerId}`, {
lastOrderDate: new Date().toISOString()
});
// Delete a record
await del(`temp-items/${item.id}`);
These methods automatically include authentication and use relative URLs within your API.
User-Context Operations
By default, the built-in HTTP methods (get, post, etc.) run with full admin privileges, bypassing access rules. Use user.* methods to run operations as the current user, respecting their access permissions:
| Method | Description |
|---|---|
user.get(url) | GET as the current user |
user.post(url, data) | POST as the current user |
user.put(url, data) | PUT as the current user |
user.patch(url, data) | PATCH as the current user |
user.delete(url) | DELETE as the current user |
user.getFileBase64(url) | Get file as user |
user.postFileBase64(...) | Upload file as user |
// Admin context - bypasses access rules
const allOrders = await get('orders');
// User context - only returns orders the user can access
const userOrders = await user.get('orders');
// User context - will fail if user lacks write access
await user.post('products', { name: 'New Product' });
This is useful when:
- You want to enforce access rules within function logic
- You need to verify what the user can actually see/modify
- Building multi-tenant logic where users should only see their own data
External HTTP Requests (fetch)
Use fetch for external API calls:
const response = await fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Authorization': `Bearer ${secrets.EXTERNAL_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: 'value' })
});
const data = await response.json();
The fetch function for external HTTP requests is only available on paid tiers. Free tier APIs cannot make external HTTP calls.
File Operations
| Method | Description |
|---|---|
getFileBase64(url) | Get file as Base64 string |
postFileBase64(url, fileName, content, mimeType) | Upload Base64 file |
// Download a file
const fileContent = await getFileBase64(`products/${item.id}/image`);
// Upload a file
await postFileBase64(
`products/${item.id}/thumbnail`,
'thumbnail.jpg',
resizedImageBase64,
'image/jpeg'
);
Sequences
Use getSequence() to generate atomic, auto-incrementing numbers. Each call returns the next value in the sequence, guaranteed to be unique even under concurrent execution.
const orderNumber = await getSequence();
item.orderNumber = orderNumber; // e.g., 1, 2, 3, ...
This is useful for:
- Order numbers — Human-readable sequential IDs
- Invoice numbers — Sequential numbering for accounting
- Ticket numbers — Support ticket or queue numbers
- Any sequential identifier — When UUIDs aren't appropriate
getSequence() uses database-level sequencing, ensuring no duplicates even when multiple function instances run concurrently.
Secrets
Secrets are securely stored credentials injected into your function.
Configuring Secrets
- Create secrets in Settings → Secrets
- Select which secrets to inject when configuring your function
- Access them via the
secretsobject
Using Secrets
// Access secrets
const apiKey = secrets.STRIPE_API_KEY;
const webhookSecret = secrets.WEBHOOK_SECRET;
// Use in external calls
const response = await fetch('https://api.stripe.com/v1/charges', {
headers: {
'Authorization': `Bearer ${secrets.STRIPE_SECRET_KEY}`
}
});
Never log secrets or include them in error messages.
Logging
| Function | Severity |
|---|---|
log(...args) | Info (multiple arguments supported) |
logInfo(message) | Info |
logWarn(message) | Warning |
logError(message) | Error |
log('Processing started');
log('Item:', item.name, 'Status:', item.status); // Multiple args
logInfo('Single info message');
logWarn('Customer has pending balance');
logError('Failed to sync with external service');
Logs include timestamps and milliseconds from execution start.
Context by Function Type
Trigger Functions
// Available: item, me, api, secrets, req, collectionName, validationErrors
log('Processing item:', item.id);
log('Collection:', collectionName);
log('Triggered by:', me.name);
log('Method:', req.method);
// Validate
if (!item.title) {
validationErrors.push('Title is required');
}
// Modify item before save
item.updatedBy = me.id;
Timer Functions
// Available: api, me (service account), secrets
// No item or request context
log('Running scheduled task for API:', api.name);
const ordersRes = await get('orders?filter=status eq "pending"');
for (const order of ordersRes.data) {
// Process each order...
}
Invocable Functions
// Available: req, res, me, api, secrets
const input = req.bodyJson;
const result = processData(input);
res = {
status: 200,
bodyJson: { success: true, result }
};
Error Handling
Validation Errors
Use validationErrors to return user-facing errors. If this array has any items when the function completes, the operation returns 400 Bad Request with the errors in the response body.
if (!item.email) {
validationErrors.push('Email is required');
}
if (item.price < 0) {
validationErrors.push('Price cannot be negative');
}
// Multiple errors are returned together
Response when validation fails:
["Email is required", "Price cannot be negative"]
Runtime Errors vs Validation Errors
| Type | Example | Returned to Client | Logged |
|---|---|---|---|
| Validation errors | validationErrors.push('...') | Yes (400 response) | Yes |
| Runtime errors | undefined.foo, failed fetch | No | Yes |
Runtime errors (like undefined variables or failed external requests) are only logged server-side and not exposed to the client. Use validationErrors for errors you want users to see.
Try-Catch
Handle external failures gracefully:
try {
const external = await fetch(externalUrl);
if (!external.ok) {
logError('External API returned: ' + external.status);
validationErrors.push('Unable to verify with external service');
}
} catch (error) {
logError('External call failed: ' + error.message);
// Decide: add to validationErrors or let it proceed?
}
Filtered Headers
For security, these headers are removed from req.headers:
authorization,cookie,hostx-forwarded-*,x-azure-*,x-arr-*- Other infrastructure headers
Best Practices
- Use
itemnotbody— For trigger functions, the record is initem - Use built-in methods —
get(),post(), etc. handle auth automatically - Use
validationErrorsfor user-facing errors — These are returned in the response - Handle external failures with try-catch — Decide whether to block or continue
- Log important operations — Aids debugging and auditing
- Keep secrets secure — Never expose in logs or responses