xMatix
Sign in Request demo
xMatix
PRODUCTS
SalesField Sales & SFACRMRewardsClaimsInventoryProcurementWarehouse ManagementField ServiceServiceSupportTelephony & MessagingFinance & AccountingPayrollExpense ManagementCommercePortalsAnalytics & ReportingData StudioMobile AppSee all products →
PLATFORM
Platform overviewApp BuilderAutomationIntegrationsSecurity & GovernanceChange ManagementDevelopers
SENSE AI
Sense AI overviewSense AssistSense ControlSense VisionAI StudioTrust & governanceIn Claude & ChatGPTUse cases
SOLUTIONS
Auto DMSConsumer Goods DMSSales Force Automation (SFA) FMCG & DistributionManufacturing & Dealer NetworksAutomotive & DealershipsPharma & HealthcareConsumer DurablesAgri-InputsBuilding MaterialsService NetworksWarehousing & 3PLFinancial AccountingERP SoftwareIndia GST ComplianceUAE VAT & e-InvoicingSaudi ZATCA & VATAll solutions →
RESOURCES
Knowledge CenterDeveloper & CLIBlogGuidesWhat is xMatix?Company facts
COMPANY
AboutCareersPartnersEventsContactAuthorsLegal
Sign in Request demo
Home/Docs/Developer/Scripts in the CLI
REFERENCE · Last reviewed

Scripts in the CLI

xmatix scripts copies automation scripts between a tenant and your workspace, so you can edit a script's C# body in your own editor, keep it in git, push a new version, test it against a record and watch its runs from a terminal. Scripts are created and bound in the product under Setup → Process Studio → Scripts. The CLI works on the versions of scripts that already exist there.

xmatix scripts list
xmatix scripts pull --all                   # every script in the tenant
xmatix scripts pull <name>                  # one script, exact name
xmatix scripts push <file.csx>              # upload a new version (not live)
xmatix scripts push <file.csx> --activate   # …and make it the active version
xmatix scripts run <file-or-name> --record <record-id>
xmatix scripts logs --script <name-or-id> --follow
xmatix runs tail --follow                   # the same feed across all scripts

Overview

There are five commands, plus one alias:

  • scripts list prints a table with the columns Name, Disabled, Current Version Id and Updated, sorted by name, and ends with "N script(s)." A tenant with no scripts prints "(no scripts in this tenant)".
  • scripts pull writes a script's active version to a .csx file and its settings to a .meta.json file next to it.
  • scripts push uploads a .csx file as a new version of its script. The new version goes live only with --activate.
  • scripts run asks the server to run the script's active version once against a record, and prints the result.
  • scripts logs lists the script run history, and with --follow streams new runs as they arrive.
  • runs tail is the same command as scripts logs. Without --script both show runs from every script in the tenant.

The CLI can't create, rename, disable or delete a script, add or change a binding, activate an older version, or delete a version. Do those in the script editor.

When to use it

  • To keep a tenant's script bodies in source control and review a change as a diff before it goes live.
  • To edit a script with your own editor's tooling and push each save automatically with xmatix watch.
  • To push a new version from a build pipeline and activate it only after a check passes.
  • To follow script runs from a terminal while you test, instead of refreshing the Runs tab.

Use the script editor in the product to create a script, write its first version (in the editor, saving a body that compiles activates it at once — the CLI is the way to stage a version without making it live), add bindings, publish it as an agent tool and read the Readme tab, which documents the script API.

How xMatix handles it

Every push is a new version. The server numbers versions 1, 2, 3… per script and compiles each body as it saves it. The version is saved even when compilation fails: it is stored with its compile errors (shown as Errors on the script editor's Versions tab), and --activate does not make it active. The active version is the one bound triggers run — pushing without --activate changes nothing at run time.

Push checks for conflicts first. The .meta.json file remembers which version was active when you last pulled. Before pushing, the CLI reads the script's active version from the server. If someone has activated a different version since (in the product or from another workspace), push stops with "Server has a newer current version than your local copy. Pull, merge, then re-push (or use --force)." and exits with code 2. --force skips this check.

Push updates your meta file only when it activates. After a successful push with --activate, the CLI records the new version as the active one in .meta.json. After a push without --activate, or a push that failed to compile, the meta file still points at the old active version, because that is still what runs.

"Nothing to push" is detected only after an activating push. Push compares a fingerprint of your file with the one in .meta.json and, when they match, prints "No changes vs current version (vN); nothing to push." and stops. A pull stores the fingerprint in a different format from the one push calculates, so straight after a pull the two never match: pushing an unchanged file creates another identical version. Once you have pushed with --activate from this workspace, the check works until your next pull.

Run tests the active version, and only that script. scripts run sends the script, an entity, a phase or action, a change kind and an optional record id to the same test facility as the product's Runs tab. The server runs the script's active version directly — other scripts bound to the same trigger don't run, and the script doesn't need a binding that matches. Side effects are real: records the script writes are saved and messages it sends are sent. Test against records you can afford to change.

Run history is not a full log. The run history keeps every test run and every live run that failed or was cancelled. A live run that succeeded is kept only when the person whose action triggered it has tracing switched on (see Configuration), or when the platform is set to keep every run. So an empty feed for a busy trigger usually means the runs succeeded, not that nothing ran.

Names are matched exactly. pull, run and logs --script find a script by its exact name, case included. logs --script also accepts the script's id.

Prerequisites

  • An initialised, signed-in workspace: run xmatix init in your repository root and sign in (see Authentication and Workspace layout). Outside a workspace every command stops and tells you to run xmatix init in your repository root first.
  • The scripts capability (setup.automation.scripts.manage), the same one Setup → Process Studio → Scripts needs. Without it every command fails with HTTP 403.
  • The script must already exist in the product and have an active version, or pull skips it and run fails.

Procedure

Step 1 — Find the script

Run xmatix scripts list. Read the Disabled column (a disabled script doesn't run from its triggers) and the Current Version Id column: <none> means the script has never had a version activated, so there is nothing to pull or run.

Step 2 — Pull it

Run xmatix scripts pull <name> for one script, or xmatix scripts pull --all for every script. Running pull with neither prints "Specify a script name or pass --all." Each script written is listed as "Pulled xmatix/automation/scripts/<Entity>/<Name>.csx (vN)", and the command ends with "Done. pulled=N skipped=M". A script with no active version is listed as "Skipping <name> (no current version)". If no script has that name you see "No script matches <name>".

Pull overwrites existing files and never deletes any. Commit right after pulling so your next diff shows only your own changes.

Step 3 — Edit the body

Change the .csx file. Leave the .meta.json file alone — push reads the script id from it, and run reads the entity and phase from it. Keep the two files together: a .csx file without its .meta.json can't be pushed or run by file name.

Step 4 — Push a new version

Run xmatix scripts push xmatix/automation/scripts/Account/MyValidation.csx -m "<commit id>". The CLI prints "Pushing MyValidation.csx -> MyValidation" and then "Created vN (compileOk=yes)".

  • If the body doesn't compile, you see "compileOk=no", the compile errors, and exit code 3. Version N is saved with its errors but is not active. Fix the file and push again.
  • To make the new version live in the same step, add --activate. The result line ends with "[activated]". With --activate and a compile error, nothing is activated.

Step 5 — Activate (if you didn't in Step 4)

To activate a version you pushed without --activate, either push the same file again with --activate (this creates one more version and activates it), or open the script in the product and select Activate on version N in the Versions tab. Then run xmatix scripts pull <name> so your meta file points at the new active version — otherwise your next push stops with the conflict message.

Step 6 — Test it

Run xmatix scripts run <file-or-name> --record <record-id>. The CLI first prints the script, entity, phase or action, change kind and record it is about to use, then the server's answer as JSON: the script and version that ran, and one outcome per run with its run id, status (Succeeded, Failed or Cancelled), validation failures, error and duration. --action <name> tests a server-action binding instead of a lifecycle phase.

Step 7 — Watch the runs

Run xmatix runs tail --follow (or xmatix scripts logs --script <name> --follow). The CLI prints "Tailing runs (Ctrl+C to stop)…" and then a row for every new run: start time (UTC), status, phase, entity, duration, run id and the first 80 characters of the error. Run xmatix scripts logs --detail <run-id> to print everything recorded for one run, including its trace and record snapshots when they were kept.

Options

scripts pull

OptionEffect
<name>The script to pull, by exact name
--allPull every script in the tenant. Needed when you give no name

scripts push

OptionEffect
<file>The .csx file to upload. Its .meta.json must sit next to it
--activateMake the new version the active one, if it compiles. Without it, run-time behaviour doesn't change
-m, --message <text>A comment stored with the version, shown in the Comment column of the Versions tab. A commit id is a good habit
--forcePush even when the server's active version is no longer the one your meta file recorded

scripts run

OptionEffect
<file-or-name>A local .csx file (with its .meta.json) or a script name. A path that exists is read as a file; anything else is looked up as a name
-r, --record <guid>The record the script runs against. Needed for a lifecycle phase (see Business rules); optional for a server action
-e, --entity <name>The entity, for example Account. Defaults to the entity of the script's first binding
-p, --phase <phase>LoadDefaults, PreValidate, Validate, BeforeSave, BeforeDelete, AfterSave or AfterDelete. Defaults to the first binding's phase, then to BeforeSave
--action <name>Run as a server action with this name instead of a lifecycle phase. When set, --phase is ignored
--change-kind <kind>Insert, Update or Delete. Default Update

scripts logs and runs tail

OptionEffect
-s, --script <name-or-id>Only this script's runs
--status <status>Pending, Running, Succeeded, Failed or Cancelled
-n, --top <count>How many rows to show, 1–500. Default 20
-f, --followKeep polling and print only new runs. Ctrl+C stops it
--interval <seconds>Seconds between polls with --follow. Default 3, minimum 1
--detail <run-id>Print the full record of one run as JSON and stop
--jsonPrint rows as JSON instead of a table

Exit codes from scripts push

CodeMeaning
0Version created (or nothing to push)
1File not found, script no longer on the server, or the server refused the request
2The server's active version changed since your last pull
3Version created but it doesn't compile; not activated

Use these in a pipeline to fail a build on code 2 or 3.

File layout

Pull writes each script under xmatix/automation/scripts/, in a folder named after the entity of its first binding (bindings are taken in their Order, then by name). A script with no binding goes under _unbound/. Characters that can't appear in a file name are replaced with _.

xmatix/automation/scripts/Account/MyValidation.csx        # the body of the active version
xmatix/automation/scripts/Account/MyValidation.meta.json  # the script's settings

The .meta.json file holds the script's id, name, label, description and disabled flag; the active version's number, id and body fingerprint; the time of the last pull; and each binding's id, name, entity, type, phase, change kinds, order, run-asynchronously flag and disabled flag. Push and run read it but never send the bindings back — changing a binding in the meta file has no effect on the server.

If a script's first binding moves to another entity, the next pull writes it to the new entity folder and leaves the old pair behind. Delete the old pair yourself.

Business rules

  • A new version is numbered one higher than the script's highest existing version, whether or not that one compiled.
  • A version that doesn't compile is stored, but push --activate won't activate it. The Activate button on the Versions tab doesn't check this, so look at the Status column (Compiled or Errors) before activating a version there.
  • Only --activate (or activation in the product) changes what runs. The active version can't be deleted.
  • scripts run with a lifecycle phase and no --record runs nothing: the server answers with an empty list of outcomes. Pass a record id, or use --action for a server action that needs no record.
  • scripts run uses the active version at the moment you run it, not the file you have open. Push with --activate before testing a change.
  • When scripts run gets a script name together with --entity, it doesn't read the bindings, so the phase defaults to BeforeSave unless you pass --phase.
  • If the script has no active version, scripts run fails with "Script or active version not found."
  • Test runs are always kept in the run history with their trace and record snapshots. Live runs are kept as described in How xMatix handles it.

Example

A developer keeps a validation script, CreditHoldCheck, bound to the Order entity at BeforeSave.

  1. xmatix scripts pull CreditHoldCheck writes xmatix/automation/scripts/Order/CreditHoldCheck.csx (v4) and its meta file. They commit both.
  2. They change the message the script shows when an account is on hold and run xmatix scripts push xmatix/automation/scripts/Order/CreditHoldCheck.csx -m "hold message". The CLI prints "Created v5 (compileOk=yes)". Orders still run v4.
  3. They run xmatix scripts push xmatix/automation/scripts/Order/CreditHoldCheck.csx --activate -m "hold message". The CLI prints "Created v6 (compileOk=yes) [activated]" and the meta file now records v6.
  4. They run xmatix scripts run CreditHoldCheck --record <id of a test order for a held account>. The outcome shows status Cancelled with the new message in its validation failures — the script blocked the save, which is what it is for.
  5. In a second terminal, xmatix runs tail --script CreditHoldCheck --follow shows the test run, then each order a colleague saves that the script blocks.

Training

Practice exercise

In a sandbox tenant, create a script in Setup → Process Studio → Scripts with one binding on an entity you can test freely, and activate its first version. Then, from a workspace:

  1. Pull the script and confirm the .csx file sits under a folder named after the binding's entity.
  2. Add a deliberate syntax error and push. Expected result: "compileOk=no", the compile errors, exit code 3, and the script's active version unchanged in scripts list.
  3. Fix the error and push with --activate. Expected result: a version two higher than the one you pulled, marked "[activated]".
  4. Run the script against a test record. Expected result: one outcome with a run id.
  5. Run xmatix scripts logs --detail <that run id>. Expected result: the run's full record as JSON, including its trace and the record snapshot it saw.

Quick reference

  • push makes a version; only --activate makes it live.
  • A version with compile errors is saved but never active.
  • Pull again after activating in the product, or the next push stops with exit code 2.
  • run always tests the active version, and only that script.
  • Lifecycle tests need --record; with no record nothing runs.
  • Test runs and failures are always in the run history; successful live runs only with tracing on.
  • Names are exact and case-sensitive.
  • Bindings live in the product — editing them in .meta.json does nothing.

Permissions

TaskWhat you need
Every scripts and runs commandsetup.automation.scripts.manage
Turning on tracing for a user, so their successful runs are keptsetup.diagnostics.tracing.view (opens Tracing)

The CLI acts as the signed-in user. A user without the scripts capability gets HTTP 403 from every command; Setup hides the Scripts register from them too.

Configuration

  • Scripts, bindings and activation — Setup → Process Studio → Scripts. Create the script, add its bindings on the Bindings tab, switch a script or a single binding off, and activate or delete versions on the Versions tab.
  • Keeping successful runs — Setup → Platform → Monitoring → Tracing. Choose Start tracing, pick the user and a window of 15, 30 or 60 minutes. While the window is open, that user's successful live script runs are kept in the run history as well, with their trace and snapshots. Stop it early from the Active tracing windows table.
  • Auto-push on save — xmatix watch pushes every .csx file you save under xmatix/automation/scripts/ by running scripts push on it (never with --activate). Use --no-scripts to leave scripts out, and --debounce <ms> (default 400) to wait longer before pushing.

Common problems

"Server has a newer current version than your local copy."

Someone activated a different version since your last pull — in the product, from another workspace, or by activating your own unactivated push in the product. Pull the script, compare the result with your change in git, merge, and push again. Use --force only when you have decided your file should win.

My push said "Created vN" but nothing changed when I saved a record

You pushed without --activate, so the old version is still active. Push again with --activate, or activate version N in the product.

"compileOk=no" and exit code 3

The body doesn't compile. The version was still saved, with its errors, and is not active. Fix the errors the CLI printed (the script editor's Readme tab lists what the script API offers) and push again.

Every push creates a new version even when I changed nothing

Right after a pull, the "nothing to push" check can't recognise an unchanged file (see How xMatix handles it), and xmatix watch pushes on every save. The extra versions are harmless; they are not active unless you activate them.

scripts run returns an empty list of outcomes

You tested a lifecycle phase without --record, so the script had no record to run against. Pass --record <id> for an existing record, with --change-kind set to what you want to simulate.

"Could not infer entity name; pass --entity."

The script has no binding, so run can't tell which entity to use. Pass --entity <name>.

"No sibling .meta.json next to …"

You pushed or ran a .csx file that wasn't written by pull, or its meta file was moved or deleted. Pull the script again and copy your body into the new .csx file.

runs tail shows nothing although the trigger fires

Successful live runs are only kept while tracing is on for the user who triggered them. Start tracing for that user, or rely on the failures and test runs the feed always shows.

Common questions

What is a good editing loop?

Keep xmatix watch running in one terminal and xmatix runs tail --follow in another. Watch pushes each save as a new version and shows compile errors straight away, without making anything live. When the body compiles and you are ready, push it once with --activate and test it with scripts run against a test record; the tail shows the run as it lands. On a busy trigger, switch off the one binding in the product while you iterate and switch it back on when the fix is verified, so colleagues don't run half-finished versions.

Can I create a new script from the CLI?

No. Push needs the .meta.json file that only pull writes, and pull only finds scripts that exist on the server with an active version. Create the script in Setup → Process Studio → Scripts, write a first body that compiles and save it (saving a compiled body there makes it the active version), add its bindings, then pull it. From then on you can work from your editor.

Does scripts run save the record I point it at?

It doesn't save the record itself — it runs the script with that record as its input, outside a real save. But anything the script does is real: records it creates or updates are saved and messages it sends go out. A validation script that blocks shows Cancelled with its validation failures, and nothing else happens.

Where is the script API documented?

In the product: the Readme tab of the script editor is the full, versioned reference for everything a script can call, with transaction rules and sandbox limits. The scripts quickstart explains bindings, phases, testing and run history, and all of it applies to scripts edited through the CLI.