> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corti.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Commands

> Learn how to build custom dictation commands

A key functionality that brings an application from speech to text to a complete dictation solution is Commands. Commands give users control over their workflow, letting them insert templates, navigate the application, automate repetitive tasks and more.

<Card title="Feature availability:">
  <Columns cols={3}>
    <Card icon="circle-check" horizontal href="/stt/transcribe">
      /transcribe
    </Card>

    <Card icon="circle-x" horizontal href="/stt/streams">
      /streams
    </Card>

    <Card icon="circle-x" horizontal href="/stt/transcripts">
      /transcripts
    </Card>
  </Columns>

  <br />

  <Tip>
    See the [full API specification](/api-reference/transcribe#commands) for more information on how to define commands in your configuration.
  </Tip>
</Card>

***

## API Definitions

Commands are set in dictation configuration when directly calling the `/transcribe` API or when using the [Dictation Web Component](/sdk/dictation/overview).

Explanation of parameters used in `/transcribe` command configuration:

| Parameter | Type   | Definition                                                                                                                                                 |
| :-------- | :----- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id        | string | Identifier for the command when it is detected by the speech recognizer.                                                                                   |
| phrases   | array  | Word sequence that is spoken to trigger the command.                                                                                                       |
| variables | array  | Placeholder in command phrases to define one or more words that can trigger the command. Variables can be defined as an enum list or open-ended wildcards. |

***

## Commands with `enum` Variables

When configured commands are recognized by the server, the `command` object will be returned over the WebSocket (as opposed to the `transcript` object for speech recognized text), which includes the command `id` and `variables` that the integrating application uses to execute the defined action.

Below are some examples, including both the request configuration and server response.

### Constraints

* A single command should **not have a mixture of phrases with and without variables** - all phrases defined for a command including a variable must use the variable in the phrase.
* **All variables defined** for a command should be used in command phrases.
* **Client applications are responsible** for management and execution of the actions to be triggered by recognized commands.

### Enum Command Examples

<Info>Note that the `id`, `phrases`, and `variables` shown below are mere examples. Define these values as needed for your own commands.</Info>

<AccordionGroup>
  <Accordion title="Navigation">
    Command to navigate around your application (e.g., to a section within the current document template). The command includes a defined list of words that can be recognized for the `section_key` variable.

    <CodeGroup>
      ```js Command config: Navigation icon="square-code" expandable theme={null}
      {
        commands: [
          {
            id: "go_to_section",
            phrases: ["go to {section_key} section"],
            variables: [
              {
                key: "section_key",
                type: "enum",
                enum: ["subjective", "objective", "assessment", "plan", "next", "previous"]
              }
            ]
          }
        ]
      }
      ```

      ```json Server response theme={null}
      {
          "type": "command",
          "data": {
              "id": "go_to_section",
              "variables": {
                  "section_key": "assessment"
              },
              "rawTranscriptText": "Go to assessment section.",
              "start": 47.58,
              "end": 48.57
          }
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Select Text">
    Command to select text in the editor. The command includes a defined list of words that can be recognized for the `select_range` variable. Your application can define different select actions for each of the options, or add more complex handling for selecting specific words mentioned in the command utterance.

    <Tip>Pair this with a wildcard `select {text}` command</Tip>

    <CodeGroup>
      ```js Command config: Select text icon="square-code" expandable theme={null}
      {
        commands: [
          {
            id: "select_range",
            phrases: ["select {select_range}"],
            variables: [
              {
                key: "select_range",
                type: "enum",
                enum: ["all", "the last word", "the last sentence"]
              }
            ]
          }
        ]
      }
      ```

      ```json Server response theme={null}
      {
          "type": "command",
          "data": {
              "id": "select_range",
              "variables": {
                  "select_range": "all"
              },
              "rawTranscriptText": "Select all.",
              "start": 8.06,
              "end": 8.88
          }
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Delete Text">
    Command to delete text. The command includes a defined list of words that can be recognized for the `delete_range` variable. Your application can define different delete actions for each of the options.

    <CodeGroup>
      ```js Command config: Delete text icon="square-code" expandable theme={null}
      {
        commands: [
          {
            id: "delete_range",
            phrases: ["delete {delete_range}"],
            variables: [
              {
                key: "delete_range",
                type: "enum",
                enum: ["everything", "the last word", "the last sentence", "that"]
              }
            ]
          }
        ]
      }
      ```

      ```json Server response theme={null}
      {
          "type": "command",
          "data": {
              "id": "delete_range",
              "variables": {
                  "delete_range": "that"
              },
              "rawTranscriptText": "Delete that.",
              "start": 7.19,
              "end": 8.01
          }
      }
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

### Epic SmartPhrase

Command to insert an Epic SmartPhrase to pull in pre-defined text or structured data. See [Epic SmartPhrase tokens](/textgen/smartphrase-tokens) for more about this Epic feature.

<CodeGroup>
  ```js Command config: Epic SmartPhrase icon="square-code" expandable theme={null}
  {
    commands: [
      {
        id: "epic_smarts",
        phrases: ["dot {smartphrase}", "pull {smartphrase}", "insert {smartphrase}"],
        variables: [
          {
            key: "smartphrase",
            type: "enum",
            enum: ["vitals", "labs", "medications", "attestation"]
          }
        ]
      }
    ]
  }
  ```

  ```json Server response theme={null}
  {
      "type": "command",
      "data": {
          "id": "epic_smarts",
          "variables": {
              "smartphrase": "vitals"
          },
          "rawTranscriptText": "insert vitals",
          "start": 7.19,
          "end": 8.01
      }
  }
  ```
</CodeGroup>

### Putting It All Together

The three commands defined above can be combined into one dictation configuration with accompanying JavaScript to execute the actions.

<Accordion title="SDK Integration">
  The below example includes navigation commands to move between sections within the SOAP note template, delete text commands to remove the last word or sentence, and select text commands to select text within the active text field:

  <CodeGroup>
    ```js title="index.js" expandable theme={null}
    import { deleteLastSentence, deleteLastWord, getActiveTextarea, hideInterim, insertAtSelection, selectLastSentence, selectLastWord, setActiveTextarea, setCommandOutput, showInterim } from "./helpers.js";

    // Assumes you already have:
    // - dictation (the Dictation Web Component instance)
    // - textareas (array of editable fields)
    // - activeIndex (index of the currently active field)

    const ui = {
      interimTranscript: document.getElementById("interimTranscript"),
      commandOutput: document.getElementById("commandOutput"),
    };

    dictation.dictationConfig = {
      primaryLanguage: "en",
      interimResults: true,
      spokenPunctuation: true,
      automaticPunctuation: false,
      commands: [
        {
          id: "go_to_section",
          phrases: ["go to {section_key} section"],
          variables: [
            {
              key: "section_key",
              type: "enum",
              enum: ["subjective", "objective", "assessment", "plan", "next", "previous"],
            },
          ],
        },
        {
          id: "delete_range",
          phrases: ["delete {delete_range}"],
          variables: [
            {
              key: "delete_range",
              type: "enum",
              enum: ["everything", "the last word", "the last sentence", "that"],
            },
          ],
        },
        {
          id: "select_range",
          phrases: ["select {select_range}"],
          variables: [
            {
              key: "select_range",
              type: "enum",
              enum: ["all", "the last word", "the last sentence"],
            },
          ],
        },
      ],
    };

    dictation.addEventListener("transcript", (e) => {
      const { data } = e.detail;
      const textarea = getActiveTextarea({ textareas, activeIndex });

      if (data.isFinal) {
        insertAtSelection(textarea, `${data.text} `);
        hideInterim(ui);
        return;
      }

      showInterim(ui, data.rawTranscriptText);
    });

    dictation.addEventListener("command", (e) => {
      const { data } = e.detail;
      hideInterim(ui);

      if (data.id === "go_to_section") {
        const section = data.variables.section_key.toLowerCase();

        if (section === "next") {
          ({ activeIndex } = setActiveTextarea({ textareas, activeIndex }, activeIndex + 1));
          setCommandOutput(ui, `Command: ${data.id} (section: ${section})`);
          return;
        }

        if (section === "previous") {
          ({ activeIndex } = setActiveTextarea({ textareas, activeIndex }, activeIndex - 1));
          setCommandOutput(ui, `Command: ${data.id} (section: ${section})`);
          return;
        }

        const index = textareas.findIndex((el) => el.id.toLowerCase() === section);
        if (index !== -1) ({ activeIndex } = setActiveTextarea({ textareas, activeIndex }, index));

        setCommandOutput(ui, `Command: ${data.id} (section: ${section})`);
        return;
      }

      if (data.id === "delete_range") {
        const range = data.variables.delete_range.toLowerCase();
        const textarea = getActiveTextarea({ textareas, activeIndex });

        if (range === "everything") {
          textarea.value = "";
          setCommandOutput(ui, `Command: ${data.id} (range: ${range})`);
          return;
        }

        if (range === "the last word") {
          textarea.value = deleteLastWord(textarea.value);
          setCommandOutput(ui, `Command: ${data.id} (range: ${range})`);
          return;
        }

        if (range === "the last sentence") {
          textarea.value = deleteLastSentence(textarea.value);
          setCommandOutput(ui, `Command: ${data.id} (range: ${range})`);
          return;
        }

        if (range === "that") {
          const start = textarea.selectionStart ?? 0;
          const end = textarea.selectionEnd ?? 0;

          textarea.value =
            start !== end ? textarea.value.slice(0, start) + textarea.value.slice(end) : deleteLastWord(textarea.value);

          setCommandOutput(ui, `Command: ${data.id} (range: ${range})`);
          return;
        }
      }

      if (data.id === "select_range") {
        const range = data.variables.select_range.toLowerCase();
        const textarea = getActiveTextarea({ textareas, activeIndex });

        if (range === "all") {
          textarea.focus();
          textarea.setSelectionRange(0, textarea.value.length);
          setCommandOutput(ui, `Command: ${data.id} (range: ${range})`);
          return;
        }

        if (range === "the last word") {
          selectLastWord(textarea);
          setCommandOutput(ui, `Command: ${data.id} (range: ${range})`);
          return;
        }

        if (range === "the last sentence") {
          selectLastSentence(textarea);
          setCommandOutput(ui, `Command: ${data.id} (range: ${range})`);
          return;
        }
      }
    });
    ```

    ```js title="helpers.js" theme={null}
    export function getActiveTextarea(state) {
      return state.textareas[state.activeIndex];
    }

    export function setActiveTextarea(state, nextIndex) {
      const index = Math.max(0, Math.min(nextIndex, state.textareas.length - 1));
      state.textareas[index]?.focus();
      return { activeIndex: index };
    }

    export function hideInterim(ui) {
      ui.interimTranscript?.classList.add("hidden");
    }

    export function showInterim(ui, text) {
      if (!ui.interimTranscript) return;
      ui.interimTranscript.classList.remove("hidden");
      ui.interimTranscript.textContent = text;
    }

    export function setCommandOutput(ui, text) {
      if (!ui.commandOutput) return;
      ui.commandOutput.textContent = text;
    }

    export function insertAtSelection(textarea, text) {
      const start = textarea.selectionStart ?? 0;
      const end = textarea.selectionEnd ?? 0;

      textarea.value = textarea.value.slice(0, start) + text + textarea.value.slice(end);

      const cursor = start + text.length;
      textarea.setSelectionRange(cursor, cursor);
    }

    export function deleteLastWord(text) {
      const words = text.trim().split(/\s+/);
      words.pop();
      return words.length ? `${words.join(" ")} ` : "";
    }

    export function deleteLastSentence(text) {
      const sentences = text.match(/[^.!?]+[.!?]*\s*/g) ?? [];
      sentences.pop();
      return sentences.join("");
    }

    export function selectLastWord(textarea) {
      const value = textarea.value;
      const trimmed = value.trimEnd();
      const lastSpace = trimmed.lastIndexOf(" ");
      const start = lastSpace === -1 ? 0 : lastSpace + 1;
      textarea.focus();
      textarea.setSelectionRange(start, trimmed.length);
    }

    export function selectLastSentence(textarea) {
      const value = textarea.value;
      const sentences = value.match(/[^.!?]+[.!?]*\s*/g) ?? [];
      if (!sentences.length) return;

      const start = sentences.slice(0, -1).reduce((acc, s) => acc + s.length, 0);
      const end = start + sentences[sentences.length - 1].length;

      textarea.focus();
      textarea.setSelectionRange(start, end);
    }
    ```
  </CodeGroup>
</Accordion>

***

## Commands with Wildcard Variables

Unlike `enum` variables that are defined as part of the command configuration, `wildcard` variables provide the ability to recognize a command based on undefined, open-ended text.

### Constraints

When using `wildcard` variables, the following rules apply:

#### Configuration

* **Trigger word required**: A wildcard variable must be preceded by at least one literal word. Phrases may not begin with a wildcard variable.
* **Separated by literals**: Multiple wildcard variables in a single phrase must be separated by a non-empty literal string (e.g. `"select {text1} end select {text2}"` is valid; `"{text1} {text2}"` is not).
* **No `enum` field**: Wildcard variables do not use an `enum` list — omit the `enum` field when `type` is `"wildcard"`.

#### Recognition

* A literal **trigger word is required** before defining a wildcard variable, such as "select" or "insert before".
* **1.5 seconds of silence** must precede and follow the command phrase.
* Wildcard variables can support a **maximum of 10 words** to be recognized following the literal trigger text. When the word cap is reached without a following literal endpoint, the command fires automatically with the accumulated words as the variable value.
* Commands with **wildcard variables are recognized *after* commands with enumerated variables**, so that defined matches are recognized first when there are overlapping phrase terms defined.

<Note>The 2 second silence buffer and max word limit constraints are server-side defaults. Review and refinement of these values are in progress. [Contact us](mailto:help@corti.ai) to share details about your implementation, or for more information.</Note>

### Wildcard Command Examples

<AccordionGroup>
  <Accordion title="Literal endpoint: select {utterance} end select">
    Adding a literal word at the end of the phrase creates an endpoint — the wildcard captures everything spoken between the trigger and the endpoint.

    <CodeGroup>
      ```js Command config icon="square-code" expandable theme={null}
      {
        commands: [
          {
            id: "select_text",
            phrases: ["select {utterance} end select"],
            variables: [
              {
                key: "utterance",
                type: "wildcard"
              }
            ]
          }
        ]
      }
      ```

      ```json Server response theme={null}
      {
          "type": "command",
          "data": {
              "id": "select_text",
              "variables": {
                  "utterance": "forty year old male"
              },
              "rawTranscriptText": "select forty year old male end select",
              "start": 1.20,
              "end": 3.80
          }
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Multiple wildcards: insert {content} before {target}">
    Two wildcard variables in a single phrase must be separated by a non-empty literal string — here `"before"` acts as the separator.

    <CodeGroup>
      ```js Command config icon="square-code" expandable theme={null}
      {
        commands: [
          {
            id: "insert_before_after",
            phrases: ["insert {content} before {target}"],
            variables: [
              {
                key: "content",
                type: "wildcard"
              },
              {
                key: "target",
                type: "wildcard"
              }
            ]
          }
        ]
      }
      ```

      ```json Server response theme={null}
      {
          "type": "command",
          "data": {
              "id": "insert_before_after",
              "variables": {
                  "content": "new patient details",
                  "target": "assessment section"
              },
              "rawTranscriptText": "insert new patient details before assessment section",
              "start": 2.10,
              "end": 5.40
          }
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Mixed wildcard and enum: select {text} and {operation}">
    Wildcard and `enum` variables can be combined in a single phrase. Enum variables are always matched first, so the wildcard captures whatever is spoken between the trigger and the recognized enum value.

    <CodeGroup>
      ```js Command config icon="square-code" expandable theme={null}
      {
        commands: [
          {
            id: "select_and_edit",
            phrases: ["select {text} and {operation}"],
            variables: [
              {
                key: "text",
                type: "wildcard"
              },
              {
                key: "operation",
                type: "enum",
                enum: ["overwrite", "delete", "replace"]
              }
            ]
          }
        ]
      }
      ```

      ```json Server response theme={null}
      {
          "type": "command",
          "data": {
              "id": "select_and_edit",
              "variables": {
                  "text": "forty year old male",
                  "operation": "overwrite"
              },
              "rawTranscriptText": "select forty year old male and overwrite",
              "start": 1.50,
              "end": 4.20
          }
      }
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

### Putting It All Together

Using the `select_text` command to replace text existing in the document:

<Accordion title="Integrated Workflow Sequence">
  <Steps titleSize="h3">
    <Step title="Command configuration" icon="braces">
      ```js Command config: select_text icon="square-code" highlight={8-9} theme={null}
      {
        commands: [
          {
            id: "select_text",
            phrases: ["select {utterance}"],
            variables: [
              {
                key: "utterance",
                type: "wildcard"
              }
            ]
          }
        ]
      }
      ```
    </Step>

    <Step title="Speech" icon="audio-lines">
      `Dictation`: "The patient is a forty year old male comma here today for annual well visit period he is known to have diabetes and hypertension and his last fasting A1c was six point eight percent period"
    </Step>

    <Step title="STT response" icon="braces">
      `Transcript`: "The patient is a 40-year-old male, here today for annual well visit. He is known to have diabetes and hypertension and his last fasting A1c was 6.8%."
    </Step>

    <Step title="Speech" icon="audio-lines">
      `Dictation`: "select male"
    </Step>

    <Step title="STT response" icon="braces">
      ```json Server response: select_text command icon="square-code" highlight={5} theme={null}
      {
          "type": "command",
          "data": {
              "id": "select_text",
              "variables": {"utterance": "male"},
              "rawTranscriptText": "select male",
              "start": 7.19,
              "end": 8.01
          }
      }
      ```
    </Step>

    <Step title="Client app/UI" icon="text-cursor">
      `Highlight`: "male"
    </Step>

    <Step title="Speech" icon="audio-lines">
      `Dictation`: "female"
    </Step>

    <Step title="STT response" icon="braces">
      `Transcript`: "female"
    </Step>

    <Step title="Client app/UI" icon="text-cursor">
      `Insert (overwrite) text`: ~~male~~ female
    </Step>
  </Steps>

  <br />
</Accordion>

***

## Additional Information

### Best Practices

See [command best practices](/stt/best-practices-commands) for how to build commands effectively.

### More Examples

Browse the [command configurations library](https://github.com/corticph/corti-examples/tree/main/dictation/commands) for ready-to-use examples.

<br />

<Note>
  Use of commands is **optional** for dictation configuration. They should be configured based on how the integrating application will perform the actions.

  Please [contact us](mailto:help@corti.ai) to report errors, or for more information on this feature.
</Note>
