Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Slack By James Joyner IV · · 11 min read

Validating Slack Block Kit Inputs: dispatch_action and Inline Errors Done Right

Build Slack input forms that validate as users type and on submit, using dispatch_action and response_action errors so ops modals reject bad data without losing input.

  • #slack
  • #ai
  • #block-kit
  • #validation
  • #modals

The first ops modal I shipped let someone schedule a maintenance window for a Kubernetes cluster that didn’t exist, with an end time before the start time, and then it cheerfully posted the broken window to a channel of fifty engineers. Block Kit had rendered the form perfectly. It had collected the values perfectly. It had validated exactly nothing, because validation in Block Kit is something you opt into, in two distinct places, and I’d opted into neither.

This is a guide to validating Slack input forms properly: catching errors inline as the user interacts via dispatch_action, and catching them on submission via response_action: "errors". The two mechanisms cover different moments, they compose, and getting both right is the difference between a form that guides people to correct input and one that lets garbage through to your ops automation.

The two validation moments

Block Kit gives you two places to validate, and they answer different questions:

  • On submit — when the user clicks the modal’s submit button, your handler receives the full view.state.values. You validate everything, and if something’s wrong you respond with response_action: "errors" to show field-level messages without closing the modal or losing what the user typed. This is your last line of defense and it’s mandatory.
  • As they interact — with dispatch_action enabled on an input, Slack fires a block_actions event the moment the user changes that field (on enter or on character change, depending on config). This lets you validate or react mid-form: resolve a service name against a real inventory, enable a dependent field, or flag a typo before submit.

Most teams only do the first, and that’s defensible — submit-time validation is the one you cannot skip. But dispatch_action is what turns a form from “fill it all out, submit, get rejected, try again” into something that corrects you as you go. If you’re building ops tooling in Slack, the interactive validation is what makes the form feel like a tool rather than a gauntlet.

Submit-time validation: response_action errors

Start here, because it’s the non-negotiable one. When the modal is submitted, validate and return errors keyed to each input’s block_id:

app.view('schedule_maintenance', async ({ ack, view }) => {
  const values = view.state.values;
  const start = values.start_block.start_input.selected_date_time; // unix ts
  const end = values.end_block.end_input.selected_date_time;
  const service = values.service_block.service_input.value;

  const errors = {};
  const now = Math.floor(Date.now() / 1000);

  if (start < now) {
    errors.start_block = 'Start time must be in the future.';
  }
  if (end <= start) {
    errors.end_block = 'End time must be after the start time.';
  }
  if (!KNOWN_SERVICES.has(service)) {
    errors.service_block = `Unknown service "${service}". Check the inventory.`;
  }

  if (Object.keys(errors).length > 0) {
    await ack({ response_action: 'errors', errors });
    return;
  }

  await ack();
  // ...proceed with the validated values
});

The keys in errors must be block_id values, not action_id values — mixing these up is the single most common reason the error message silently fails to render. Slack keeps the modal open, shows each message under its field, and preserves everything the user entered. No re-typing, no lost work.

Pro Tip: You get exactly one ack() per view submission, and it carries the response_action. You cannot ack once to show errors and again to proceed. Decide the outcome, then ack once.

Interactive validation: dispatch_action

To validate as the user types, enable dispatch_action on the input block and give the element a dispatch_action_config:

{
  "type": "input",
  "block_id": "service_block",
  "dispatch_action": true,
  "element": {
    "type": "plain_text_input",
    "action_id": "service_input",
    "dispatch_action_config": {
      "trigger_actions_on": ["on_enter_pressed"]
    }
  },
  "label": { "type": "plain_text", "text": "Service name" }
}

Now Slack fires a block_actions event when the user presses enter in that field. You handle it, resolve the value, and update the modal to reflect what you found:

app.action('service_input', async ({ ack, body, client }) => {
  await ack(); // ack first — the 3-second rule applies here too
  const typed = body.actions[0].value;
  const resolved = await lookupService(typed);

  const view = buildScheduleView({
    serviceStatus: resolved ? `✅ ${resolved.name}` : `⚠️ not found`,
    // preserve other entered values from body.view.state.values
  });

  await client.views.update({ view_id: body.view.id, view });
});

Two things matter here. First, you ack within three seconds like any other interaction, then do the lookup. Second, when you views.update, you must rebuild the view with the values the user already entered, pulled from body.view.state.values — an update replaces the view, and anything you don’t carry forward is gone. This is the trap that makes people swear off dispatch_action: they update the modal and the user’s half-filled form resets.

Where AI helps, and where it doesn’t

I let an LLM draft these handlers, because the shape is repetitive and the model has seen thousands of them. A prompt like this gets a usable skeleton:

Write a Slack Bolt view-submission handler for a maintenance-scheduling modal. Validate that start is in the future, end is after start, and service is in a known set. Return response_action errors keyed to block_id for each failure. Ack only once.

And it’ll return something close to the first snippet above. But the AI drafts, the human verifies — because the model reliably makes two mistakes here. It keys errors to action_id instead of block_id, and it forgets to preserve state on views.update. Both render fine in a quick read and fail in a real workspace. I keep a checklist and scan every generated handler for exactly those two bugs before it goes near a modal users will touch. If you’re iterating on these, the saved prompt templates save re-typing the constraints each time, and a tool like Cursor keeps the handler and its validation in one view.

A validation checklist

Before any input form ships:

  1. Every input has submit-time validation. Never rely on dispatch_action alone; interactive validation is a convenience, not a guarantee. A user can submit without triggering it.
  2. Errors key to block_id. Double-check this; it fails silently otherwise.
  3. One ack() per submission. Decide proceed-or-error, then ack once.
  4. views.update carries forward state. Rebuild from body.view.state.values.
  5. Server re-validates everything. Treat the client as untrusted even when dispatch_action already checked a field — clients vary and values can be stale.

Wrapping Up

Validation in Slack forms lives in two places, and you need both: response_action: "errors" on submit as the non-negotiable backstop, and dispatch_action for the interactive guidance that makes a form feel like a tool. The mechanics are small but unforgiving — error keys are block_id values, you get one ack per submission, and an update wipes any state you don’t carry forward. Let an AI draft the handlers, then scan for those exact failure modes before anything reaches a real modal. Do that, and the next person who fills out your maintenance form won’t be able to schedule a window for a cluster that isn’t there.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.