← All posts

7 min read

From help article to action: turning your 3 most repeated tasks into code

In short

Take the help article you send most often and turn its numbered steps into one function your page registers. Each step becomes a line of code, and the last step, the button that sends, stays with the customer. The assistant then does the steps when asked, instead of linking to them.

Take the help article you send most often and turn its numbered steps into one function your page registers. The article’s title becomes the action’s name and description, each step becomes a line of code, and the last step, the button that sends, stays with the customer. After that, the assistant does the steps when a customer asks, instead of linking to them.

This guide does it once, end to end, with “how to invite a teammate”, and then shows how to apply the same recipe to your next two tasks.

Which help articles should become actions?

Only the ones that walk someone through a task, not every help article. An article that explains how something works (“what’s the difference between admin and member?”) is a question, and the assistant can answer it from your docs as they are. An article that walks someone through doing something (“how to invite a teammate”) is a task, and tasks are the ones that end in a call with you.

If you haven’t sorted your inbox that way yet, Questions or tasks: sort your support inbox in 20 minutes shows how. From the task pile, pick three that are:

  • Repeated. You did them by hand more than once this month.
  • On one screen, or close. The steps happen in your app, not in an email to their IT team.
  • Ending in a button. There’s a clear point where the customer saves, sends or confirms.

Don’t assume the article failed because nobody read it. Harvard Business Review reported in 2017 that 81% of customers try to take care of things themselves before they contact a person. They usually found your article. They got stuck between reading it and doing it.

Before: the article

Say your help center has this article. It’s illustrative, but you probably have one just like it:

How to invite a teammate

  1. Go to Settings → Team.
  2. Click Invite member.
  3. Enter your teammate’s email address.
  4. Choose a role: Admin, Member or Viewer.
  5. Click Send invite.

Five steps, a screenshot per step, and still one of the most common messages in your inbox. Read it again as a developer and it’s already a function: navigate, open a dialog, type an email, pick a role, press a button.

How does each step of the article become code?

Each step becomes one line, because aside’s SDK has a helper for each kind of step. The table is the whole translation:

Article stepWhat it really isIn the action
1. Go to Settings → TeamNavigationaside.navigate('/settings/team')
2. Click Invite memberA harmless click that opens a dialogYour app’s own function, or aside.press(...)
3. Enter the emailTyping into a fieldaside.fill(..., email)
4. Choose a roleA native <select>aside.fill(..., role)
5. Click Send inviteIt sends somethingaside.highlight(...): the customer presses it

Step five is the one that changes. The article tells the customer to click Send; the action points at it and stops. An action prepares and the customer confirms what saves, sends or deletes. The reasoning is in Prepare, don’t commit.

Mark the controls

Before writing the handler, give every element it touches a stable data-aside attribute. Classes change with every redesign and button text changes with translations; an attribute stays where you put it, and when it’s missing the action fails loudly instead of clicking the wrong thing.

<button data-aside="team-invite-open" type="button">Invite member</button>
<input  data-aside="invite-email" type="email" />
<select data-aside="invite-role">
  <option value="admin">Admin</option>
  <option value="member">Member</option>
  <option value="viewer">Viewer</option>
</select>
<button data-aside="invite-send" type="submit">Send invite</button>

After: the action

Here is the article as an action. openInviteDialog stands for the function your Team page already calls when someone clicks Invite member; the name is illustrative, use whatever your app has. The handler runs in the page, so it can call your own code directly.

import { openInviteDialog } from './team/inviteDialog.js'; // your app's own function

const ROLES = ['admin', 'member', 'viewer'];

const actions = [{
  name: 'invite_teammate',
  description: 'Open the invite dialog on the Team page and fill in the email and role. '
    + 'It does not send the invitation: the user checks it and presses Send invite.',
  inputSchema: {
    type: 'object',
    properties: {
      email: { type: 'string', description: 'The teammate\'s email address' },
      role: { type: 'string', enum: ROLES, description: 'Their role; member if the user did not say' },
    },
    required: ['email'],
  },
  label: 'invite a teammate',
  run: async ({ email, role = 'member' }, aside) => {
    if (!email.includes('@')) throw new Error(`"${email}" is not an email address. Ask the user for it again.`);
    aside.navigate('/settings/team');
    await aside.waitFor('[data-aside="team-invite-open"]');
    openInviteDialog();
    await aside.fill(await aside.waitFor('[data-aside="invite-email"]'), email);
    await aside.fill('[data-aside="invite-role"]', role);
    aside.highlight('[data-aside="invite-send"]', 12000);
    return { filled: { email, role }, next: 'the user checks it and presses Send invite' };
  },
}];

if (window.aside) window.aside.register(actions);
else window.addEventListener('aside:ready', () => window.aside.register(actions), { once: true });

Line by line, it’s the article:

  • navigate is step one. The page isn’t there the moment you navigate, so waitFor holds until the team screen has rendered, and again until the dialog’s field appears.
  • openInviteDialog() is step two, done by your own code instead of a simulated click. If you’d rather click, await aside.press(await aside.waitFor('[data-aside="team-invite-open"]')) works too: opening a dialog is harmless.
  • The two fill calls are steps three and four. They type visibly, so the customer watches the form fill in rather than finding it filled.
  • highlight is step five, turned into a ring around the button. The customer presses it.

Two lines aren’t in the article at all, and they matter. The throw checks the argument at the boundary and gives the model a sentence it can repeat to the customer. The return tells the model what’s left to do, so it says “check the details and press Send invite” rather than “done”.

Write the description for the model

Three of the fields in that object reach the model: name, description and inputSchema. The model chooses an action from those alone, so write them like you’d brief a colleague:

  • The name is the intent, not the gesture. invite_teammate, not click_invite_button.
  • The description says when to use it and what it doesn’t do. “It does not send the invitation” is the sentence that stops the model from telling the customer it’s done.
  • The schema closes what it can. An enum for the role means the model can’t invent a “Superadmin” role.

label is for people: it shows up as a suggestion on the panel’s front page, and the model never sees it. Write it the way the customer would ask.

The same recipe for tasks two and three

Once one action works, the next ones take less time, because the pattern doesn’t change. Here’s the template filled in for two more common tasks. Again illustrative: use your own screens and names.

Help articleAction nameArgumentsWhat the action fillsWhat the customer presses
How to invite a teammateinvite_teammateemail, roleEmail and role in the invite dialogSend invite
How to change your planchange_planplan (enum)The plan selector on the billing pageConfirm
How to connect Slack alertsconnect_slack_webhookwebhook URL, channel nameThe URL and channel on the integration pageSave

The full change_plan handler is in Prepare, don’t commit.

For each one, copy the article’s steps into the first column of the mapping table, mark the controls, and write the handler. If a step names something that exists, like a project or a teammate, match it exactly and throw with the list of candidates when nothing or more than one matches. A loose “contains” match is how “Jon” becomes “Mary Jones”.

Check it before a customer does

You can’t talk to the assistant from a terminal, but you can check each piece:

  1. In the browser console, window.aside.version returns 1: the widget loaded.
  2. Call the handler directly to see it work without the model. Export the array from its module, then run actions.find(a => a.name === 'invite_teammate').run({ email: 'test@example.com' }, window.aside). The page should navigate, open the dialog and fill both fields.
  3. Then ask the assistant in words: “invite ana@example.com as a viewer”.

Search your handlers for press too. Every call should be on a tab, a dialog or a “next” button, never on something that sends, saves or deletes.

Should you delete the help article afterwards?

No, you don’t delete the help article. It still answers “what can a viewer do?”, and aside answers that kind of question from your docs, with one script tag and no code. What changes is the task: instead of linking to five steps, the assistant runs them and leaves the last click to the customer. Answering from docs needs no code beyond the script tag; actions need someone who can write a function, because it’s your code that runs.

aside’s own dashboard is built this way: you tell it in one sentence how you want your agent, and it sets it up for you through actions the dashboard registers, the same kind this post builds.

For the broader picture, including which functions to expose and what never to automate, read How to let an assistant act inside your web app without handing it the keys. Then pick your three articles and start with the one you sent most this week.

· Founder of aside

Software engineer in Barcelona. By day he runs the ecommerce integrations of a SaaS, where a sync bug ends up as an accounting problem for the customer; by night he builds aside. He writes about building products at 0311b.com.