Services
Services handle external data operations - API calls and custom functions that power your form's dynamic behavior.
What Are Services?
Services are reusable operations that handle data fetching, API communication, and custom business logic. They're called by lifecycle hooks, actions, and validations to create dynamic form experiences.
Two Types of Services
🌐REST API Services
Call external REST APIs with full control over HTTP methods, headers, and request/response handling.
{
"type": "rest",
"url": "/api/users/{{context:userId}}",
"method": "GET",
"headers": {
"Authorization": "Bearer {{context:authToken}}"
}
}⚡Function Services
Execute custom JavaScript functions for complex logic, data transformation, and calculations.
{
"type": "function",
"function": "calculateTotal"
}REST API Service Structure
Complete REST Service Definition
{
"userApiService": {
"type": "rest",
"url": "/v1/users/{{context:userId}}",
"method": "GET",
"baseURL": "https://jsonplaceholder.typicode.com",
"headers": {
"Accept": "application/json"
}
},
"orderApiService": {
"type": "rest",
"url": "/api/orders",
"method": "POST",
"format": "json",
"body": {
"userId": "{{context:user.id}}",
"email": "{{context:form.email}}",
"data": "{{event:customData}}"
},
"baseURL": "https://api.example.com",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer {{context:auth.token}}",
"X-Custom-Header": "value"
},
"bearer": "{{context:auth.bearerToken}}",
"auth": {
"username": "{{context:credentials.username}}",
"password": "{{context:credentials.password}}"
}
}
}REST Service Properties:
1. type: "rest"
Always "rest" for HTTP API calls
2. url
API endpoint path (can include context placeholders)
/api/users/{{context:userId}}3. method
HTTP method (GET, POST, PUT, DELETE, PATCH)
4. format
Request body format: "json" or "formData"
5. body
Request payload (for POST/PUT/PATCH)
6. baseURL
Base URL for the API (optional)
Example: "https://api.example.com"
Combines with url to create full endpoint:
• baseURL: "https://api.example.com" + url: "/users" = "https://api.example.com/users"
7. headers
Custom HTTP headers (supports context placeholders)
8. bearer
Bearer token for Authorization header
9. auth
Basic authentication credentials
Placeholder Syntax
| Placeholder | Description | Example |
|---|---|---|
| {{context:path}} | Access form context data | {{context:user.email}} |
| {{secret:path}} | Access encrypted secret data (🔒 secure) | {{secret:apiToken}} |
| {{event:property}} | Access event/lifecycle data | {{event:userId}} |
| {{block:path}} | Access current block data | {{block:id}} |
🔒 Security & Encrypted Secrets
Never Store Sensitive Data in Plain Text
API tokens, passwords, and credentials should never be stored in regular context. Use {{secret:path}} placeholders with encrypted secret context instead.
Using Encrypted Secrets
For sensitive data like API tokens and passwords, use the secretContext with {{secret:path}} placeholders:
{
"fetchUserData": {
"type": "rest",
"url": "/api/user",
"method": "GET",
"bearer": "{{secret:apiToken}}"
},
"authenticateUser": {
"type": "rest",
"url": "/api/auth",
"method": "POST",
"auth": {
"username": "{{secret:apiUsername}}",
"password": "{{secret:apiPassword}}"
}
},
"callExternalAPI": {
"type": "rest",
"url": "/api/data",
"headers": {
"X-API-Key": "{{secret:externalApiKey}}"
}
}
}Mixed Context & Secrets
You can safely combine regular context values with encrypted secrets:
{
"getUserOrders": {
"type": "rest",
"url": "/api/users/{{context:userId}}/orders",
"method": "GET",
"bearer": "{{secret:apiToken}}",
"headers": {
"X-Request-ID": "{{context:requestId}}"
}
}
}✅ Security Best Practices
- • Use
{{secret:}}for tokens, passwords, API keys - • Use
{{context:}}for user IDs, form data, non-sensitive info - • Store encryption keys in environment variables
- • Never hardcode sensitive data in JSON configs
- • Enable HTTPS in production
→ Learn more about Security & Encrypted Secrets
Function Service Structure
Function Service Definition
{
"calculateTax": {
"type": "function",
"function": "calculateTax"
},
"formatName": {
"type": "function",
"function": "formatUserName"
}
}Function Service Properties:
1. type: "function"
Always "function" for custom functions
2. function
Name of the JavaScript function to call
Function Implementation
Functions are implemented as JavaScript functions that receive context and return data:
// In your application code
function calculateTax(context) {
const subtotal = context.order.subtotal;
const taxRate = context.location.taxRate || 0.08;
return Math.round(subtotal * taxRate * 100) / 100;
}
function formatUserName(context) {
const first = context.user.firstName;
const last = context.user.lastName;
return `${first} ${last}`.trim();
}Common Service Patterns
1. Data Fetching
{
"loadUserProfile": {
"type": "rest",
"url": "/api/users/{{context:userId}}",
"method": "GET"
}
}2. Form Submission
{
"submitForm": {
"type": "rest",
"url": "/api/submit",
"method": "POST",
"format": "formData",
"body": {
"name": "{{context:form.name}}",
"email": "{{context:form.email}}",
"file": "{{context:form.uploadedFile}}"
}
}
}3. Auto-lookup Services
{
"lookupCity": {
"type": "rest",
"url": "/api/lookup/city/{{context:zipCode}}",
"method": "GET"
},
"validateEmail": {
"type": "function",
"function": "validateEmailFormat"
}
}4. Complex Calculations
{
"calculateTotal": {
"type": "function",
"function": "calculateOrderTotal"
},
"applyDiscount": {
"type": "function",
"function": "applyCouponDiscount"
}
}Error Handling
Services can fail, so always handle errors in your lifecycle hooks:
{
"lifecycle": {
"onFormLoad": [
{
"type": "callApi",
"service": "loadUserProfile",
"then": [
{
"type": "assignContext",
"assign": {
"user": "{{event}}"
}
}
],
"else": [
{
"type": "toast",
"message": "Failed to load user profile. Please try again."
}
]
}
]
}
}Testing Services
Test your services independently before integrating them into forms:
// Test REST service
const testService = {
type: "rest",
url: "/api/test",
method: "GET"
};
// Test function service
function testCalculation(context) {
return context.price * context.quantity;
}