1. App Functions
Salla Platform Docs
  • Welcome 👋
  • What are App Functions?
  • Get Started
  • Supported Events
  • Testing
  • Responses
  • NodeJs Support
  • Command Line Interface (CLI)
  • Merchants Events
    • Invoice Events
    • Shipping Zone Events
    • Category Events
    • Special Offer Events
    • Customer Events
    • Store Branch Events
    • Shipping Company Events
    • Review Events
    • Shipment Events
    • Cart Events
    • Brand Events
    • Communication Events
    • Order Events
  • Customers Events
    • Cart & Checkout Events
    • Account Events
    • Product Events
    • Promotion & Coupon Events
    • Wishlist Events
Merchant
Merchant
  • Merchant API
  • Embedded SDK
  • Salla OAuth 2.0
Storefront
Storefront
  • Twilight Engine
  • Twilight SDK
  • Web Components
  • Ecommerce Events
  • Component Bundle
  • Checkout APIs
  • Change Log
App Functions
Partner APIs
Partner APIs
  • App API
  • Shipments & Fulfillment APIs
  • Salla AWB
  • Recurring Payments API
  • Billing System Salla partners
  • Communication Apps
Dev Tools
Dev Tools
  • Partners Agent Kit
  • Salla Apps Playbook
  • Salla CLI
Merchant
Merchant
  • Merchant API
  • Embedded SDK
  • Salla OAuth 2.0
Storefront
Storefront
  • Twilight Engine
  • Twilight SDK
  • Web Components
  • Ecommerce Events
  • Component Bundle
  • Checkout APIs
  • Change Log
App Functions
Partner APIs
Partner APIs
  • App API
  • Shipments & Fulfillment APIs
  • Salla AWB
  • Recurring Payments API
  • Billing System Salla partners
  • Communication Apps
Dev Tools
Dev Tools
  • Partners Agent Kit
  • Salla Apps Playbook
  • Salla CLI
Salla - Opensource
Salla - Developers Community
  1. App Functions

Command Line Interface (CLI)

Everything you have built so far in this guide lives in the Partner Portal editor. The Salla CLI gives you the other way in: author your App Functions locally in TypeScript, keep them in version control, and ship them with salla app functions build, deploy, and serve.
This page walks you through that workflow end to end — from installing the CLI to watching your deployed handlers react to live store events.
Important
App Functions created from the CLI and App Functions created from the dashboard / Salla Partners Portal are not interchangeable. If an App already has functions that were created from the dashboard or the portal, you can't add CLI functions to that App — and the other way around. For the CLI, use a new App in which no App Function was previously added.

Prerequisites#

Before you begin, make sure you have:
✔️ The basics from Get Started — a Salla Partner account, a demo store, and the App Scopes your events need
✔️ A new Salla App — one with no existing App Functions (see the warning above)
✔️ Node.js ≥ 22.12 — required by the Salla CLI
✔️ A terminal — every command on this page runs from your project root

Step 1: Install the Salla CLI#

1
Install the CLI globally
This puts the salla binary on your PATH.
2
Log in to your Partner account
deploy and serve need a valid session. build runs fully offline.
3
Verify the installation
Full installation details, including Rosetta for Apple silicon and PowerShell execution policies, are in the Salla CLI Usage guide.

Step 2: Create Your Project#

The quickest way to start is the official App Functions Starter Kit — a ready-made TypeScript project with the entry point, example handlers, and tests already wired up.
1
Scaffold from the template
Use GitHub's Use this template button, or clone it directly:
2
Install dependencies
You get this structure:
src/
  index.ts                # the events map — your default export
  functions/              # one file per handler
    order-created.ts
    customer-login.ts
test/
  index.spec.ts           # example tests for the handlers
dist/
  index.js                # build output (generated; this is what gets deployed)
.env.example              # template for CLI config — copy to .env

Step 3: Register Your Event Handlers#

A CLI project has exactly one contract: src/index.ts must default-export an events map that binds Salla event names to handlers. The CLI refuses to build without it.
import type { DefineEvents } from "@salla.sa/app-functions-types";
import { orderCreated } from "./functions/order-created";
import { customerLogin } from "./functions/customer-login";

// The types package ships no runtime code, so provide the identity
// implementation and borrow its `DefineEvents` signature for key validation.
const defineEvents: DefineEvents = (events) => events;

const events = defineEvents({
  "order.created": orderCreated,
  "customer.login": customerLogin,
});

export default events;
Each handler receives the typed context and returns a FunctionResponse. Handlers may be async and return Promise<FunctionResponse>:
import type { FunctionResponse, Order } from "@salla.sa/app-functions-types";

export const orderCreated = (context: Order): FunctionResponse => {
  const order = context.payload.data;

  console.log(`New order #${order.id} received!`);

  return {
    success: true,
    status: 200,
    message: `Order ${order.reference_id} received`,
    data: { orderId: order.id },
  };
};
To wire up a new event:
1.
Create src/functions/<your-handler>.ts exporting a handler function
2.
Import it in src/index.ts
3.
Add a '<salla.event>': yourHandler entry to the map
Event names are exact string literals in dot.case — order.created, not "Order Created". Browse them all in Supported Events. You can also define your own — see Working with Custom Events.

Step 4: Point the CLI at Your App#

All three commands resolve your App ID the same way, stopping at the first source that has one:
PrioritySourceExample
1The [app_id] argument or --app-id optionsalla app functions deploy 1234567890
2SALLA_APP_ID in your project's .envsee below
3The App ID remembered from your last runstored in ~/.salla/config.json
4Interactive pickerdeploy and serve ask you to choose
Setting it once in .env is the recommended setup for a project you deploy repeatedly — every command then just works with no arguments:
When you provide nothing at all:
🚀 deploy and 👁️ serve show an interactive picker, listing your Salla Partners Apps by name and ID
📦 build doesn't need an App ID — it only uses it to print a ready-to-copy deploy command, falling back to an <app_id> placeholder
? Select the app you want to deploy to
❯ My Awesome App [1234567890] (public)
  Shipping Companion [9876543210] (shipping)
Every resolved App ID — typed explicitly or picked from the list — is remembered as the default for your next build, deploy, or serve. To work against a different App, pass its ID explicitly; an explicit App ID always overrides the remembered one. If you pass both a positional argument and --app-id, the option wins.

Step 5: Build Your Functions#

The build command bundles src/index.ts — together with everything it imports — into a single ESM file at dist/index.js, ready to be deployed.
app-functions-build.gif
On success, the CLI prints the output path and the exact follow-up command to run:
✔ Hooray! Built /your-project/dist/index.js successfully.
ℹ You can now deploy your app using: salla app functions deploy 1234567890
Details worth knowing:
⚙️ Bundling is done with esbuild (format: esm, target: es2022), so TypeScript is compiled and all local imports are inlined into one file
❌ If the project has no src/index.ts, the build fails with a clear error
🏷️ The App ID in the follow-up hint comes from Step 4; if none is set, an <app_id> placeholder is shown instead

Step 6: Deploy to Salla#

The deploy command builds your bundle and ships it to Salla's infrastructure.
app-functions-deploy.gif
If you don't pass an App ID, the CLI asks you to pick one first (see Step 4). The deploy then runs three stages, and the CLI reports each one:
1
Build
The bundle is rebuilt from src/index.ts, identical to running build. Pass --skip-build to deploy the existing dist/index.js as-is; if that file doesn't exist, the command stops and asks you to run salla app functions build first.
2
Upload
The bundle is uploaded to the App Builder API, and the CLI prints the deployment job ID and the new version.
✔ Uploaded — job 42 (v7).
3
Deploy status
The CLI connects to Salla's deploy-status websocket and waits up to 5 minutes for your job's result.
✔ Deploy complete!
✔ Preview: https://portal.salla.partners/apps/1234567890/functions
ℹ You can preview your changes using the preview URL above, and watch live logs using: salla app functions serve
If the deploy fails, the failure reason is printed. If no status arrives within 5 minutes, the CLI times out and asks you to check the job status on the App Builder dashboard.
Your functions are live. Open the preview URL to try them in the portal, or move on to Step 7 to watch them handle real events. 🎉

Step 7: Watch Live Logs#

The serve command connects to your app's log channel and streams every console.log / console.error your deployed functions emit, as they handle live events.
app-functions-serve.gif
On start-up, serve reads the events map from your project and sets up a way to trigger each kind of event it finds:
1
It connects to your log channel
ℹ Connecting to p:416209744 ...
✅ App functions are now serving.

Come back here to view logs.
2
For Salla events, it opens the portal in your browser
The portal can fire these for you — place a test order, update a product, sign in as a customer — and the log lines land in your terminal as they happen.
ℹ To test "order.created", you'll be redirected to your browser — trigger it from the portal (e.g. place a test order for order.created).
If the browser can't be opened, the CLI prints the link for you to open manually.
3
For custom events, it prints a ready-to-run cURL
The portal can't trigger custom.event.* handlers, so the CLI builds the request for you and prints one block per custom event in your map. Copy it, paste it into your terminal or Postman, and the event shows up in the same serve session.
See Triggering Events for Testing for the full block and what to edit before running it.
The session then keeps streaming until you press Ctrl+C:
2026-07-23 14:05:12 |  INFO | New order #560915 received!
2026-07-23 14:05:12 | ERROR | Failed to notify the shipping provider
📝 Each line is formatted as timestamp | LEVEL | message, with the level color-coded (TRACE, DEBUG, LOG, INFO, WARN, ERROR)
🔍 Messages belonging to your other Apps are filtered out automatically
🐛 Add --raw to print the full message envelopes exactly as the server sends them — including deploy-status and connection messages that are normally hidden
For unit tests, the preview panel, and testing strategies in general, see Testing App Functions.

Working with Custom Events#

Beyond Salla's own events, you can define your own — useful for cron-style jobs, back-office triggers, or anything your app needs to kick off itself. Custom events are namespaced under custom.event. and registered in the same map:
const events = defineEvents({
  "custom.event.nightly-inventory-sync": nightlyInventorySync,
});
The suffix is validated at compile time, so a bad name fails npm run typecheck rather than at deploy:
Rule✅❌
Lowercase a–z, 0–9, - and . onlycustom.event.order-synccustom.event.orderSync
No leading or trailing hyphencustom.event.sync-v2custom.event.-sync
No consecutive hyphenscustom.event.sync-v2custom.event.sync--v2
Max 60 characters—a 61-character suffix

Triggering a Custom Event#

Custom events are fired by POSTrequest to your App's trigger URL:
https://api.salla.dev/platform/general/<app_id>/local
You don't have to assemble that request yourself — run salla app functions serve and the CLI prints it for every custom event in your map, token included:
Before you run it
1.
Your App must be installed on the store you want to test with.
2.
Replace <YOUR_STORE_ID> with that store's ID — the CLI does not fill this one in for you. It lists your stores right below the cURL block; you can also run salla store list.
3.
Fill in data with your own payload — see below.

Passing Your Own Data#

The CLI prints "data": {} as a placeholder, but that object is yours to define. Unlike Salla's events — where the payload shape is fixed by the platform — a custom event carries whatever you put in it, and the platform passes it through untouched:
Your handler reads it back from context.payload.data:
import type { FunctionResponse, SallaCustomEvent } from "@salla.sa/app-functions-types";

export const nightlyInventorySync = (context: SallaCustomEvent): FunctionResponse => {
  const { sku, quantity, source } = context.payload.data;

  console.log(`Syncing ${quantity} × ${sku} from ${source}`);

  return {
    success: true,
    status: 200,
    message: "Inventory synced",
    data: { sku, quantity },
  };
};

Accessing the Custom Event Authorization#

By default, custom.event.* handlers are public — anyone who knows your event URL can trigger them without providing any credentials.
If you want to restrict a custom event so only authorized callers can trigger it, make it private by requiring the caller to send an X-App-Function-Authorization header on the request:
X-App-Function-Authorization: Bearer <your-token-is-filled-in-here>
The function reads this header and exposes it to your handler as context.authorization.
If the header is present, context.authorization contains the original token together with the parsed scheme:
{
  token: "Bearer <token>",
  scheme: "Bearer"
}
If the header is missing or blank (i.e. the event is public and no credentials were sent), the handler still runs and context.authorization is set like this:
{
  token: null,
  scheme: null
}
That means your custom-event handlers can rely on context.authorization directly instead of parsing the request header themselves — check whether token is null to know whether the incoming request was authenticated.
Using it to call another endpoint
A common pattern is to forward this same token when your handler calls out to another API — for example, hitting one of your own protected endpoints to fetch data needed to process the event:
export default async function (context) {
  if (!context.authorization.token) {
    return { status: 401, body: "Unauthorized" };
  }

  const response = await fetch("https://api.example.com/inventory/status", {
    method: "GET",
    headers: {
      "Authorization": context.authorization.token,
      "Content-Type": "application/json",
    },
  });

  const data = await response.json();

  // ...continue processing the event with `data`
  return { success: true, status: 200, body: data };
}
Here, context.authorization.token already includes the scheme prefix (e.g. Bearer <token>) if the X-App-Function-Authorization header is present, so it can be passed straight into the outgoing Authorization header without any extra formatting.
Prefer a visual client? The Postman tab in Triggering Events for Testing shows how to import the same request.

Triggering Events for Testing#

With serve running, pick the trigger that matches the event you want to exercise. Whichever you use, the resulting log lines land in your open serve session in real time.
Partner Portal
cURL
Postman
Best for Salla's own events — order.created, customer.login, product.updated, and the rest.
serve opens the portal on your App's functions page for you. From there, perform the real action against your demo store — place a test order, update a product, sign in as a customer — and watch the corresponding log lines appear in your terminal as they happen.
ℹ To test "order.created", you'll be redirected to your browser — trigger it from the portal (e.g. place a test order for order.created).
Important
The portal cannot trigger custom.event.* handlers. Use the cURL or Postman tab for those.

Command Reference#

CommandDescriptionApp ID
salla app functions build [app_id]Bundles src/index.ts into dist/index.jsOptional — only used in the printed hint
salla app functions deploy [app_id]Builds and deploys your bundle to SallaOptional — prompts if omitted
salla app functions serve [app_id]Streams live logs and prints event triggersOptional — prompts if omitted
Options
OptionApplies toDescription
[app_id] / --app-id <app_id>allTarget App ID (overrides SALLA_APP_ID from .env)
--skip-builddeployDeploy the existing dist/index.js without rebuilding
--rawservePrint full message envelopes instead of formatted log lines
The command surface may evolve — run salla app functions --help for the current list.

Troubleshooting#

SymptomLikely cause
No src/index.ts foundRunning the CLI outside the project root, or the events map isn't the default export
Argument of type '{ … }' is not assignable to parameter of type 'never'An event key isn't recognised — keys are lowercase dot.case triggers (product.viewed, not "Product Viewed")
No dist/index.js foundYou used --skip-build before ever building — run salla app functions build
No apps found for your partner accountCreate an App in the Partner Portal first
Deploy times out after 5 minutesNo status arrived on the websocket — check the job on the App Builder dashboard
serve prints no cURL blockNo custom.event.* key was found in your events map — check src/index.ts
serve shows no logsNo events fired yet, or the App ID doesn't match the App you deployed to
Custom event returns but nothing runs<YOUR_STORE_ID> wasn't replaced, or the app isn't installed on that store

Next Steps#

📋 Supported Events — find the events your handlers should listen to
🤝 Understanding App Function Responses — what to return, and how the platform reacts
🧪 Testing App Functions — validate your handlers before shipping
💻 NodeJs Support — which runtime APIs are available inside a function

Need Help?#

📖 Documentation — Browse complete documentation
🐛 Report a CLI bug — Open an issue on the Salla CLI repository
👤 Partner Portal — Access your developer dashboard
👥 Community — Join the Salla developer community on Telegram
Modified at 2026-09-06 15:24:11
Previous
NodeJs Support
Next
Invoice Events