[{"data":1,"prerenderedAt":534},["ShallowReactive",2],{"gate-notes":3,"layout-site":134,"layout-footer":152,"gate-uses":160,"gate-gallery":324,"journey":369},[4],{"id":5,"title":6,"body":7,"cover":114,"date":119,"description":105,"draft":120,"extension":121,"meta":122,"navigation":123,"path":124,"placeholder":120,"readingMinutes":125,"seo":126,"stem":127,"summary":128,"tags":129,"__hash__":133},"blog\u002Fblog\u002Fwhat-renaming-a-plugin-actually-touches.md","What renaming a plugin actually touches",{"type":8,"value":9,"toc":104},"minimark",[10,15,19,22,25,29,37,71,74,78,81,84,87,91,94,97,101],[11,12,14],"h2",{"id":13},"the-second-rename-was-not-my-idea","The second rename was not my idea",[16,17,18],"p",{},"Defer Forms for Contact Form 7 has had two names before this one. The first rename\nwas mine — the old name said nothing about what the plugin did, and its folder name\nbroke a wordpress.org naming rule I'd rather have caught earlier. That one I chose,\non my own schedule.",[16,20,21],{},"The second was not a choice. It went into wordpress.org review under its new name\nand came back rejected: the name led with a generic word and sat too close to an\nexisting plugin's. Review doesn't negotiate. You get a reason and a rename.",[16,23,24],{},"The part worth writing down isn't the naming — plenty has been written about naming\nthings. It's that a rename looks like a one-line change and isn't one, the moment\nthe plugin has been installed anywhere.",[11,26,28],{"id":27},"a-name-is-not-just-a-label","A name is not just a label",[16,30,31,32,36],{},"The visible rename — the string in the plugin header, the text in the admin menu —\ntook an afternoon. Everywhere else the old name was actually ",[33,34,35],"em",{},"encoded"," took four\nmore releases to find and fix, one at a time:",[38,39,40,48,59,65],"ul",{},[41,42,43,47],"li",{},[44,45,46],"strong",{},"The database table."," Submissions, roles, everything the plugin stores lived\nunder a table prefixed with the old internal short name.",[41,49,50,53,54,58],{},[44,51,52],{},"The developer hooks."," Seven of them, documented, with real signatures — anyone\nwho had already filtered on ",[55,56,57],"code",{},"old_prefix_submission_created"," needed to know it was\nnow something else, not silently stop firing.",[41,60,61,64],{},[44,62,63],{},"The REST namespace."," Every request the builder makes goes through it. Miss one\nreference and the admin looks fine until you open the one screen that calls it.",[41,66,67,70],{},[44,68,69],{},"Post meta keys, script and style handles, admin page URLs."," The last one meant\ntelling people to update their own bookmarks — a genuinely small cost, and still\na cost, and still something I had to remember to say.",[16,72,73],{},"None of this shows up in a diff of the plugin's header comment. All of it is the\nactual rename.",[11,75,77],{"id":76},"the-migration-has-to-run-once-and-only-once","The migration has to run once, and only once",[16,79,80],{},"The database table was the one that mattered most, because it's the one with a\nvisitor's data in it. Renaming a table isn't a decision you make in code and walk\naway from — every install that already has the old table needs something to run,\nonce, that moves its data to the new one before anything tries to read from the new\nname and finds nothing there.",[16,82,83],{},"That migration shipped in the same release as the prefix change. It checked for the\nold table, moved what it found, and got out of the way — the kind of code that is\neasy to write defensively and easy to forget about afterward, because once it's\nshipped it mostly just sits there working correctly for installs that need it and\ndoing nothing for installs that don't.",[16,85,86],{},"Sitting there is not free, though. Every release after that carried one more piece\nof logic whose entire job was reasoning about a table name nothing was going to use\ngoing forward. That's a real, if small, tax — one more thing to read when you're\ntrying to understand what a fresh install actually does, one more thing that could\nin principle misfire against data it was never meant to touch.",[11,88,90],{"id":89},"knowing-when-its-safe-to-delete","Knowing when it's safe to delete",[16,92,93],{},"The migration came out four releases later, and the reason it was safe to remove is\nthe part I'd actually recommend paying attention to: not \"it's probably fine by\nnow,\" but a specific, checkable claim — this plugin has never been distributed\nunder its old internal prefix. Every install anyone could download from that point\non was already past the rename. There was no table for the migration to ever find,\non any install that didn't already exist before the code was written, and there\nnever would be one again.",[16,95,96],{},"That's a different thing from \"nobody's complained.\" It's a claim about the\ndistribution history, and it's the kind of claim you can actually check rather than\nassume. Dead code that runs a permission check first and does nothing is safe. Dead\ncode you're only fairly sure is dead is a liability wearing the same clothes.",[11,98,100],{"id":99},"what-id-do-differently","What I'd do differently",[16,102,103],{},"Rename the internal prefix in the same release as the visible name, not four\nreleases later. Splitting them meant the plugin spent a stretch of its life with a\npublic name that didn't match anything in its own codebase — which is confusing to\nread, and is exactly the kind of gap a second, unplanned rename can catch you\nin the middle of. The visible name is the one wordpress.org reviews. The internal\none is the one that actually has to be right, and it deserves to move on the same\nday.",{"title":105,"searchDepth":106,"depth":106,"links":107},"",3,[108,110,111,112,113],{"id":13,"depth":109,"text":14},2,{"id":27,"depth":109,"text":28},{"id":76,"depth":109,"text":77},{"id":89,"depth":109,"text":90},{"id":99,"depth":109,"text":100},{"src":115,"alt":116,"width":117,"height":118},"\u002Fimages\u002Fblog\u002Fwhat-renaming-a-plugin-actually-touches.webp","Abstract illustration for an article about renaming a WordPress plugin and migrating its internal identity",1600,900,"2026-09-03",false,"md",{},true,"\u002Fblog\u002Fwhat-renaming-a-plugin-actually-touches",6,{"title":6,"description":105},"blog\u002Fwhat-renaming-a-plugin-actually-touches","A wordpress.org review rejected the name once. The rename itself was the easy part — the database table, the hooks API, and the REST namespace all had to move too, for a plugin that already had real installs.",[130,131,132],"wordpress","php","products","yyGeKgB7V-MTe1mpjFtKFNbd162HcN-P5NvkBD-dNpA",{"id":135,"email":136,"extension":137,"meta":138,"name":139,"role":140,"socials":141,"stem":148,"summary":149,"whatsapp":150,"__hash__":151},"site\u002Fglobal\u002Fsite.json","rupash.das.202@gmail.com","json",{},"Rupash Das","Full-stack developer",[142,145],{"label":143,"url":144},"GitHub","https:\u002F\u002Fgithub.com\u002FRupashdas",{"label":146,"url":147},"Fiverr","https:\u002F\u002Fwww.fiverr.com\u002Frupash_das","global\u002Fsite","Laravel and Vue applications, and React-based WordPress plugins. Behind them: eight years of client work, and around 45 production sites shipped as a US agency's development partner.","https:\u002F\u002Fwa.me\u002F8801873139202","EeGW4F_WwVqwPebxGBi6Bks5X-7JlYlrabI1_3un-1M",{"id":153,"colophon":154,"ctaLabel":155,"extension":137,"meta":156,"stem":157,"tagline":158,"__hash__":159},"footer\u002Fglobal\u002Ffooter.json","Rupash Das · Built with Nuxt","Start a project",{},"global\u002Ffooter","Applications, APIs and plugins. Built in Bangladesh, shipped worldwide.","ioAbs2Z6IXBmsOAL3B48yjlepJt_zX4shLK2YbnqVHc",[161,194,219,241,273,298],{"id":162,"category":163,"extension":137,"items":164,"meta":189,"order":190,"stem":191,"summary":192,"__hash__":193},"uses\u002Fpages\u002Fuses\u002F01-applications.json","The application stack",[165,169,173,177,181,185],{"name":166,"url":167,"note":168,"key":123},"Laravel","https:\u002F\u002Flaravel.com","An API that could have a second client put in front of it, rather than a framework quietly rendering its own front end. Sanctum for authentication; the authorisation layer is written by hand.",{"name":170,"url":171,"note":172,"key":123},"Vue 3, Composition API","https:\u002F\u002Fvuejs.org","Composition over Options for anything non-trivial. Logic that belongs together lives together, and it can be pulled out into a composable the day a second screen needs it.",{"name":174,"url":175,"note":176,"key":123},"Pinia","https:\u002F\u002Fpinia.vuejs.org","One store per domain, each owning its own loading and error state. A single global store is comfortable for a week and unreadable by the third feature.",{"name":178,"url":179,"note":180},"Vite","https:\u002F\u002Fvite.dev","The build for everything — applications and plugin interfaces both. Fast enough that a React panel inside wp-admin stops feeling like a compromise.",{"name":182,"url":183,"note":184},"Tailwind","https:\u002F\u002Ftailwindcss.com","Tokens in one file, utilities everywhere else. The token layer is the part that matters; the utilities are only how it gets applied.",{"name":186,"url":187,"note":188},"Nuxt","https:\u002F\u002Fnuxt.com","This site. Prerendered, with a bundle budget that fails the build rather than being admired in a readme.",{},1,"pages\u002Fuses\u002F01-applications","What I reach for when the thing being built is an application rather than a page.","hGL4lHq-kkVaIrg0fv7gwqYxrk8AVgOVVNf80UJ2_tU",{"id":195,"category":196,"extension":137,"items":197,"meta":215,"order":109,"stem":216,"summary":217,"__hash__":218},"uses\u002Fpages\u002Fuses\u002F02-local.json","Local development",[198,202,206,209,212],{"name":199,"url":200,"note":201,"key":123},"Laragon","https:\u002F\u002Flaragon.org","Where the Laravel side lives. Apache, PHP and MySQL without an afternoon of configuration.",{"name":203,"url":204,"note":205,"key":123},"LocalWP","https:\u002F\u002Flocalwp.com","One throwaway site per plugin. A plugin has to be installed on a clean WordPress before I believe it works.",{"name":207,"note":208},"MySQL","The database under both stacks. Enough of it that a data migration can be a query rather than a plugin.",{"name":210,"note":211,"key":123},"A test suite that runs locally","Just over a hundred suites on the form plugin alone. They exist because it ships to sites I will never see, and no amount of clicking replaces that.",{"name":213,"note":214},"A guarded release script","Building a release by hand is how a stray development file ends up in a zip. The script refuses rather than reminds me.",{},"pages\u002Fuses\u002F02-local","Two stacks, kept apart. Application work and WordPress work do not share an environment.","O6x-4PI-YpMIY4lMnasic5o5S74m0XvaSJobTsv9zo0",{"id":220,"category":221,"extension":137,"items":222,"meta":237,"order":106,"stem":238,"summary":239,"__hash__":240},"uses\u002Fpages\u002Fuses\u002F03-terminal.json","Version control and terminal",[223,226,229,231,234],{"name":224,"note":225,"key":123},"Git","Every project is a repository, including the client sites that were never supposed to have one. It is also the reason I can work on a live site and still have a way back.",{"name":227,"note":228,"key":123},"Git Bash on Windows Terminal","POSIX habits on a Windows machine. Most tooling assumes a Unix shell, and it is less work to bring one than to translate every command.",{"name":143,"url":144,"note":230},"Public for the plugins, private for client work. Actions for anything that has to happen on a schedule.",{"name":232,"note":233},"Composer","PHP dependencies, and increasingly the autoloader inside plugins that used to get by without one.",{"name":235,"note":236},"npm","The other half of every build. Kept boring on purpose — a dependency in a plugin is a dependency in somebody's site.",{},"pages\u002Fuses\u002F03-terminal","The layer underneath everything else. Unglamorous, and the part I would miss first.","T-s-xcuuGET_9dm2M0uVTuhlZPPSeCAvRAiH65e1P4w",{"id":242,"category":243,"extension":137,"items":244,"meta":268,"order":269,"stem":270,"summary":271,"__hash__":272},"uses\u002Fpages\u002Fuses\u002F04-wordpress.json","The WordPress side",[245,249,253,256,260,264],{"name":246,"url":247,"note":248,"key":123},"React with @wordpress\u002Fcomponents","https:\u002F\u002Fdeveloper.wordpress.org\u002Fblock-editor\u002Freference-guides\u002Fcomponents\u002F","Plugin interfaces are applications mounted inside wp-admin. Using the WordPress components is why they look like they belong there rather than like something bolted on.",{"name":250,"url":251,"note":252,"key":123},"dnd-kit","https:\u002F\u002Fdndkit.com","Dragging that works from a keyboard and announces itself to a screen reader. Retrofitting that is far more expensive than choosing it.",{"name":254,"note":255,"key":123},"The WordPress REST API","The only seam between a plugin's PHP and its interface. It is what makes the PHP testable without a browser anywhere near it.",{"name":257,"url":258,"note":259},"Advanced Custom Fields Pro","https:\u002F\u002Fwww.advancedcustomfields.com","For client sites, where the content model lives. Fields named in the client's vocabulary, not the database's.",{"name":261,"url":262,"note":263},"Elementor","https:\u002F\u002Felementor.com","Not a compromise — the right answer when an in-house team edits weekly. Locked templates, a small palette, global styles set once.",{"name":265,"url":266,"note":267},"WooCommerce","https:\u002F\u002Fwoocommerce.com","With as few extension plugins as the requirement genuinely needs. Every add-on is a licence somebody has to keep paying.",{},4,"pages\u002Fuses\u002F04-wordpress","What goes into a plugin, and what goes onto a client site. Two different lists with two different standards.","9KoxuzjlxZ4muWCbNdHrgY5KXOxLFueIx9g9orrTomo",{"id":274,"category":275,"extension":137,"items":276,"meta":293,"order":294,"stem":295,"summary":296,"__hash__":297},"uses\u002Fpages\u002Fuses\u002F05-services.json","Hosting and delivery",[277,280,283,287,290],{"name":278,"note":279,"key":123},"The client's own hosting","Shared hosting, a control panel, no staging environment and no deploy pipeline. That is the honest description of most WordPress work, and pretending otherwise is how a rebuild gets quoted when a fix was needed.",{"name":281,"note":282,"key":123},"FTP against production","Not a preference, a constraint of agency work. It teaches one discipline above all: change one thing, verify it, keep a way back.",{"name":284,"url":285,"note":286,"key":123},"Cloudflare","https:\u002F\u002Fcloudflare.com","DNS, CDN and Pages. This site is prerendered and served from it.",{"name":288,"note":289},"A transactional email provider","Site mail leaves through a real provider, never the web host's PHP mail. This is the single most common reason a contact form fails silently.",{"name":291,"note":292},"Backups the client owns","To storage in their account, not mine. Leaving me should never mean leaving their data.",{},5,"pages\u002Fuses\u002F05-services","Mostly other people's infrastructure. The skill is working inside constraints I did not choose.","FbmQ8ReFvSj2JxfK-J7EKTve-3XXK3FlX2rLGYsSX4s",{"id":299,"category":300,"extension":137,"items":301,"meta":320,"order":125,"stem":321,"summary":322,"__hash__":323},"uses\u002Fpages\u002Fuses\u002F06-working.json","How I work",[302,305,308,311,314,317],{"name":303,"note":304,"key":123},"Model the data before the interface","The fastest way to make something expensive to own is to build the screens first and discover the shape of the data afterwards. True of a Laravel schema and of an ACF field group alike.",{"name":306,"note":307,"key":123},"Read the source before the documentation","Learned in two years of having nobody to ask. It is still the fastest route into an unfamiliar framework, and it is why picking up a new one is a week rather than a season.",{"name":309,"note":310,"key":123},"Ask who maintains it, before choosing how to build it","The same question opens every project. It decides page builder or custom theme, and it decides how much of an application belongs in its API rather than its front end.",{"name":312,"note":313},"Deep work in the morning, calls at night","US clients, Dhaka time zone. The overlap is my evening, which leaves the mornings uninterrupted.",{"name":315,"note":316},"A repeated complaint is a specification","The same request from four unrelated clients is not an inconvenience. It is the only market research a one-person shop gets for free, and every plugin I have finished started there.",{"name":318,"note":319},"Write the handover in the maintainer's language","One of my plugins ships its maintainer notes in Bengali. Documentation somebody has to translate before using is documentation that will not be used.",{},"pages\u002Fuses\u002F06-working","Process rather than software. Most of it was learned the expensive way.","TXfbDM6GA9PGTV6J6tU1Hgl8j10rEHfQnjVVFF0cYe0",[325,338,348,358],{"id":326,"title":327,"category":328,"extension":137,"image":329,"meta":335,"order":190,"stem":336,"__hash__":337},"gallery\u002Fpages\u002Fgallery\u002F01-tazko-board.json","Kanban board","Applications",{"src":330,"alt":331,"width":332,"height":333,"caption":334},"\u002Fimages\u002Fgallery\u002Ftazko-board.webp","A kanban board in Tazko with Todo, In Progress, Review and Done columns and task cards carrying priority and due date",1400,1050,"Dragging updates the store first and reconciles against the API. The server still owns the order.",{},"pages\u002Fgallery\u002F01-tazko-board","TgaWh_sQJBBgJ6hawBbHU4gnRWEJFjCwfDP171Cc1ao",{"id":339,"title":340,"category":328,"extension":137,"image":341,"meta":345,"order":109,"stem":346,"__hash__":347},"gallery\u002Fpages\u002Fgallery\u002F02-permissions-matrix.json","The permission matrix",{"src":342,"alt":343,"width":332,"height":333,"caption":344},"\u002Fimages\u002Fgallery\u002Fpermissions-matrix.webp","The Tazko role editor showing capability groups for Activity, Clients and Comments, with 73 of 80 permissions enabled for the Admin role","Roles composed from capabilities, so a workspace can define its own without a release.",{},"pages\u002Fgallery\u002F02-permissions-matrix","PL75HpzIO1ckSp-VJiV57O07DaiqgK2p5ZAFrD6OjvA",{"id":349,"title":350,"category":328,"extension":137,"image":351,"meta":355,"order":106,"stem":356,"__hash__":357},"gallery\u002Fpages\u002Fgallery\u002F03-tazko-task.json","A task discussion",{"src":352,"alt":353,"width":332,"height":333,"caption":354},"\u002Fimages\u002Fgallery\u002Ftazko-task.webp","A Tazko task detail view with a subtask checklist at 67 percent and a three-message discussion thread below it","One polymorphic thread serves tasks and projects. The reply you have not sent is saved on the server, not in the tab.",{},"pages\u002Fgallery\u002F03-tazko-task","my5OA-xN5tuWban5-uTAOfVRIVLQZ8Mt-ZJ71uIfLZg",{"id":359,"title":360,"category":361,"extension":137,"image":362,"meta":366,"order":125,"stem":367,"__hash__":368},"gallery\u002Fpages\u002Fgallery\u002F06-plugin-settings.json","Settings, after the cuts","Plugins",{"src":363,"alt":364,"width":332,"height":333,"caption":365},"\u002Fimages\u002Fgallery\u002Fplugin-settings.webp","The spam settings screen in Defer Forms, with toggles for honeypot protection, a time-trap and duplicate-submission blocking","Most of what a settings page wants to be belongs in the hooks API instead.",{},"pages\u002Fgallery\u002F06-plugin-settings","11L8JKOQF2SaNPqMAzxj49pHozh58LqUmBF0Zm3N9QY",{"id":370,"chapters":371,"extension":137,"intro":528,"meta":531,"stem":532,"__hash__":533},"journey\u002Fpages\u002Fjourney.json",[372,392,418,445,474,505],{"id":373,"period":374,"role":375,"org":376,"place":377,"summary":378,"body":379,"highlights":382,"stack":385,"metrics":386,"image":387},"curiosity","2012 — 2016","Where the curiosity started","No computer of my own","Rural Bangladesh","A village where the electricity could not be relied on, and a kid who volunteered to charge other people's phones so he could spend an hour inside them.",[380,381],"Phones were charged off a solar panel. Neighbours would hand us one, I would plug it in and then spend the next hour opening every menu and changing every setting, and hours later we would find it almost exactly as flat as when it arrived. It is a funny story now. At the time it was the only access I had to a computer of any kind.","I got in front of a real one in 2012 and bought my own in 2016 — a low-powered dual-core desktop, paid for slowly. The reason it became a career rather than a hobby is unromantic: it was the one thing I owned that could plausibly change the next ten years.",[383,384],"Borrowed phones and a solar panel, in place of a computer","First real computer in 2012, first of my own in 2016",[],[],{"src":388,"alt":389,"width":390,"height":118,"caption":391},"\u002Fimages\u002Fjourney\u002Fcuriosity.webp","A small dark slab resting on warm paper in a shaft of low sunlight, a thin orange glow along one edge",1200,"Borrowed light",{"id":393,"period":394,"role":395,"org":396,"place":397,"summary":398,"body":399,"highlights":403,"stack":407,"metrics":413,"image":414},"first-lines","2016 — 2018","Teaching myself to build","Self-taught, with one mentor","At home, on a dual-core desktop","Two years of front-end fundamentals learned with almost nothing available in my own language — which turned out to be the most useful constraint I have had.",[400,401,402],"An older brother pointed me at web development and told me where to start; that was most of the guidance available. Material in Bangla barely existed, my English was not strong enough to read the good English material quickly, and the community you can ask a question of today was a fraction of its current size. A problem a beginner would now solve in ten minutes could take me two or three days.","That is where the habit came from. When there is nobody to ask, you read the source, change one thing and watch what breaks — and I have used that on every unfamiliar codebase since, including every framework I have picked up as an adult.","By the end of it I could build and style a page properly, and I had enough PHP to stop treating the server as somebody else's problem. Which is what made the next step possible.",[404,405,406],"Front end first, then PHP — no framework in between","Learned to read source because there was nothing else to read","Two years of building for practice before charging anyone",[408,409,410,411,412],"HTML\u002FCSS","JavaScript","jQuery","Bootstrap","PHP",[],{"src":415,"alt":416,"width":390,"height":118,"caption":417},"\u002Fimages\u002Fjourney\u002Ffirst-lines.webp","A small clay vessel alone in a large empty room, catching the first light","First light",{"id":419,"period":420,"role":421,"org":422,"place":423,"summary":424,"body":425,"highlights":429,"stack":432,"metrics":434,"image":441},"freelance","2018 — 2021","First paid work, then a specialism","Freelance, remote","Clients across five continents","First paid work in June 2018, and two years of turning design files into markup before WordPress arrived at the end of 2020.",[426,427,428],"The first paid job came in June 2018, two years after I started learning. For the next two years the work was almost entirely design-to-code conversion — a PSD or a Figma file in, hand-written HTML and CSS out. Narrow work, and a good place to get fast at the part everything else sits on.","The value was volume — a compressed run of briefs, deadlines and handovers that would have taken years to accumulate through referrals. Fixed prices meant every bad estimate came out of my own evenings, which is an expensive and very fast way to learn what to ask before quoting. That discipline is still the reason I can put a date on a piece of work and hold it.","WordPress arrived at the end of 2020. With HTML, CSS, JavaScript and PHP already in place it was not a new subject, it was a new arrangement of ones I knew — and it was where the work was. Within a few months it was most of what I sold, and a few months after that it was what an agency wanted a partner for.",[430,431],"Design-to-code conversion for the first two years, WordPress from late 2020","Clients across the US, Canada, Europe, Australia and New Zealand",[433,412,409,408,207],"WordPress",[435,438],{"value":436,"label":437},"223","orders delivered",{"value":439,"label":440},"100%","on-time",{"src":442,"alt":443,"width":390,"height":118,"caption":444},"\u002Fimages\u002Fjourney\u002Ffiverr.webp","Many identical clay tokens laid out in loose rows, one of them glazed orange","Volume, one at a time",{"id":446,"period":447,"role":448,"org":449,"place":450,"summary":451,"body":452,"highlights":456,"stack":460,"metrics":463,"image":470},"agency","2021 — Present","White-label development partner","A US digital agency","Remote, US hours","Around 45 production sites across five sectors, built under someone else's name. The volume that turned preferences into a method — and produced the problems everything since is an answer to.",[453,454,455],"White-label means the work ships under the agency's name: their designer's file, their project manager, their client. Feedback arrives specific and immediate, measured against a file rather than an opinion, which is uncomfortable for about a month and then extremely useful.","The range is wider than a portfolio usually admits — marketing sites, custom themes with modelled content, page builders where the client's own team had to keep editing, LMS and education sites, WooCommerce stores, and a great deal of ordinary CMS work, often on hosting nobody involved chose.","Forty-five of anything is enough to stop guessing. The decision that mattered on each one turned out not to be technical: it was who would be maintaining this in a year. The other thing it produced was a list — the same handover questions, the same requests, the same awkward edit, repeating across unrelated clients. Written down, that stops being anecdotes and becomes a specification.",[457,458,459],"Around 45 sites across construction, industrial, hospitality, education and e-commerce","Custom themes, page builders, WooCommerce and LMS — chosen by who maintains the site","Five years working US hours from Bangladesh, under an agency's review process",[433,461,462,261,265,207],"PHP 8","ACF Pro",[464,467],{"value":465,"label":466},"45","sites built",{"value":468,"label":469},"5","years with one partner",{"src":471,"alt":472,"width":390,"height":118,"caption":473},"\u002Fimages\u002Fjourney\u002Fagency.webp","Three overlapping matte planes of unequal size interlocking cleanly, one orange","Made to interlock",{"id":475,"period":476,"role":477,"org":478,"place":479,"summary":480,"body":481,"highlights":485,"stack":489,"metrics":494,"image":501},"plugins","2024 — Present","Building the tools the work asked for","Independent","Dhaka, working remotely","Taking that list seriously. Plugins built as real applications inside wp-admin — React over a PHP core, talking through REST — rather than settings pages with a form on them.",[482,483,484],"Defer Forms for Contact Form 7 came straight off the list: a visual builder for Contact Form 7, around 12,400 lines of PHP behind a React canvas, built so that no form already living on a site has to change. The others followed the same rule — take a job clients kept asking me to repeat, and make it something they can do themselves.","Building them is where the modern front-end work actually started. A drag-and-drop canvas with selection, undo and a properties panel is not a jQuery problem; it is a React application that happens to be mounted inside somebody else's admin. The PHP side stopped knowing anything about the interface and started answering REST requests instead, which is the same seam I would later build an entire API across.","The discipline that came with them was the WordPress.org review process — coding standards, escaping, internationalisation, nothing bundled that should not be. Useful to have applied to your own code rather than only to a client's.",[486,487,488],"React interfaces over a PHP core, joined by REST rather than by templates","Extension points documented as a hooks API before any settings page","Storage designed as tables where the data is relational, not as post meta",[490,491,250,492,493,178],"PHP 8.2","React","@wordpress\u002Fcomponents","WordPress REST API",[495,498],{"value":496,"label":497},"~12k","lines of PHP in Defer Forms for Contact Form 7",{"value":499,"label":500},"112","test suites",{"src":502,"alt":503,"width":390,"height":118,"caption":504},"\u002Fimages\u002Fjourney\u002Fplugins.webp","Three finished clay vessels standing on a shelf in warm side light, one glazed orange","Three, shipped",{"id":506,"period":507,"role":508,"org":478,"place":479,"summary":509,"body":510,"highlights":515,"stack":519,"metrics":523,"image":524},"applications","Now","Laravel and Vue applications","Where the work is now. Full-stack applications, built where none of the answers a CMS hands you arrive for free.",[511,512,513,514],"WordPress answers most of the hard application questions before you start — users, roles, storage, routing — and it answers them one particular way. The plugins kept running into the edge of that. A permission rule about a record rather than a type of thing is not a capability; a table you actually want to query is not post meta. At some point the honest move is to build where those decisions are yours.","So the current work is Laravel and Vue: REST APIs authenticated with Sanctum, authorisation models designed rather than inherited, migrations, and single-page front ends that have to deal with loading, failure and reconciliation honestly. Decoupled on purpose — an API I could put a second client in front of, rather than one framework quietly doing both jobs.","The patterns that keep proving themselves are the ones the earlier stages taught me. Model the data before the interface. Keep the dependency count low, because every one is something somebody inherits. And decide by who maintains it, which turns out to be as true of an API contract as it ever was of a client's homepage.","Client work continues alongside, and funds the rest.",[516,517,518],"Laravel APIs with Sanctum and capability-based authorisation","Vue 3 single-page front ends with one Pinia store per domain","Applications built at a size where the decisions are real ones",[520,521,522,174,178,182,207],"Laravel 12","Sanctum","Vue 3",[],{"src":525,"alt":526,"width":390,"height":118,"caption":527},"\u002Fimages\u002Fjourney\u002Fapplications.webp","Two clay forms standing apart, joined by a single taut orange thread","Two ends, one thread",[529,530],"Six stages, and each one is the reason the next was possible. Learning alone taught me to read source; freelancing taught me to scope; the agency taught me volume and standards; the volume produced the problems the plugins answer; the plugins ran into the limits of what a CMS will do for you, which is where the applications start.","This is the long version. The short one is on the About page.",{},"pages\u002Fjourney","w1-xNcq2Ovmf7DgXCpay8kibjgS0wKAEv1prKblV_v4",1788434573082]