In any growing application, business logic tends to scatter. A user sign-up process might have validation on the frontend, an entry created in a backend service, a welcome email triggered by a separate worker, and a database trigger to update a counter. Over time, this distributed logic becomes a tangled web that’s difficult to understand, maintain, and update.
What if you could untangle it? What if every core interaction in your business could be defined as a single, clear, and executable piece of code?
This is the core principle behind Business-as-Code, a revolutionary approach to Action Management. With Verbs.do, you translate your business operations into powerful, executable verbs. Instead of spreading rules across your stack, you centralize your business logic into declarative, auditable, and automated actions.
Let's explore how you can model five common business actions as code today, transforming complexity into clarity.
Before we dive in, let's look at the basic structure of a Verb. It’s a simple, declarative object that defines an action:
Here’s the classic "Follow" example.
import { Verb } from 'verbs.do';
const follow = new Verb({
name: 'Follow',
subject: { type: 'User' },
object: { type: 'User' },
effects: [
{ type: 'createEdge', from: 'subject', to: 'object', label: 'follows' },
{ type: 'notify', user: 'object.id', message: '`{subject.name}` started following you.' }
]
});
// Execute from anywhere in your system
await follow.execute({ subjectId: 'user-123', objectId: 'user-456' });
With one definition and one execute call, you've created a social graph connection and sent a notification. The logic is atomic, guaranteed, and centralized.
Now, let's apply this powerful pattern to other common actions.
A fundamental interaction in content platforms, "Liking" involves more than just a UI change. It updates counters and creates a record of the user's engagement.
The Action: A User likes a Post.
The Verb:
const like = new Verb({
name: 'Like',
subject: { type: 'User' },
object: { type: 'Post' },
effects: [
// Create an edge to represent the 'like' relationship
{ type: 'createEdge', from: 'subject', to: 'object', label: 'likes' },
// Atomically increment the like count on the Post
{ type: 'incrementCounter', on: 'object', field: 'likeCount' }
]
});
// Usage:
await like.execute({ subjectId: 'user-789', objectId: 'post-abc' });
Why it's better: The logic for incrementing the counter and recording the "like" are coupled. You’ll never have a scenario where one happens without the other. This single Verb becomes the single source of truth for what it means to "like" something in your system.
Moving from social features to core business operations, let's model a purchase order. This action requires specific data and triggers a clear B2B workflow. This is a perfect example of sophisticated API Automation.
The Action: A Department Manager creates a Purchase Order for a Supplier.
The Verb:
const createPurchaseOrder = new Verb({
name: 'CreatePurchaseOrder',
subject: { type: 'User' }, // The user creating the PO
object: { type: 'Supplier' },
properties: {
// Define required data for this action
items: { type: 'array', required: true },
totalAmount: { type: 'number', required: true }
},
effects: [
// 1. Create the official PO record
{ type: 'createRecord', collection: 'PurchaseOrders', data: { /*...*/ } },
// 2. Send a formal email to the supplier
{ type: 'sendEmail', to: 'object.email', template: 'new-po-notification' },
// 3. Queue a task to deduct from the department's budget
{ type: 'triggerJob', name: 'updateDepartmentBudget' }
]
});
// Usage:
await createPurchaseOrder.execute({
subjectId: 'manager-42',
objectId: 'supplier-acme',
properties: { items: [/*...list of items...*/], totalAmount: 499.99 }
});
Why it's better: The complex process of creating a record, notifying an external partner, and updating internal budgets is defined in one place. Your frontend or service layer doesn't need to know the implementation details; it just needs to execute the CreatePurchaseOrder action.
Actions don't have to exist in a vacuum. The effect of one Verb can be to trigger another, creating powerful and observable workflows. This is the foundation of building an Agentic Workflow.
The Action: A Manager approves an Expense Report submitted by an Employee.
The Verb:
const approveExpense = new Verb({
name: 'ApproveExpense',
subject: { type: 'Manager' },
object: { type: 'ExpenseReport' },
effects: [
// 1. Update the report's status
{ type: 'updateRecord', on: 'object', data: { status: 'Approved' } },
// 2. Notify the employee who submitted it
{ type: 'notify', user: 'object.submitterId', message: 'Your expense report was approved.' },
// 3. Trigger the next action in the workflow
{ type: 'triggerVerb', verb: 'ProcessPayment', with: { expenseReportId: 'object.id' } }
]
});
// Usage:
await approveExpense.execute({ subjectId: 'manager-bob', objectId: 'expense-report-xyz' });
Why it's better: This Verb is a composable building block. By successfully executing ApproveExpense, you automatically kick off the ProcessPayment action. Your business process is now explicitly coded, versionable, and easy to follow.
User and permission management can be complex. An action like adding a user to a team often involves creating relationships, granting roles, and sending notifications.
The Action: A Team Lead adds a new User to their Team.
The Verb:
const addUserToTeam = new Verb({
name: 'AddUserToTeam',
subject: { type: 'User', role: 'TeamLead' }, // Can add authorization rules here
object: { type: 'User' },
properties: {
teamId: { type: 'string', required: true }
},
effects: [
// Create the membership link between the user and the team
{ type: 'createEdge', from: 'object', to: { type: 'Team', id: 'properties.teamId' }, label: 'isMemberOf' },
// Grant them the default permissions for that team
{ type: 'grantPermissions', user: 'object.id', context: 'properties.teamId', role: 'Member' },
// Let the user know they've been added
{ type: 'sendEmail', to: 'object.email', template: 'team-invitation' }
]
});
// Usage:
await addUserToTeam.execute({ subjectId: 'lead-jane', objectId: 'new-user-sam', properties: { teamId: 'team-devops' } });
Why it's better: The entire onboarding logic is encapsulated. If you decide to add a Slack notification later, you only change this one Verb definition, and every part of your application that uses this action will automatically inherit the new behavior.
Deactivating a service involves a sequence of events: updating status, revoking access, and potentially notifying other systems. Defining this as a Verb ensures it's done correctly every time.
The Action: A User cancels their Subscription.
The Verb:
const cancelSubscription = new Verb({
name: 'CancelSubscription',
subject: { type: 'User' },
object: { type: 'Subscription' },
effects: [
// Mark the subscription as canceled at the end of the current period
{ type: 'updateRecord', on: 'object', data: { status: 'Canceled', cancelDate: 'now' } },
// Revoke access permissions tied to the subscription
{ type: 'revokePermissions', user: 'subject.id', source: 'object.id' },
// Notify the billing system via a webhook
{ type: 'fireWebhook', url: 'https://billing.system/hooks/cancel', payload: { /*...*/ } }
]
});
// Usage:
await cancelSubscription.execute({ subjectId: 'user-111', objectId: 'sub-222' });
Why it's better: This action guarantees a clean off-boarding process. You avoid scenarios where a user cancels but still has access, or where the billing system isn't notified. The definition acts as an executable checklist for your business rule.
By defining core interactions as Verbs, you create a single source of truth for how your business operates. This paradigm of Business-as-Code moves logic from being an implicit, scattered side effect to an explicit, manageable asset.
The benefits are immediate:
Ready to stop chasing logic through your codebase?
Visit Verbs.do to learn how to define, manage, and automate your business actions as code.