Amirhossein Hosseinpouramirhp
CV
Active WordPress plugin · Internal

Toolkit

Every fix we kept writing twice, written once.

The plugin that ships with every site Pigment Dev builds. Seventeen modules, hundreds of switches, and a settings panel you can search: WooCommerce, LearnDash, Elementor, Woodmart, SMS, Telegram, secure video, performance, roles, and the long tail of small fixes that every serious WordPress site eventually needs.

See every module Why we built it

Installed on the sites Pigment Dev builds and looks after, and nowhere else. Not for sale; some modules are licensed per site. I co-founded the studio and lead its development.

Since the first commit2023 to today
wp-admin · Toolkit · WooCommerce

WooCommerce

Currency & Geo

Currency and geo

Add second currencyShow prices in a second currency alongside the store's own

Use currency switcherLet visitors switch, remembered per visitor

Currency-aware page cacheSeparate cache per currency so switching never shows stale prices

Checkout fields

Different fields for courses and eventsSkip address fields for anything that is not shipped

Use mobile number as emailFor markets where customers do not have one

Hide the customer note

Save changes

What the panel does

  • Search that edits: type a few letters and land on the setting, inside its tab
  • Every setting carries a tooltip a client can read before flipping it
  • The address follows the tab, so a link lands a colleague on the same one
  • Saves in place: no reload, no jump back to the top
  • Lays itself out right to left or left to right, from the admin language

// why

Why we built it instead of writing it again

It did not start as a product. It started as one client's plugin, and a folder full of others exactly like it.

Before

For years, every client site we built came with a small plugin of its own, called something like Edits or Custom Codes or Snippets. It held everything WordPress, WooCommerce and the theme could not do out of the box. Hide the customer note at checkout. Show dates in the Persian calendar. Send an SMS when an order ships. Let editors manage products without making them administrators. Show a zero price as Free. Add a second currency.

The requests were never exotic. That was the problem. The same dozen asks arrived from client after client, each one slightly different, and each time we wrote it again: copy the function from the last site, rename it, adjust it, forget which version was newest.

Then WooCommerce would change something, and one of those plugins would break. Not all of them at once, which would at least have been efficient. One at a time, on whichever site happened to update first, on a Friday. And because each plugin was a file of code with no interface, the client could not turn anything off, and we could not remember without opening the file which site had which fix switched on.

The realisation

We were maintaining the same feature in a dozen places and calling it custom work.

In March 2023 we took the largest of those client plugins and made it the seed of something else. The first commit says init snippets plugin, because that is exactly what it was: one client's snippets, lifted out of their site and made general. It had already lived on two client sites, each time copied and renamed. This time it kept its own name, and within a week it had a settings page.

The rule from the start was simple. Every fix becomes a module. Every module has a switch. Only what is switched on ever loads. And every switch has a label and a tooltip a client can read, so which site has which fix is a page in the admin rather than a memory in our heads.

It has grown every year since, and faster this year than in the two before combined, because once the toolkit existed the question changed. Not how do we build this for this client, but which module is missing.

Before

A plugin per client

Copied from the last site and renamed. A WooCommerce change broke them one at a time, and nobody could see which fix was on where.

Now

One plugin on every site

Clients get the fixes on day one instead of on request. We maintain one plugin instead of a folder of them, and when WooCommerce changes something, we fix it once.

// every-module

Every module, every switch

Seventeen modules. Each is a tab in the panel, each can be switched off entirely, and none loads a single line unless it is on. Open one to see what it holds, taken from the settings schema.

WooCommerceThe largest by far. The commerce layer stores in our market need, and the fixes WooCommerce itself never shipped.

Currency and geo

  • A second currency with its own mapping, name, symbol and symbol position
  • A switcher for visitors, changing prices without a reload
  • A currency-aware page cache, so a cached page never shows the wrong currency
  • WPML-aware symbols; a geo detection mode, including forcing a country for testing

Payment gateways

  • A notice shown at checkout
  • The gateway list reshaped by rule, and a gateway per currency
  • The final status for cash on delivery, chosen by the store
  • A transaction log for the regional provider

Order status and checkout

  • Final statuses for course and event orders and for virtual-only orders
  • The pay button restricted to chosen statuses
  • Different checkout fields for courses and events
  • Hide the country field or the customer note; the mobile number as the email

Cart and shipping

  • Redirect after add to cart, separately for products and for courses or events
  • Hide the added-to-cart notice; remove recurring totals from the cart
  • A shipping class for courses and events
  • Shipping only when something is actually shipped

Pricing and stock

  • A zero price shown as Free, with the label translatable
  • A variable price range collapsed to From, with the pattern editable
  • A unit price shortcode for any product or variation
  • A stock log on its own admin page; an out-of-stock SMS

Emails, notices, catalogue

  • Custom email templates; store emails forced to English; a Persian typeface in them
  • A thank-you per product or variation: one for the page, one for the email, one for SMS
  • The right-to-left price slider, fixed; sort by price only
  • Sell on WhatsApp: a button that opens a conversation instead of a cart
Custom feesA rule engine for cart fees. Every active rule whose conditions match adds its own fee line, visible and explained.

How a fee is calculated

  • A fixed amount
  • A percentage of one product's line, of the cart subtotal before discounts, of the total after coupons, of the total plus shipping, or of the whole order
  • Taxable or not, per rule

When a rule applies

  • Cart type, subtotal range, products or variations in the cart, coupon codes
  • Billing country, shipping country and state, shipping method
  • Payment gateway, user role including guests
  • Currency: any, or only while the primary or the second one is being viewed

In the panel

  • A label, a priority so lower rules run first, and an active switch per rule
  • Duplicate a rule to start the next one from it
  • Export and import as JSON, so a set of rules moves between sites in one file
Email templatesA template builder for WooCommerce's emails, where the right template depends on what was bought and the language the buyer reads.

Builder

  • A library of templates you author and keep
  • Every WooCommerce email: new order, processing, completed, refunded, cancelled, failed, on hold, customer invoice, note, reset password, new account
  • Rich HTML editing, with a CSS editor scoped to emails alone

Assignment

  • By order type: a physical order, a virtual one, a course or an event can each use a different template for the same email
  • By site language, and per language under WPML, so a bilingual store sends the email in the customer's language with a layout designed for it
WaitlistCustomers ask to be told when a product is back, then they are told, by email, SMS or both.

The form

  • Channels: email, SMS, or both
  • Position on the product page, title, placeholders, button label
  • Success and already-joined messages; an optional consent checkbox

The messages

  • A welcome email on joining
  • A back-in-stock email and SMS, each editable
  • Keep or remove the entry after notifying; translatable strings

The page

  • Every entry with its status: waiting, notified, left
  • Export to a spreadsheet, sized for large lists
  • Delivered through the queue, so a restock never fires thousands of messages at once
LearnDashAccess that expires, access that extends, and enrolment in bulk.

Access

  • Buy a course once: a purchased course cannot be added to the cart again
  • Expire access on a specific date rather than after a duration
  • Extend access on repurchase, only if the buyer already had active, time-limited access
  • Continue learning: one link back to the last lesson

Notifications

  • Scheduled notifications for enrolled students
  • Delivered through the queue, with retries

Bulk actions

  • Enrol from a spreadsheet, with a sample file generated for you
  • Remove or extend access in bulk
  • Find users by email or mobile, whichever the spreadsheet has
Sometimes the platform catches up

LearnDash did not offer expiry on a date when we added it. As of September 2026 it does, officially. We ship the features we know a platform is missing.

ElementorWidgets we kept rebuilding, display conditions Elementor never shipped, a control it should have had, and fixes for the editor's own bugs.

Widgets

  • Mega menu and search bar
  • FAQ accordion, video player
  • Comment form and comment list, as separate widgets
  • ACF field, and an ACF repeater rendered as a swipeable carousel

Display conditions

  • By an ACF field's value
  • By product attribute, taxonomy, meta, or a property such as stock or sale state
  • By invoice state, for stores running our invoicing plugin
  • A custom dynamic tag so any of these can feed any control

Controls and fixes

  • Wrapper link: make any container a link
  • Smooth scroll for anchors; a popup with a Gravity Form inside; a floating action button
  • The edit panel's endless loading spinner, fixed
  • Duplicate SVG masks on repeated widgets, fixed
Forms and TelegramForm submissions that reach a person, not an inbox. One Telegram transport shared by every feature that needs it.

Telegram bot

  • Bot token, default recipients, and topics inside a group
  • An alternate API base for networks where the default is unreachable
  • Hidden link previews, silent delivery, a timeout

Form notifications

  • Two new Elementor form actions: send to Telegram, send by SMS
  • An uploaded file arrives as a document and an image as a photo
  • Buttons under the message, a throttle per hour, a delivery log with retry
  • Persian digits converted before sending, so numbers dial

Daily summary

  • One spreadsheet a day: an overview sheet, then one sheet per form
  • Send time, recipients, file name pattern, caption, rows per sheet
  • Yesterday only, so the summary is a summary
WoodmartFor stores on the Woodmart theme: the price interface it should have, and the mobile navigation it almost has.

Price and product

  • A new price interface, everywhere or in chosen places
  • A single variation's swatch as the product image
  • A sticky add-to-cart bar; add-to-cart removed from listings

Navigation

  • The site icon before the mobile navigation, extra content after it
  • An alternative responsive menu by shortcode
  • Profile login on checkout, for stores using our profile plugin

Fixes

  • An accordion wired to ACF FAQs
  • Lazy loading re-initialised after dynamic content loads
  • The heading outline repaired: product titles in listings demoted to paragraphs
  • Duplicate and stray markup removed where it hurt the page's structure
RolesThe request we heard most often from shop owners: let my staff run the shop without handing them the keys.

Capabilities

  • Editors manage products, orders and coupons, each separately
  • Search users by mobile number in the admin
PerformanceThe list every performance consultant sends, as switches, plus fixes for problems WooCommerce and Woodmart cause on their own.

Speed and bloat

  • Emoji scripts, oEmbed, XML-RPC and self pingbacks off
  • Query strings removed from asset URLs; the head cleaned
  • The heartbeat throttled; revisions limited

Fixes

  • WordPress's own lazy loading removed when the theme already does it
  • The WooCommerce admin repaired when it breaks its own layout
  • Woodmart's listing headings demoted, for the page's outline

Transient guard

  • Layered-navigation count transients blocked, since they grow without limit on large catalogues
  • Transient and database cleanup from the panel
  • The password-change and new-user emails silenced
SMS and mailOne switch chooses the SMS provider. Everything else in the toolkit that sends a text goes through it.

Gateways

  • Four gateways: two regional providers by API, plus two existing SMS plugins used as transports, which opens every gateway those plugins support
  • A new gateway is a class
  • Pattern messages for the provider that requires pre-approved templates
  • A bridge that routes another plugin's texts through the toolkit's provider; a test SMS from the panel

Mail

  • Sender name and address
  • A styled template for everything the toolkit sends
  • A test email from the panel
Order notifierAn SMS on every order status change, written per status, to the customer, the shop, or both.

Per status

  • A message per status: processing, completed, cancelled and the rest
  • Customer, admin, or both for each
  • Global admin numbers, or numbers per status
  • Sent immediately, or through the queue
Post-purchase notificationsMessages that go out days after a purchase: a reminder to review, a link to the course, a follow-up offer.

Per product

  • Up to eight notifications per product or variation
  • Email, SMS or both, each with its own delay
  • A cron-driven queue with an interval, a batch size, retries and its own manager page
Secure videoCourse video that cannot simply be downloaded, served to each student from the server that will be fastest for them.

Delivery

  • Up to three delivery servers, each with its own base URL and signing secret
  • The server chosen by the student's country, or a default
  • Signed links through nginx secure link, bound to the viewer's IP address and expiring after a set time
  • Servers switched at runtime through an endpoint

Player

  • A notice showing which server is active, with its position configurable
  • Button labels for video and audio, and their colours
Live stream Licensed moduleFor course sites that stream rather than host, so nobody walks away with the original video files.

Providers

  • Bunny Stream, integrated end to end
  • Mux, in development
  • HLS live streams as well as stored lessons

Protection

  • Token authentication, so a stream link only works for the viewer it was issued to
  • DRM through MediaCage
  • A watermark rendered per viewer, so a leaked recording carries the name of whoever leaked it
CustomizationThe can-you-change-this-word requests, answered from a page instead of a code editor.

Text

  • Translation replacement: rewrite any translated string the site prints
  • String replacement: rewrite anything else, translated or not
  • A standalone Translations page for both

Popups and profile

  • A welcome popup asking a first-time visitor to choose a currency
  • A course intro on the profile, for stores running our profile plugin
SEOFor the cases where a plugin's generated structured data is not what the page needs.

Custom structured data

  • Hand-written JSON-LD in a proper code editor
  • Per post on the post types you choose, per term on the taxonomies you choose
  • The injection location in the page, your call
GeneralNot about a plugin or a theme, but about running a WordPress site in this part of the world.

Dates and locale

  • Jalali dates across the site, or on the front end only
  • The geo detection mode used by currency and gateway rules

Editor and content

  • The block editor off, or the classic editor with syntax highlighting
  • Custom comments
  • Read more: long text collapsed with your own labels and height

Media and admin

  • Favicon URL, front-end icon font URL
  • A media size column in the library
  • Debug mode for the toolkit itself

// the-panel

The panel

Hundreds of settings would be a liability without a good interface. So the interface is where a lot of the work went.

Search that edits

Type a few letters and the panel jumps to the matching setting inside its tab, highlights it, and lets you change it right there. Nobody remembers which tab holds the free-price label.

Every setting explains itself

Each item carries a short tooltip, so clients read what a switch does before they flip it, and send fewer messages asking.

Tabs split into sections

WooCommerce alone is a dozen: currency, gateways, order status, checkout fields, cart, shipping, pricing, stock, emails, thank-you notices, catalogue fixes, products.

Deep links

The address bar follows the tab you are on. Bookmark it, reload it, send it to a colleague; they land on the same tab.

Saves without leaving

A floating save button, and saving happens in place. No reload, no scroll to the top, a small confirmation where your eyes already are.

Reads in both directions

The panel detects the admin language and lays itself out right to left or left to right, fields included.

Pages where a table beats a form

The stock log, the waitlist and its export, translations and rewrites, the cron queue, the email template builder and the fee rules each have their own page.

Its own changelog and guide

A changelog viewer inside the plugin, entry by entry, and a documentation page with the hooks. Nobody leaves the admin to find out what changed.

A plugin manager

An installer page for companion plugins, without the round trip through the WordPress plugin screens.

// under-the-hood

Under the hood

The decisions that let one plugin carry this much without becoming the thing it replaced.

Nothing loads unless it is on
Each module checks its switch before registering a single hook. A site with everything off is, in effect, running nothing.
One schema, one source of truth
Tabs, sections, fields, types and tooltips are defined once as data. The panel renders from it, saving reads from it, search indexes it.
One transport per channel
Exactly one class talks to Telegram and one path sends SMS, so throttling, logging and retries happen in one place.
A queue for anything that can wait
Post-purchase messages, waitlist alerts, course reminders and bulk sends go through a cron queue with a batch size and a retry ceiling.
Spreadsheets in both directions
An Excel reader and writer ship with the plugin: bulk enrolment reads, the waitlist export and the daily summary write. No external service.
Two calendars, natively
A Jalali date library is part of the toolkit, so every module that shows a date can show it in either calendar.
One release, every site, at once
Each tagged release builds an install-ready zip, and the plugin's own updater delivers it to every client site the moment it is published.
One plugin, licensed parts
Some modules need a signed, site-bound authorization with plan tiers, checked on load. Locked modules stay visible behind an overlay.
Shortcodes and hooks for the rest
Unit price, ACF field and responsive menu shortcodes, and hooks documented on the plugin's own guide page.

// developers

For developers

The parts that matter when you inherit a site running this, or when you have to move one.

One option row, not autoloaded

The entire configuration, every tab and every switch, lives in a single row in the options table, excluded from what WordPress loads on every request. Switches and short values, no serialised content blobs.

Portable

Export and import the whole configuration as a file, so a new site starts from a known-good setup in one step. Fee rules, templates, translations and rewrites move the same way.

Real editors

CodeMirror and Ace wherever the panel takes code, a snippets area for site-specific CSS and JavaScript, and a CSS editor scoped to WooCommerce emails only.

Extension points

Documented hooks, shortcodes for what the switches do not cover. A new SMS gateway is a class; a new Elementor condition is a class.

// built-where-it-is-hard

Built where it is hard

Much of this toolkit exists because the market it serves has requirements the global plugin ecosystem never bothered with.

Two calendars
Jalali dates across the admin and the front end, or the front end alone.
Two currencies
A second currency with its own gateway, symbol and switcher, cache-safe.
Regional gateways
A dedicated slug and a transaction log for the local payment provider.
Regional SMS
Two local providers by API, pattern messages, and bridges into two SMS plugins.
Right to left
The price slider fix, a Persian typeface in store emails, a panel that follows the admin's language.
Persian digits
Converted before they reach an SMS gateway, so phone numbers dial.
Mobile as identity
A mobile number where the customer has no email, and users searchable by it.
Restricted networks
An alternate Telegram API base for servers that cannot reach the default.
It is not for sale

Toolkit is installed on the sites Pigment Dev builds and looks after, and nowhere else. Some of its modules are licensed per site. It is here because it is the clearest example of how we work: the same fix, written once, maintained in one place, and switched on wherever a client needs it, from day one instead of on request.

branch main 6 active projects ↑ 113 releases products/toolkit.md Sari --:-- UTC+3:30 its@amirhp.com