Skip to main content

Customize the order ticket

Configured order ticket with broker routing fields and calculated account impact
The broker declares field meaning and validation; the host controls layout and presentation.

TradeScript order tickets separate execution semantics from presentation. The broker declares order rules and fieldLayout. The SDK owns draft state, validation, standard controls, and submission. The host applies theme, wording, and React presentation extensions.

Minimal field layout

This layout writes one route value to TradingOrderDraft.customFields.route after the quantity field.

ticketFields.ts
import type { TradingOrderTicketFieldLayout } from '@tradescript/pro/sdk';

export const fieldLayout = {
sections: [{
id: 'routing',
title: 'Routing',
placement: 'after-quantity',
fields: [{
id: 'route',
kind: 'select',
label: 'Route',
binding: { target: 'customFields', field: 'route' },
defaultValue: 'SMART',
options: [
{ value: 'SMART', label: 'SMART' },
{ value: 'ARCA', label: 'ARCA' },
],
}],
}],
} satisfies TradingOrderTicketFieldLayout;

Return it from getTradingSymbolInfo().orderTicketSettings.fieldLayout, or pass it through the ticket's host options. The placed draft carries the exact custom-field key and selected value.

Field kinds and bindings

KindControlTypical use
textText inputClient tag or free-form broker value
numberNumeric inputNumeric broker parameter
pricePrice inputBroker-owned price field
quantityQuantity inputDisplay or reserve quantity
selectDropdownRoute or execution algorithm
segmentedVisible button groupSmall mutually exclusive choice
checkboxCheckboxBoolean order flag
readonlyCalculated displayTrade value, fees, cash, margin, or leverage

Editable fields bind either to a supported draft property or to one explicit customFields key. The schema is serializable and does not accept React renderers.

type TradingOrderTicketFieldBinding =
| {
target: 'draft';
field: 'quantity' | 'price' | 'stopPrice' | 'trailPercent'
| 'relativeOffset' | 'duration' | 'postOnly';
}
| { target: 'customFields'; field: string };

Sections and conditions

Sections are ordered and mount at after-order-type, after-quantity, before-exits, after-exits, or before-submit. Use one to three columns.

Fields can declare visibleWhen, enabledWhen, requiredWhen, min, max, and step:

import type { TradingOrderTicketFieldDefinition } from '@tradescript/pro/sdk';

const displayQuantity = {
id: 'display-quantity',
kind: 'quantity',
label: 'Display quantity',
binding: { target: 'customFields', field: 'displayQuantity' },
min: 1,
step: 1,
visibleWhen: {
source: 'customField',
field: 'route',
equals: 'SMART',
},
enabledWhen: {
source: 'orderType',
oneOf: ['limit', 'iceberg'],
},
disabledReason: 'Display quantity requires a limit or iceberg order.',
} satisfies TradingOrderTicketFieldDefinition;

Conditions can use order type, side, symbol type, position presence, account margin support, and exact custom-field values. They compose with all, any, and not. Declared constraints run before preview or placement.

Calculated rows

readonly fields display SDK-calculated values without copying calculation logic into the host:

import type { TradingOrderTicketReadonlyField } from '@tradescript/pro/sdk';

export const projectedCash = {
id: 'projected-cash',
kind: 'readonly',
label: 'Cash after',
source: 'cashAfter',
format: 'money',
} satisfies TradingOrderTicketReadonlyField;

Sources include prices, quantity, trade value, commission, cash, buying power, margin, tick value, and leverage. Missing facts render as -, not zero.

Canonical and broker-native order types

Keep normalized meaning separate from provider identity:

import type { TradingOrderTypeRule } from '@tradescript/pro/sdk';

const stopMarket: TradingOrderTypeRule = {
type: 'stop',
brokerOrderTypeId: 'STP-MKT',
label: 'Stop-MKT',
requiresStopPrice: true,
};

TradingOrderDraft.type remains the canonical type used by SDK validation and risk. brokerOrderTypeId travels beside it for adapter mapping. Use disabledReason when a known type is temporarily unavailable.

Configuration precedence

ContractLowest to highest precedence
CapabilitiesAccount, then exact symbol rules
Ticket settingsAccount, symbol, host options, allowed user preferences
fieldLayoutAccount, symbol, host options
Legacy custom fieldsAccount legacy/settings, symbol legacy/settings, host options.customFields

Persisted user settings can change only the typed preference allowlist. They cannot replace order rules, provider ids, or broker-owned field semantics.

These settings merge only at the top level. fieldLayout is a whole-value, shallow replacement: a symbol layout replaces the account layout, and a host layout replaces both. Sections and fields from lower-precedence layouts are not deep-merged. Put the complete intended layout at the winning level.

User settings and appearance

The ticket's built-in settings modal persists only the typed user-setting allowlist. Implement getOrderTicketSettings and setOrderTicketSettings on the trading adapter to load and save those values in the selected account and exact-symbol scope.

TradingTicketAppearanceSettings currently exposes the margin meter colors:

import type { TradingTicketAppearanceSettings } from '@tradescript/pro/sdk';

const appearance: TradingTicketAppearanceSettings = {
marginAvailableColor: '#22c55e',
marginMeterTrackColor: '#172033',
};

Pass the same serializable contract through the ticket's options.appearance when the host supplies a default. Persisted user settings take precedence over that default. Use theme and slotStyles for host-owned presentation that must not be stored as a user preference.

Return symbol-specific configuration from the adapter:

import type {
TradingOrderContext,
TradingOrderTicketFieldLayout,
TradingSymbolInfo,
} from '@tradescript/pro/sdk';

declare const fieldLayout: TradingOrderTicketFieldLayout;

async function getTradingSymbolInfo(
context: TradingOrderContext,
): Promise<TradingSymbolInfo> {
return {
symbol: context.symbol,
accountId: context.accountId,
supportedOrderRules: [
{ type: 'market', brokerOrderTypeId: 'MKT', label: 'Market' },
{ type: 'limit', brokerOrderTypeId: 'LMT', label: 'Limit', requiresLimitPrice: true },
],
supportedDurations: [
{ type: 'day', label: 'Day' },
{ type: 'gtc', label: 'GTC' },
],
orderTicketSettings: { fieldLayout },
};
}

Host presentation

Use semantic configuration for broker facts and React props for appearance:

NeedSurface
Theme and densitytheme, className, style
Built-in wordinglabels
Direction layoutdirectionSelector
Impact rowsorderInfo.items
One control styleslotClassNames, slotStyles
Complete region markuprenderDirectionSelector, renderOrderInfo
import type {
SdkSymbolInfo,
TradeScriptSdkProducts,
TradingControllerApi,
TradingOrderTicketFieldLayout,
} from '@tradescript/pro/sdk';
import { TradingOrderTicket } from '@tradescript/pro/react/widgets/order-ticket';

declare const sdk: TradeScriptSdkProducts;
declare const trading: TradingControllerApi;
declare const symbol: SdkSymbolInfo;
declare const fieldLayout: TradingOrderTicketFieldLayout;

<TradingOrderTicket
sdk={sdk}
controller={trading}
symbol={symbol}
currentPrice={214.4}
options={{ fieldLayout }}
labels={{ tradeValue: 'Notional', commission: 'Fees' }}
slotStyles={{ submitButton: { minHeight: 44, fontWeight: 700 } }}
/>

Import TradingOrderTicketProps and the field-layout types instead of copying slot or schema inventories into application code.

Migrate legacy fields

Legacy customFields remain supported. Move a field to fieldLayout when it needs section placement, numeric constraints, conditions, segmented controls, or calculated rows. Keep the same custom-field binding key and never declare one destination in both systems.

Verify the integration

  • Every field appears only under its declared condition.
  • The winning fieldLayout contains every intended section; no lower-level sections leak into it.
  • Local constraints block preview and placement with a useful message.
  • The placed draft contains exact canonical and broker-native order identities.
  • User preferences cannot override broker rules or field meaning.
  • Missing calculated values remain unavailable instead of becoming zero.

Next steps