Files
gridcraft/docs/macros.md

28 KiB
Raw Permalink Blame History

Macros

Last updated: 2026-11-02 · Change: minor (form buttons run macros, macro.run takes output, and a button action kept in a hidden name comes back on reopen only while the file still agrees with it; earlier: a close that waits on the step editor's question runs in the editor's window, and a further close asked meanwhile waits for the answer or is refused; earlier: closing a workbook or quitting asks about the step editor's unsaved draft first; recording macros and the management commands; Record Macro takes a name, shortcut and description; the macro interface: Automate › Macros, the Macros and Record Macro dialogs, the step editor, shortcuts)

GridCraft macros are typed, declarative lists of commands, not VBA. Every step runs a GridCraft command (the same ids the ribbon, CLI and MCP use) or one of three bounded control steps. A macro declares the inputs it works on, runs as one undo step, rolls all workbook changes back on an error (a file it already wrote stays), checks where its results will land before it writes them, and does not touch files without permission. Agents run macros with macro.run like any other command.

This page describes the engine, its commands and the macro interface in the window.

A macro

{
  "name": "Totals",
  "description": "Adds a total next to the amounts",
  "inputs": [{"name": "Input", "header": true, "cols": [2, 2], "colTypes": ["text", "number"]}],
  "output": {"anchor": "Input.right(2).cell(1,2)", "onConflict": "ask"},
  "steps": [
    {"type": "command", "id": "cell.set", "params": {"cell": "Output", "input": "Total"}},
    {"type": "command", "id": "cell.set", "params": {"cell": "Output.offset(1,0)", "input": "=SUM({{Input.data.col(2)}})"}},
    {"type": "repeat", "over": "Input.data", "of": "rows", "max": 1000, "body": [
      {"type": "if", "cell": "Row.cell(1,2)", "op": "gt", "value": 100,
       "then": [{"type": "command", "id": "home.bold", "params": {"range": "Row"}}]}
    ]},
    {"type": "stop", "message": "Done."}
  ],
  "permissions": []
}
  • Steps: command (id, params), repeat (each row or column of over, at most max times), if (cell compared with value by eq, ne, gt, ge, lt, le, empty, notEmpty or contains; then and else lists) and stop (message; rollback: true undoes the run). Every step can carry enabled, breakpoint and note. The short form {"command": "cell.set", "params": {…}} is accepted for a command step.
  • Inputs: kind (cell or range), rows and cols as [min, max] (0 for no maximum; rows exclude the header), header, colTypes (number, text, date, bool, any), nonEmpty and defaultRef. These declare what a range must look like for the macro to work on it. An input's name is letters, digits and _, starting with a letter, and is neither a token of its own (Output, Row, Column, Name) nor something that reads as a cell reference (Q1), so a step written with plain A1 text keeps meaning the cell. The declaration is checked as a whole: neither rows nor cols may have a least above its most, a cell input has one row and one column ([1, 1], or the default [1, 0]), no header and at most one column type, and no input lists more column types than it allows columns. A run binds each input to the range it is given, its default, or (for the first input) the selection, and refuses with messages that name the cells: "Input column 2 (Amount) must hold numbers; C4 holds text."
  • Limits: 1,000 macros a workbook, 10,000 steps a macro, Repeat and If nested 4 deep, and 64 inputs a macro. Names (of macros and inputs) and command ids are at most 255 characters; descriptions, step notes, Stop messages and If values (as JSON) 4,096; references and tokens (output.anchor, defaultRef, a Repeat's over, an If's cell) 1,024; a shortcut 64. A macro lists at most 256 permissions, each a command id, and an input at most 16,384 column types. One-line fields hold no line breaks or other control characters, and a command id is letters, digits, . and _. A command step's params are bounded only by the size of the whole macro (4,194,304 characters of JSON). A macro read from a file that breaks any of these is left out with a warning; it is never changed to fit. When a macro runs, a Repeat whose range has more rows or columns than its max is refused, and a run executes at most 100,000 steps (each pass through a Repeat counts), passes its commands at most 256 MB of parameters, all steps together, and takes at most 60 seconds, all three shared by the footprint dry runs and the run itself (timeLimit to change the time; the web build has no clock, so there only the step and parameter limits apply). The debugger gives each of its actions the time limit on its own.

Tokens

References in step parameters can be tokens, worked out when the step runs:

Token Means
Input, or another input's name the range the input is bound to
Output the output rule's anchor
Row, Column the current row or column of the Repeat around the step
Name:Total a defined name, or a table's data

Methods narrow or move a range and chain: col(2) or col("Amount"), row(1), cols(2..3), rows(2..), cell(r,c) (relative to the top-left cell; cell(0,1) is the cell above), offset(r,c), resize(h,w), data (without the header row), below(n) and right(n). col("Amount") looks the name up in an input's header row, directly or after data; on a range without a header row it is refused. A defined name can hold periods: Name:Sales.Q1 is the name Sales.Q1, and Name:Sales.Q1.cell(1,1) its first cell, because the longest name or table that exists is used and methods follow it.

Write a token as the whole value of a reference parameter (cell, range, target, keys ending in Cell or Range…), as {"$ref": "Input.col(2)"} anywhere, or as {{Input.col(2)}} inside any string, such as a formula; {{{{ in a string is a literal {{, and {"$text": "Output"} anywhere is the text as it is, never read as a token or for {{…}} (a defined name called Output, or Dear {{name}}). Under the general keys that other commands use for plain words (to, from, at, area, source, target, destination, location), a bare Row or Column stays text (a replacement word, a pivot area); write Row.col(2), {{Row}} or {"$ref": "Row"} there. Plain A1 references stay absolute; a Name: anchor follows inserted rows. A reference with a sheet name is never a token, so Output!A1 is a cell on a sheet called Output, even when a token or input has that name. Input.data on a range that is only a header row, and Name: of a table with no data rows, are refused rather than read as the header.

Running

macro.run {name, inputs?, output?, onConflict?, overwrite?, approve?, timeLimit?} (output replaces the macro's output anchor for this run, as a form button that sends the output elsewhere does; macro.check and the debugger take it too). The policy and the approval go by the macro as it is stored; the footprint, the conflict rule and the check of each step work on the cells output names, so data there is protected as at the macro's own anchor. An output that isn't reference text of at most 1,024 characters, or that names no cell (a bad reference, a sheet that doesn't exist, a cell outside the grid), is refused before anything runs:

  1. Policy. File commands other than Properties, exports and printing (such as Open, Save and Close), app, window (including New Window, Hide and Unhide), automate and macro commands, Undo and Redo, and pressing a form control (control.press), never run in a macro. File › Properties runs, and so do exports and printing (file.exportPdf, exportCsv, exportHtml, print, printPreview, saveBytes) that only produce bytes. Reading or writing a file (data.getData, data.fromTextCsv, insert.picture or pageLayout.background with a path, or an export or print with a path) needs the command in the macro's permissions. Any path other than null counts, including a blank one and a token, and the policy is checked again on the parameters as they are when the step runs. A macro that touches files, or that came with a workbook opened from a file, runs once the person approves it: the call fails with approve: … and runs with approve: true. Approval lasts for the session, until the macro changes. Every step other than an approved file step runs inside the file sandbox that scripts use, so no other parameter reaches a file either.
  2. Footprint. Unless the conflict rule is overwrite, the macro first runs in dry-run mode on the real workbook, and everything is put back. Cells it would write outside its inputs that hold data, and sheets and objects it would delete, are conflicts, handled by onConflict (or the macro's output.onConflict): ask (the call fails with confirm: …; overwrite: true answers it), fail, overwrite, shiftDown or shiftRight (Insert Cells makes room, then the footprint is checked again). Steps that touch files, and Solver, end the dry run early; the footprint then says it is partial. Unless the rule is overwrite, the run also checks every step as it goes, since the footprint can miss a write: it is partial, or a value such as RAND or NOW sends the run another way than the dry run. A step that writes over cells, sheets or objects that held data when the run started stops the run and rolls the workbook back (a file already written stays): it asks (confirm: …) under ask and fails otherwise, since shifting can't make room that late. Moving cells that hold data, as inserting or deleting rows or cells above them does, counts as writing over them, in the footprint and in this check, and so does a formula whose references such a move rewrites: both compare cells by place, so they may ask about data that only moved, never the other way.
  3. Run. One undo step, "Run Macro: Totals". Any error rolls the whole run back and names the step: Step 3.1 (cell.set): … (paths are 1-based; 2.1 is inside a Repeat, 4.then.1 inside an If). The message ends "Nothing was changed.", or names the files the run already wrote, which stay. An input the macro doesn't have, an input that isn't reference text, and onConflict, overwrite, approve or timeLimit of the wrong kind are refused rather than ignored. When a step inserts, deletes or moves sheets, rows, columns or cells, the inputs, the output and the range of a Repeat in progress move with their cells, as references in formulas do; a step that deletes one of them, or changes the size of a range a Repeat is going over, stops the run.

macro.check {name, inputs?, output?, onConflict?, overwrite?, timeLimit?} reports the bound inputs, the output anchor (output when given), any approval needed and the footprint, and puts back everything the dry run changed. ok says whether macro.run with the same parameters would go ahead without a question; when it wouldn't, errors says why: the run would fail at a step, a Stop step rolls it back, it would write over data under ask or fail, shifting can't make room (the check shifts and checks again, then puts everything back), or the run would go past the step or parameter limit counting the dry runs before it. The random numbers RAND gives move on, as after any recalculation.

Debugging

macro.debug {action, …} runs a macro a step at a time. The actions are:

  • start with name and the options of macro.run (inputs, onConflict, overwrite, approve, timeLimit), refused as macro.run refuses them when they are of the wrong kind. The checks, the approval and the footprint come first, as for macro.run; then the run pauses before its first step.
  • step runs the next step; continue runs to the next breakpoint or the end; runTo (step: a path) runs to that step, stopping at a breakpoint on the way.
  • stop ends the run: keep: true keeps the changes as one undo step, otherwise they are rolled back.
  • state answers without running anything (the default while a macro is being debugged).

breakpoints (a list of step paths of the macro, at most 10,000) can be given with any action and replaces the list set before; breakpointsOnly: true makes the list replace the steps' own breakpoint flags, also when it is given without a list. An unknown action, a breakpoint or runTo path the macro doesn't have, and keep or breakpointsOnly that isn't true or false are refused before anything changes. The time limit applies to each action on its own (start with its dry runs, and each step, continue or runTo); the step and parameter limits apply to the whole run, counting the dry runs.

Each answer gives the next step with its parameters as they would run (a Repeat over whole rows or columns shows the part of the sheet in use, as the run goes over it), the parameters of the step that just ran, the steps the run has executed (the dry runs not counted), and what the run has changed in its workbook: changed lists at most 500 entries (cell ranges, and sheets added, deleted or renamed) and changedCells counts the changed cells on the sheets it had at the start. shifted names the rule that made room at the start, and wroteFiles the files the run wrote, which stay when the run is rolled back; the answer to a rolled-back run says so in message. A run that ends (its last step, a Stop step, or a failed step, which rolls it back) or is stopped answers with state finished, stopped or failed.

While a macro is being debugged, its workbook is locked between steps: only macro.debug, queries (commands that only read), commands that look around (selection.*, Select All, sheet.activate, sheet.next and sheet.previous, Go To, Go To Special, Find, Copy and the zoom commands), Switch Window, Close (which ends the run with its workbook) and Automate › Stop Action run there. Everything else, undoable or not, saving included, is refused with "Finish or stop the macro first", also when another command runs it (stopping Record Actions saves a script), and so is macro.check, whose dry run runs the macro's steps. Other workbooks stay editable. The person may look around between steps: before the next step runs, the debugger puts the active sheet and the selection back where the run left them, so the run goes on as macro.run would run it. A zoom change made meanwhile is part of the run, and is undone with it. Stop Action rolls the run back and ends it, and its answer names the macro. The debugger runs on its own, never as a step of a script or macro, and is never recorded.

Recording

macro.record toggles recording. Starting takes relative (default true; false for absolute references) and, from the Record Macro dialog, name, shortcut and description: the macro's name, its Ctrl+Shift shortcut letter ("Ctrl+Shift+M" or "M") and its description. They are checked at once, so a name that is taken (a kept one included), too long or holds line breaks, or a letter that GridCraft or another macro uses, is refused before anything is recorded. Without a name the macro is called Macro1, Macro2 and so on. A name or description given when stopping wins over the one given when starting; a name that another macro has by then gets a suffix, such as Tidy (2), so stopping never replaces a macro, and a shortcut another macro took meanwhile is left off, which the result says in shortcutDropped. cancel: true ends a recording without saving it, the way out of one whose macro can't be saved. A parameter the call would ignore is refused: relative or shortcut when stopping, stop: false while recording, anything beside cancel: true, and one of the wrong kind. The macro is saved in the workbook where recording started, even if another one is active; if that workbook was closed, stopping ends the recording without saving it. When saving the recording fails (a name the check refuses, a workbook at its macro limit, a macro being debugged in that workbook), recording goes on, so stopping again keeps what was recorded. The person recorded the macro, so in a workbook opened from a file it runs without the approval a macro that came with the file needs, until it changes.

A recording keeps up to 100,000 commands and 64 MB of them as JSON, and a macro holds up to 10,000 steps and 4,194,304 characters of JSON. The steps past any of these limits, and steps recorded in another workbook, are left out and counted in dropped.

With relative references the selection when recording starts is the example input: references in and near it on its sheet become tokens (on another sheet C4 stays that sheet's C4), and the input's shape, header and column types are inferred from it as it was when recording started, before the recorded steps changed it. Recorded text that playback would read as a token stays text: a reference parameter that reads as one (a defined name called Output) is written {"$text": "Output"}, and {{ in a string is written {{{{. Toggles such as Bold are recorded with the state they set ("on": true), also inside a script run, so playing them back sets them rather than flipping them.

Commands a macro may not run are left out and listed in skipped, also from the steps of a macro or script run while recording. A macro run while recording is replaced by its steps when it has no inputs, no output and only plain commands (otherwise it is left out and listed), and an Automate script run by the steps it ran, without a step that only opened a dialog (a menu-style call without its parameters). Both are kept as they ran, so editing or deleting the macro or script before the recording stops changes nothing; a run whose steps did not fit in the recording's 64 MB is left out and listed.

Undo while recording works as in Excel: edit.undo is left out (a macro never runs it), and the step it undid stays in the recording, so playing the recording back makes that edit again.

GridCraft keeps the Ctrl+Shift letters it uses itself on some platform: F, G, H, J, K, L, R, S, T and Z (macros::RESERVED_SHORTCUTS). A shortcut a file brings with it on one of these letters is kept but does not run the macro.

Other commands and storage

macro.list, macro.get, macro.save {macro, replace?}, macro.delete and macro.rename {name, to}. Saving under a name a macro has is refused unless replace: true is passed, and renaming to one is refused; names compare ignoring the case of the letters A to Z. A renamed macro is checked against every limit again, its size included. Saving, renaming and deleting are undoable. Renaming a macro also renames it in the form buttons that run it.

Form buttons

A form button (Insert › Controls › Button, insert.button) runs a macro or one command when it is pressed (control.press {id, approve?, onConflict?}). control.assign {id, macro, input?, output?} makes it run a macro, on the selection when it is pressed (input: "selection", the default) or always on one range (input: "B2:D9"), with an optional output cell; control.assign {id, command, params} makes it run a command with fixed parameters. A macro runs through macro.run with its checks; a command goes through the same policy as a macro step and runs, as a step does, inside the scripts' file sandbox unless it is an approved file command, so a button in a workbook opened from a file asks before it runs anything. Like a step, a button's command never opens a web page or a dialog: link.open is refused when it is assigned or comes in from a file, and a command that would open a dialog or a page once it runs (a command given no parameters opens its dialog) is refused then, with the request dropped and nothing done. In the window, a press that asks (approve the action, or overwrite data in the macro's way) opens a prompt whose answer applies only to what it asked about: if the workbook, the sheet, the button, its action, its macro or the selection changes while the prompt is open (an agent reassigns the button), the answer runs nothing and the next press asks again.

In xlsx a button carries its macro as Excel writes it (macro="[0]!Name", a macro run on the selection). A command, a fixed input range or an output cell has no place there and is kept in a hidden defined name (_gc_control_actions_<k>), keyed by the sheet, the button's place, name and anchor cell. When the file is opened the hidden name is removed and each kept action goes back onto its button only when the file still agrees with it: a kept macro action needs the file to assign that macro still, so an assignment removed in another application stays removed and a button given another macro keeps that one; a kept command needs the file to assign no macro. A button whose name, anchor cell or place changed, or which another application removed, gets no kept action.

Macros are saved in the workbook: as a field in GridCraft's JSON format, and in xlsx as hidden defined names (_gc_macro_<n>_<k>, the macro's JSON split into string literals within Excel's limits), which other spreadsheet applications keep but do not run. A visible name of that shape is someone's own name: opening a file leaves it alone, and saving stores the macros under indices it does not use.

Each macro carries its format version, v (1 in this version; a missing v reads as 1). When a file is opened, a macro in a newer format (a v above 1) is kept unread, with a warning: this version can't show or run it, and saving writes it back unchanged, so the newer version gets it back whole. It is never read in part. Kept macros count toward the 1,000-macro limit after the readable ones, and one too large for an xlsx file is left out. They count when a macro is added too (saving, or stopping a recording, is refused once readable and kept macros together reach 1,000, and the message says how many are kept), so a save holds no more macros than the next open keeps. Their names are taken: saving or renaming a macro to one is refused, and a recording gets a suffix. A macro that is damaged or over the limits is left out with one warning, and the rest of the workbook and its other macros open; a macro that was left out is not in the saved file.

In the window

  • Automate › Macros group. Macros opens the Macros dialog. Record Macro opens the Record Macro dialog (name, Ctrl+Shift shortcut letter, description, Use Relative References); OK starts recording and stays off while the name or the letter cannot be used, with the reason under the fields. While recording, the button reads Stop Recording, the status bar shows a red dot, "Recording · N steps" and Stop, and Use Relative References is locked. Run Macro runs the macro run last (or the only one); its menu lists the recent macros, then every macro. New Macro opens the step editor on a new macro.
  • Macros dialog (Automate › Macros, View › Macros, Alt+F8). It lists the workbook's macros (with their shortcuts), then its Automate scripts, and says when the file carries a VBA project. Scripts that came with an opened file stay disabled until Trust Scripts (automate.trustScripts) trusts them for the session. Run runs the selected macro on the selection or the selected script; Step Into, Edit and Create open the step editor; Delete asks first; Options sets a macro's shortcut letter and description. The dialog keeps the result of view.macros, as before macros existed.
  • Step editor. A resizable window that works on a draft and saves it with macro.save; in a narrow app window it is no larger than the window and its toolbar wraps onto a second row. The step list shows each step's number, a breakpoint dot in the gutter (click to set or clear), a category icon, a summary, an error marker and an enabled checkbox; Repeat bodies and If branches are indented under their step, and rows drag to reorder within their list. Add Step opens a command picker (control steps first; commands a macro may never run are left out); Duplicate and Delete act on the selected step. The inspector's Details tab edits the selected step, with reference boxes that take a typed reference or a selection on the grid and switch between a token relative to the example input (Rel) and an absolute reference (Abs); the Inputs tab sets each input's shape, header and column types, and Use current selection as example infers them; the Output tab sets the anchor and what to do when results would land on data. The editor belongs to the workbook it opened in: while another workbook is active it says so and Save, Check, Run and Debug wait until that workbook is active again, and closing the workbook closes the editor. A draft with unsaved changes counts as unsaved work when its workbook closes (File > Close, Cmd+W, a window's close button), or the window that shows the editor closes while the workbook stays open in another: the editor first asks Save changes to the macro, even if the workbook itself is saved; Save or Don't Save goes on with the close, in the window it was asked in (whichever window was activated meanwhile), and a workbook that closes then asks about its own unsaved changes, and Cancel keeps both open. While the question stands, a further request for the same close (Cmd+W again, the window's close button) waits for the answer without a second question, and the close then runs once; a request for a different close (the whole workbook while only the editor's window was closing, or the reverse) is refused with the reason until the question is answered. A new macro that has not been edited does not ask. It keeps its window until it closes itself: another dialog asked for meanwhile is refused with a message, and closing the app (its window's close button, Quit, Cmd+Q) first asks about the editor's debug run and unsaved steps, in the editor's window, then about each unsaved workbook in its window; Cancel at any of these questions stops the quit with every window open (a draft already discarded with Don't Save stays discarded).
  • Debug bar. Check dry-runs the macro and colours the grid: inputs blue, the cells it writes green, the cells among them that hold data red. Run runs it as one undo step. Step, Continue and Run to Here start the debugger when needed (macro.debug with the editor's breakpoints and breakpointsOnly: true, so a dot cleared while paused takes effect at once); the current step is selected, its parameters are shown as they will run, and its target is outlined on the grid. Stop asks whether to keep the changes so far or undo them. While a macro is debugged the status bar says "Debugging" with the macro's name, and Show reopens the editor.
  • Questions. A macro that needs approval offers Run; a macro whose output would land on data offers Overwrite, Shift Down and Shift Right. Each button runs the command again with approve: true or the chosen onConflict, in the workbook that asked: an answer given while another workbook is active does nothing and says why.
  • Shortcuts. Ctrl+Shift+letter (⌘⇧letter on a Mac) on the grid runs the macro with that letter. The letters GridCraft uses itself keep their own meaning.

Commands with empty parameters never open these dialogs: macro.record {} starts recording under the next free name, and the dialogs are opened only from the ribbon, the menus and keys.

Automate scripts and VBA

Automate scripts (automate.recordActions, newScript, allScripts, runScript, deleteScript) are an older, separate feature. They keep their commands, parameters, storage (_sc_scripts) and results, and View › Macros (view.macros) still lists them. Macro commands neither read nor change their storage; a recording keeps the steps a script run ran while it recorded. A workbook can hold a script and a macro with the same name.

A VBA project in an .xlsm or .xltm file is preserved on save and never run. GridCraft macros are stored apart from it.