- SaaS · Multi-tenant
- B2B · Japan
- 59 PRs merged
Benerio
Full-stack developer on a multi-tenant B2B SaaS for Japanese multi-location businesses: built core Google Business Profile features, the partner agency tool, and plan-bound account linking with per-location permissions, across 600+ commits and 59 merged pull requests.
Visit the live site (opens in a new tab)- Role
- Full-stack
- Team
- ~3 people
- Timeline
- 06/2025 – 12/2025
- Status
- In production
600+
Commits (459 code)
59
Pull requests merged
~60
Client feedback tickets
144
RLS policies in the platform
Next.js
TypeScript
Supabase
PostgreSQL
Tailwind CSS
Vercel
Context
Benerio is a multi-tenant B2B SaaS for the Japanese market that lets businesses with many locations manage their Google Business Profile listings and Instagram / Facebook / Threads accounts from one dashboard, with AI built in. Services run on the platform as separate modules: GBP Manager, SNS Manager, a tool that lets partner agencies manage many clients, and a few industry-specific modules.
It is built with Next.js 14 (App Router), TypeScript and Supabase, deployed on Vercel, with a Japanese interface and business content in Japanese, English and Chinese. It is a large codebase that about fifteen people have contributed to since late 2024; while I was on it, around three developers were active at the same time. I worked on Benerio at Protean Studios from June to December 2025.
Problem
- Multi-location businesses had to post, reply to reviews, update details and track metrics on every Google profile and every social account separately.
- Agencies managing many clients needed to do the same across all their clients, but only on the locations assigned to them.
- The platform had to isolate each company's data, enforce permissions per company and per location, and charge by plan and usage.
My Responsibility
I worked full stack across every layer: PostgreSQL migrations and Row Level Security, server-side API routes and React interfaces. Four main areas:
- GBP Manager: business information editing, review management, scheduled posts, analytics and PDF reports, AI translation, sync jobs.
- Partner agency tool: client management, GBP posts with drafts and scheduling, failed-post handling, media management.
- Plans and usage: Google accounts bound to plans, per-location permissions, RLS for agency-supported locations.
- Google sync: the OAuth flow for linking and unlinking GBP, plus syncing posts and reviews.
In 2025: 619 commits (459 code commits), 59 merged pull requests and about 60 feedback tickets from Japanese clients — through feature branches, pull requests and code review, communicating in English and Japanese.
Constraints
- A large shared codebase (~185,000 lines of TypeScript): every change had to follow the existing module architecture and migration process.
- Isolation had to hold even if app code was wrong: one company must never see another company's data.
- Agencies act on behalf of clients: access has to cross tenants, but only for the locations they support.
- External APIs: Google OAuth tokens expire, APIs have quotas and rate limits, and data on Google can be edited or deleted outside the system.
- Japanese clients reported issues as numbered tickets; three environments (local, staging, production), with a manual confirmation step for production deploys.
Architecture
- Next.js 14 on Vercel is the only middle layer: users and cron jobs both pass through it before touching Supabase or the Google, Meta and LLM provider APIs.
- Services are plugins (built by the platform team): each module declares its routes in a
manifest.json, and one catch-all route loads a module only when that service is enabled for the company. - Data is hierarchical — Company → Location → User; one user can belong to several companies with different permission groups. Integration settings live in a service catalogue and a table of services enabled per company.
- Protection lives in the database: 71 tables, 144 RLS policies and 73 PostgreSQL functions isolate data by company and permission group.
- Background work: 10 Vercel cron jobs — scheduled posts every 5 minutes, daily and weekly GBP and social metrics sync.
In the diagram, the highlighted blocks are the parts I built directly; the rest is the platform the team built.
Key Technical Decisions
Plans and usage live in the data model
- Problem: linked Google and social accounts must count against the active plan, and features should only open for locations on a suitable plan.
- Decision: an
integrated_service_usagemodel that binds each linked account to the active plan; flows to link and unlink GBP accounts per plan; a per-location plan check before a feature runs; an RPC function that disconnects a service in a single call; a page that tracks AI usage. - Why: plan limits are enforced in data, not by whether the UI hides a button.
- Trade-off: more checks whenever a feature opens, and unlinking has to clean up consistently.
Two permission layers: the UI for experience, the database for security
- Decision: access granted per location for each user; a frontend permission hook that shows or hides sidebar items and actions; RLS in PostgreSQL as the real barrier.
- Why: UI checks keep users from seeing actions they cannot take; RLS makes sure an app-level bug still cannot leak data.
- Trade-off: both layers have to encode the same rules.
RLS for agency-supported locations
- Problem: agencies need to work on their clients' locations — which means crossing tenants.
- Decision: RLS policies that grant access only to supported locations, together with an agency client-management page and an access-transfer flow.
- Why: the agency model keeps the isolation guarantee at the database level.
Syncing posts with Google: upsert and clean up deletions
- Problem: posts can be created, edited or deleted directly on Google, outside the system.
- Decision: a sync job that upserts posts from Google and deletes local posts that no longer exist on Google, tied to the client's plan.
- Why: reruns never create duplicates, and no "ghost" posts linger in the system.
A clear post lifecycle for agencies
- Decision: GBP posts move through draft → scheduled → published / CANCELLED / failed, with failed-post handling, media stored in Supabase Storage, and CRUD for keywords, hashtags and post templates.
- Why: agencies prepare content ahead for many clients and need to see at once which posts did not go out.
Fit the existing architecture
- Decision: move the GBP APIs into the service module, following the plugin architecture; route every database change through migrations and regenerate the TypeScript types from the schema.
- Why: in a codebase many people change, new features must not break core routing or let DB and code types drift apart.
Trade-offs
- Two permission layers (UI + RLS): good experience and safety, at the cost of rules living in two places.
- Cron-based sync instead of webhooks: simple and easy to rerun, at the cost of data lagging until the next run.
- Plan limits enforced in data: more reliable, at the cost of an extra check on every action.
- A large team codebase: following the existing architecture and review process is slower than working alone, in return for a consistent system.
Implementation Highlights
- GBP business information: sub-categories, address, service area and opening hours; review management with listing, filtering, replies, reply deletion and review sync.
- GBP posts: a post screen with image upload and cancelling or deleting scheduled posts; AI translation of content.
- Analytics and reports: top search keywords, review counts, star rating and map pin; report settings and PDF export (logo, keyword table).
- Shared platform pieces: a company and location picker, file uploads to Supabase Storage, the integrated-service detail screen, and shared support modules for the agency tools.
- Google integration: the GBP OAuth flow, account linking and unlinking, and logging of Google API errors.
- Database: my own migrations (new columns, dropped unique constraints, RLS, RPC functions) and regenerated TypeScript types.
- Working with clients: about 60 feedback tickets from Japanese clients; changes revised through code review.
Result / Impact
- Delivered four feature areas in 2025: GBP Manager, the partner agency tool, plans with per-location permissions, and Google sync.
- 619 commits (459 code commits), about +116,000 / −45,000 lines, 413 new files and 59 merged pull requests.
- Resolved about 60 feedback tickets directly from Japanese clients.
What I Learned
- Isolation between customers belongs in the database; app-level checks only serve the experience.
- Plans, usage metering and feature toggles are data-model problems before they are UI problems.
- Syncing with external APIs needs upserts, cleanup of deleted data, and handling of expiring tokens and rate limits.
- In a large team, following the architecture and process (modules, migrations, review) matters more than a clever solution of my own.