Testing your App Functions is crucial to ensure they work correctly before deploying to production. This guide covers everything you need to know about testing your functions effectively.
Salla provides built-in testing tools directly in the Partner Portal, allowing you to:
Before testing your App Functions, ensure:
Click Select Store in the preview panel and choose your demo store from the dropdown.

Navigate to your demo store dashboard to get the required test data:

Enter the required parameters in the preview panel. For example, if testing an Order Status Updated function, enter the Order ID.

Click Save and Preview to execute your function with the test data.

The preview panel will display:
✔️ Execution Status — Success or failure
📦 Response Data — The data returned by your function
⏱️ Execution Time — How long the function took to run
📝 Console Logs — Any console.log() output from your function
❌ Errors — Any errors that occurred during execution
Understanding the context object structure is essential for writing effective functions.
To view the expected context structure for your function:
Right-click on the context parameter in your function signature.

Choose Peek > Peek Definition from the context menu.

View the complete context structure including all available properties and their types.

Scroll through to see:
📦 Payload structure
📋 Event-specific data fields
⚙️ Settings object
🏪 Merchant information
💻 Type definitions
When your function calls Salla APIs or external services, follow these best practices:
export default async (
context: OrderCreatedContext
): Promise<Resp> => {
// Test accessing Salla API with automatic authentication
// NOTE: Authorization header is automatically injected.
const response = await fetch("https://api.salla.dev/admin/v2/orders", {
method: "GET",
});
const orders = await response.json();
// Log for debugging (visible in preview panel)
console.log("Fetched orders count:", orders.data?.length);
const data = {
total_orders: orders.data?.length,
first_order: orders.data?.[0],
};
/*
* The .setData() should be called mandatorily. (Pass {} as default)
* The .setStatus() is optionallly called. The default status is 200.
* The .setMessage() is optional.
* Incase there is any error invoke Resp.error().
*/
const response = Resp.success().setData(data)
return response;
};
**Automatic Authentication**: You can access Salla APIs with automatic token injection. No need to manually handle authentication when coding inside the Partner Portal.
export default async (
context: OrderStatusUpdatedContext
): Promise<Resp> => {
try {
const { payload, settings, merchant } = context;
// Test external webhook
const webhookResponse = await fetch(settings.webhookUrl, {
// URL from app settings
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${settings.apiKey}`, // API key from app settings
},
body: JSON.stringify({
order_id: payload.data.id,
status: payload.data.status,
timestamp: new Date().toISOString(),
}),
});
// Log response for debugging
console.log("Webhook status:", webhookResp.status);
console.log("Webhook response:", await webhookResp.text());
const data = {
webhook_status: webhookResp.status
};
/*
* The .setData() should be called mandatorily. (Pass {} as default)
* The .setStatus() is optionallly called. The default status is 200.
* The .setMessage() is optional.
* Incase there is any error invoke Resp.error().
*/
const response = Resp.success().setData(data)
return response;
} catch (error) {
console.error('Error sending webhook:', error);
return Resp.error()
.setMessage(error.message || 'Unknown error')
.setStatus(500)
.setData({ error_type: error.name });
}
};
Test your function with different types of data:
Add console.log statements to track execution flow:
console.log("Function started for order:", context.payload.data.id);
console.log("Calling external API...");
console.log("API response received:", response.status);
console.log("Function completed successfully");
**Avoid Logging Sensitive Data**: Never log sensitive information like:
Always return a structured response object:
// Success response
return Resp.success().setData({
// Relevant data
});
// Error response
return Resp.error()
.setMessage("Descriptive error message")
.setStatus(500)
.setData({
// Additional error context
});
Use try-catch blocks and provide meaningful error messages:
try {
// Your logic
} catch (error) {
console.error("Error details:", error);
return Resp.error()
.setMessage(error.message || 'Unknown error')
.setStatus(500)
.setData({ error_type: error.name });
}
Check that required data exists before using it:
export default async (
context: OrderCreatedContext
): Promise<Resp> => {
const { payload, settings, merchant } = context;
// Validate required data
if (!payload.data?.id) {
return Resp.error()
.setMessage('Order ID is missing')
.setStatus(400)
.setData({});
}
if (!settings.webhookUrl) {
return Resp.error()
.setMessage('Webhook URL not configured in settings')
.setStatus(400)
.setData({});
}
// Continue with logic...
};
Keep response objects small and focused:
// ❌ Bad: Returning entire payload
return Resp.success().setData({
data: context.payload
});
// ✅ Good: Returning only relevant data
return Resp.success().setData({
order_id: context.payload.data.id,
status: context.payload.data.status,
processed_at: new Date().toISOString(),
});
**Response Size**: If the response is too large, log only key fields. The preview window works best with concise responses.
When testing functions that call external webhooks:
export default async (
context: OrderCreatedContext
): Promise<Resp> => {
try {
const webhookUrl = "https://webhook.site/your-unique-id";
const response = await fetch(webhookUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
event: context.payload.event,
order_id: context.payload.data.id,
merchant_id: context.payload.merchant.id,
timestamp: new Date().toISOString(),
}),
});
console.log("Webhook called successfully:", response.status);
return Resp.success().setData({
webhook_status: response.status,
});
} catch (error) {
return Resp.error()
.setMessage(error.message)
.setStatus(400)
.setData({});
}
};
Possible Causes:
Solutions:
// Add timeout to fetch
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000); // 5 second timeout
try {
const response = await fetch(url, {
signal: controller.signal, // Links the fetch request to the abort controller
});
clearTimeout(timeout);
} catch (error) {
if (error.name === "AbortError") {
console.error("Request timed out");
}
}
**Synchronous Actions Performance**: If you're testing a **synchronous action** (e.g., `shipment.creating`), remember that the user is blocked and waiting. Your function must respond in **milliseconds** (< 500ms recommended). Avoid:
Keep synchronous actions simple and fast!
Possible Causes:
Solutions:
Possible Causes:
Solutions:
Once you've thoroughly tested your function: