input and must return one or more Firmhouse actions for the Journey to execute.
Script Execution is currently a private beta feature. During the beta, Firmhouse staff needs to enable it for your project.
Note: Contact Firmhouse Support to enable this feature for your project. This feature is only available for Advanced or higher tier merchants.
What it is
Script Execution is useful when you need custom Journey logic that is not covered by the standard workflow presets. An outgoing webhook starts the workflow when its selected event happens. Instead of directly changing a subscription from the script, your script decides which supported action Firmhouse should execute next. The script:- must contain valid JavaScript
- receives the subscription and webhook trigger context as
input - must
returnan array of action objects - cannot perform direct side effects in Firmhouse by itself
Add a Script Execution workflow
- Ask the Firmhouse support team to enable the Script Execution beta for your project.
- Open your project in the Firmhouse portal.
- In the sidebar, go to Workflows.
- Click Create new workflow.
- Choose Script Execution.
- Enter a name for the workflow and optionally add a description.
- Paste your JavaScript into the script editor.
- Optionally, add Input Parameters (see below).
- Click Save workflow.
- In the sidebar, go to Apps.
- Find the Webhooks app and click Configure.
- Click New outgoing webhook.
- Set Content type to Script Execution.
- Select the Script Execution workflow you created.
- Select the Event that should run the script.
- Optionally, add a JSON Template. The rendered template is passed to the script as
input.trigger.payload. - Make sure the webhook is enabled and click Save.
Input Parameters
Input Parameters let you define key/value pairs that become available in your script. This lets you write a single reusable script and configure it differently per workflow — without editing JavaScript. Your script receives the parameters in two forms:input.params— the raw array of parameter objects exactly as configured, e.g.[{"key": "order_1", "type": "product", "value": "gid://..."}, ...]. Use this when you need access to the full metadata (type, individual entries).input.params_normalized— a convenience hash that groups values by key, e.g.{"order_1": "gid://...", "order_2": ["gid://...", "gid://..."]}. Duplicate keys are automatically collected into arrays. Use this for simple key-based lookups.
Parameter types
Each parameter has a Type selector:- Text (default) — Enter any plain text value. The value is always passed to your script as a string. If you need array-like behaviour, use a comma-separated or pipe-separated value and split it in your script (e.g.
"a,b,c".split(",")). - Product — Select a product from your catalog using a search autocomplete field. This makes it easy to deterministically reference a specific product in your input parameters without needing to look up its ID. The selected product’s Global ID is stored and passed to your script as a string (e.g.
"gid://firmhouse/Product/123").
order_2 and type Product will appear as two separate entries in input.params. In input.params_normalized, they are grouped into an array: input.params_normalized.order_2 → ["gid://firmhouse/Product/101", "gid://firmhouse/Product/102"].
The script protocol
Your script must return an array. Each item in the array must be an object with:functionarguments
Rules
- The script must use valid JavaScript syntax.
- The script must
return [...], not{ actions: [...] }. - The returned value must be an array.
- Each action must be an object.
functionmust be one of the supported function names listed below.argumentsmust be an object.- Required arguments must be present.
- Extra arguments are rejected.
- Argument types must match the expected types.
Copy-paste examples
No-op / smoke test
Use this script to verify that the workflow executes successfully without changing the subscription.Product swap example
Use this script as a starting point for a workflow that supports multiple source-to-target product mappings and returns one swap action for each matching current product.Promotion by confirmed orders count
Use this script when you want to apply different promotions after specific order milestones. In this example, one promotion is applied after 3 confirmed orders and another after 6 confirmed orders.Add a free one-off product on a configured order
Use this script when you want to add a free product to a specific upcoming order. The product is added as a one-off add-on, so it is included in that order only. For example, to ship a loyalty reward with order 3, configure the workflow to add it on order 3. Script Execution runs after an order is confirmed, so the action is returned after order 2 is confirmed and prepares the subscription for order 3. Configure the Input Parameters like this:Product sequence with revolving cycle
Some subscription businesses ship different products depending on where the subscriber is in their journey. For example, a health supplements brand might send a starter kit on the first order, then alternate between two different refill packs from order 2 onward. After the first checkout order and possible follow-up orders, you can configure a revolving cycle that spans multiple orders. This script uses Input Parameters to configure which products belong to each order in the sequence. You do not need to edit the JavaScript — just set the parameters in the workflow form.Real-world example
Imagine a supplements brand that works like this:- The customer checks out with a Starter Kit (order 1).
- On order 2, the subscription switches to two refill products: Refill Pack A and Refill Pack B.
- On order 3, the subscription has only Refill Pack A.
- From order 4 onward, orders 2 and 3 keep repeating: the customer alternates between receiving both refill packs and just one.
The resulting order sequence:
- Order 1 → Starter Kit (checkout order — already on subscription at signup)
- Order 2 → Refill Pack A + Refill Pack B (removes Starter Kit, adds both refills)
- Order 3 → Refill Pack A (removes Refill Pack B, keeps Refill Pack A)
- Order 4 → Refill Pack A + Refill Pack B (cycles back to order 2)
- Order 5 → Refill Pack A (cycles back to order 3)
- …and so on
How the Input Parameters work
Each key follows theorder_X format, where X is the order number. Set the type to Product and search for the product you want.
To add multiple products to the same order, add multiple rows with the same key (e.g. two rows both named order_2, each with a different product). The script collects all products for that order automatically.
order_1 represents the checkout order — the products the customer receives at signup. These are already on the subscription, so the script does not add them. It uses order_1 to know which products to remove when moving to order_2.
Add a key named restart_from_order_X (e.g. restart_from_order_2) with any value (e.g. true) to set where the revolving cycle begins. Once all configured orders have been fulfilled, the sequence loops back to this order number and repeats indefinitely.
The script
Paste this script into the Script Execution workflow. It reads the Input Parameters and handles add/remove automatically.Supported functions
no_action
Use this when the Journey should continue without making any subscription change right now.
Required arguments:
reason(string)
swap_product_by_id
Use this to replace one product on the subscription with another product.
Required arguments:
current_product_id(string)new_product_id(string)reason(string)
firmhouse namespace in these Global IDs, for example gid://firmhouse/Product/123. Do not use gid://gomonthly/..., because those IDs will not work in Script Execution workflows.
Example:
update_next_billing_date
Use this to move the subscription’s next billing date.
Required arguments:
next_billing_date(string, ISO date formatYYYY-MM-DD)reason(string)
update_plan_commitment_dates
Use this to update the subscription plan’s commitment dates. This is useful when a workflow needs to set, extend, or clear the plan’s minimum commitment, maximum commitment, or grace cancellation dates.
Required arguments:
reason(string)
minimum_commitment_ends_at(string, ISO date or timestamp)maximum_commitment_ends_at(string, ISO date or timestamp)grace_cancellation_ends_at(string, ISO date or timestamp)
apply_promotion
Use this to apply a promotion to the subscription.
Required arguments:
promotion_id(string)reason(string)
add_product_to_subscription
Use this to add a product to the subscription.
Required arguments:
product_id(string) — Firmhouse product Global IDreason(string)
quantity(string) — defaults to"1"if not provided
add_gift_to_subscription
Use this to add a free one-off product to the subscription. The product is added for the next order only and is removed from the subscription after that order is created.
Required arguments:
product_id(string) — Firmhouse product Global IDreason(string)
quantity(string) — defaults to"1"if not provided
add_one_off_product_to_subscription
Use this to add a one-off product to the subscription with an optional custom price. The product is added for the next order only and is removed from the subscription after that order is created.
Required arguments:
product_id(string) — Firmhouse product Global IDreason(string)
quantity(string) — defaults to"1"if not providedcustom_price_cents(stringornumber) — custom price in cents. Use0for a free product.skip_billing_product_replacement(boolean) — set totruewhen the subscription’s plan uses a Shopify billing product, but this one-off product should use its own Shopify variant instead of the billing product variant.
remove_product_from_subscription
Use this to remove a product from the subscription.
Required arguments:
product_id(string) — Firmhouse product Global IDreason(string)
update_ordered_product_metadata
Use this to replace the metadata object on an active ordered product. This is useful when a workflow needs to store data on the subscribed item itself, such as fulfillment metadata, external references, or configuration copied from an order event.
Required arguments:
ordered_product_id(stringorinteger) — Firmhouse ordered product ID or Firmhouse ordered product Global IDmetadata(object) — metadata to save on the ordered productreason(string)
ordered_product_metadata.
confirm_order
Use this to confirm an order that is awaiting confirmation. This is useful when a Script Execution workflow checks the order and decides it can continue.
Required arguments:
order_id(stringorinteger) — Firmhouse order ID or Firmhouse order Global ID for the awaiting confirmation orderreason(string)
skip_order
Use this to skip an order that is awaiting confirmation.
Required arguments:
order_id(stringorinteger) — Firmhouse order ID or Firmhouse order Global ID for the awaiting confirmation orderreason(string)
schedule_order
Use this to move a confirmed order back to the scheduled state with a future shipment date. For example, an Order confirmed outgoing webhook can trigger a Script Execution workflow that delays fulfillment until a date chosen by your business rules.
Required arguments:
order_id(stringorinteger) — Firmhouse order ID or Firmhouse order Global ID for a confirmed order on the current subscriptionshipment_date(string) — future shipment date inYYYY-MM-DDformatreason(string)
shipment_date:
add_order_line
Use this to add a product line to an order that is awaiting confirmation.
Required arguments:
order_id(stringorinteger) — Firmhouse order ID or Firmhouse order Global ID for the awaiting confirmation orderproduct_id(stringorinteger) — Firmhouse product ID or Firmhouse product Global IDreason(string)
quantity(stringorinteger) — defaults to1if not provided. Must be1or greater.price_cents(stringorinteger) — optional unit price in cents. Defaults to the product price when not provided. Use0for a free line.
remove_order_line
Use this to remove a product line from an order that is awaiting confirmation. The line is selected by the ordered product ID behind that order line.
Required arguments:
order_id(stringorinteger) — Firmhouse order ID or Firmhouse order Global ID for the awaiting confirmation orderordered_product_id(stringorinteger) — Firmhouse ordered product ID or Firmhouse ordered product Global ID for the order line to removereason(string)
replace_order_line
Use this to replace a product line on an order that is awaiting confirmation. This adds the replacement line before removing the original line, so the order is not left temporarily empty.
Required arguments:
order_id(stringorinteger) — Firmhouse order ID or Firmhouse order Global ID for the awaiting confirmation orderordered_product_id(stringorinteger) — Firmhouse ordered product ID or Firmhouse ordered product Global ID for the order line to replacereplacement_product_id(stringorinteger) — Firmhouse product ID or Firmhouse product Global ID for the replacement productreason(string)
quantity(stringorinteger) — defaults to1if not provided. Must be1or greater.price_cents(stringorinteger) — optional unit price in cents. Defaults to the replacement product price when not provided. Use0for a free line.
stop_journey
Use this when the Journey has reached its end and should not continue for this subscription.
Required arguments:
stop_journey(boolean)reason(string)
What input contains
Firmhouse passes the current subscription and outgoing webhook context into your script as input. Most subscription fields follow a fixed structure. The webhook trigger context tells your script which event started the run and includes the rendered webhook payload.
The current input payload contains:
signed_up_atconfirmed_orders_countconfirmed_or_fulfilled_orderscurrent_productscurrent_promotionsactive_plantrigger— outgoing webhook context for the event that started the script.input.trigger.payloadcontains the rendered webhook template, so its fields depend on what you include in that template.params— the raw array of Input Parameter objects you configured on the workflow (empty array[]when none are set). Each entry haskey,type, andvalue.params_normalized— a convenience hash that groupsparamsby key. Duplicate keys become arrays. Empty object{}when no parameters are set.
Field details
signed_up_atThe subscription sign-up timestamp.confirmed_orders_countThe number of confirmed or fulfilled orders on the subscription.confirmed_or_fulfilled_ordersThe confirmed or fulfilled orders, including their order lines.current_productsThe currently active products on the subscription, including instalment-related fields:product_id,instalment_original_product_id, andinstalment_current_number.current_promotionsThe promotions currently applied to the subscription.active_planSelected plan details for the subscription, including bothsubscribed_planattributes and fullplanattributes.triggerOutgoing webhook context for the event that started the script. This includessource,event,outgoing_webhook_id, andpayload. If the event is related to a specific record, it can also includereferenceable_gid. The payload shape depends on the webhook template. The order pre-confirmation examples on this page assume your template includes anorder_idfield, which is then available asinput.trigger.payload.order_id.
input object in your script and use the fields you need in your logic.
input.params contains the raw array of parameter objects you defined in the Input Parameters section of the workflow form. input.params_normalized provides the same data grouped by key for convenient lookups. See Input Parameters for details on how to configure them.
Troubleshooting
If the script does not save or does not run correctly, check the following:- the script contains valid JavaScript
- the script returns an array
- every action includes
functionandarguments - all required arguments are present
- there are no extra unsupported arguments
- the argument types match the required types
- your project ID
- the name of the workflow
- the script you saved
- the action you expected the script to return