Every button in a TSQL.APP application runs an action script: plain T-SQL, stored as a row in
the application's metadata. There is no front-end code to write. The script describes what the
user should see by calling sp_api_* procedures, and the framework draws it.
One line
The smallest useful script shows a message and waits for the user to close it:
DECLARE @AlertText NVARCHAR(MAX) = N'Hello World';
EXEC sp_api_modal_alert @alert_text = @AlertText;
Add this as the SQL of a new action on any card, click the button, and an alert appears.
Notice that the text is put in a variable first, rather than passed as a literal expression. That
is a habit worth keeping from the start: procedure parameters take values, not calculations, so
anything you compute — a CONCAT, a FORMAT, a lookup — goes into a variable on the line before.
A form that answers back
The real shape of TSQL.APP code shows up as soon as the user types something. This script asks for a name and greets the user by it:
-- 1. Declare everything at the top
DECLARE @Name NVARCHAR(MAX);
DECLARE @GreetButton NVARCHAR(MAX);
DECLARE @Greeting NVARCHAR(MAX);
-- 2. Read what the user has entered so far
EXEC sp_api_modal_get_value @name = N'@Name', @value = @Name OUT;
EXEC sp_api_modal_get_value @name = N'@GreetButton', @value = @GreetButton OUT;
-- 3. Draw the dialog
EXEC sp_api_modal_text @text = N'Hello World', @class = N'h3';
EXEC sp_api_modal_input @name = N'@Name', @value = @Name OUT, @placeholder = N'Your name';
EXEC sp_api_modal_button @name = N'@GreetButton', @value = N'Greet',
@valueout = @GreetButton OUT, @class = N'btn-primary';
-- 4. Nothing clicked yet: stop here and wait
IF @GreetButton IS NULL RETURN;
-- 5. Handle the click
IF LEN(TRIM(ISNULL(@Name, N''))) = 0
BEGIN
SET @Greeting = N'Please enter your name first.';
EXEC sp_api_toast @text = @Greeting, @class = N'bg-danger text-white';
EXEC sp_api_modal_value @name = N'@GreetButton', @value = NULL; -- un-click the button
RETURN;
END
SET @Greeting = CONCAT(N'Hello, ', @Name, N'!');
EXEC sp_api_toast @text = @Greeting, @class = N'bg-success text-white';
EXEC sp_api_modal_clear;
What just happened
The script ran twice. The first time, nothing had been entered, so @GreetButton was NULL:
the dialog was drawn and the script stopped at step 4. When the user clicked Greet, the browser
sent the values back and the framework ran the whole script again from the top. This time
sp_api_modal_get_value found a name and a clicked button, so step 5 ran.
That is the entire programming model of a dialog, and every guide here follows the same five steps:
- Declare all variables at the top.
- Synchronise state with
sp_api_modal_get_valuebefore drawing anything. - Draw the controls — every run, not only the first.
- Pause with
RETURNwhile there is nothing to do. - Handle the action, and close the dialog with
sp_api_modal_clearwhen finished.
Two details in step 5 matter more than they look:
- Reset the button after a validation error. Without
sp_api_modal_value ... NULL, the button stays "clicked", and the next run would handle it again before the user has corrected anything. - Each control gets its own variable.
@nameis the control's identity on the page, and theOUTvariable is where its current value lands. Reusing one variable for two controls mixes their values up.
The script being re-run from the top is not an implementation detail you can ignore — it is what makes a stateless web request feel like a conversation. The Inner Workings course explains the mechanism in full.