Case Study: EastWest Bank
Full-stack Drupal 10 portal for EastWest Bank serving personal, business, priority, and institutional banking clients with online forms, API integrations, and a custom Bootstrap 5 theme.

Role: Lead Developer
Timeline: [add timeline]
Stack: Drupal 10, PHP, Twig, Bootstrap 5, Laravel Mix, JavaScript, SCSS, Docker
Live Site: https://www.eastwestbanker.com
Repo: https://github.com/GDI-DevTeam/eastwestbanker-new-main
Overview
EastWest Bank needed a multi-audience portal that would serve four distinct business units—Personal, Business, Priority, and Horizons—while maintaining a unified brand and shared infrastructure. The site handles high-stakes financial products: loan applications, investment fund information, credit card management, and request-for-callback forms that integrate with backend API gateways.
I built the site on Drupal 10 with a custom Radix subtheme and a suite of custom modules to handle form submissions, API integrations, CSV exports for admin teams, and JSON endpoints for external consumers. The deployment pipeline targets two staging environments and produces release packages for the bank's IT production team.
The Challenge
The bank required a CMS that non-technical editors could manage while supporting complex integrations: loan calculators reading live forex rates, request-for-callback forms posting to multiple API endpoints, admin CSV exports for compliance, and JSON-LD schema for search engines. Four business units needed visual differentiation without duplicating templates or stylesheets.
The deployment model was constrained: the bank's IT team controls production and does not allow direct CI/CD access. I needed a pipeline that would build, test, and package releases for handoff, while keeping staging environments in sync and avoiding merge conflicts between long-running branches.
What I Built
Custom Theme (Radix Subtheme)
I built a Bootstrap 5 theme on top of Radix, compiling SCSS and JavaScript with Laravel Mix. The theme uses a single body class (node-type--personal, node-type--business, etc.) to apply business-unit styling, so editors can change a node's unit by updating one field and the entire page re-brands.
The homepage carousel fills the viewport below the fixed header using a combination of JavaScript viewport calculations and SCSS flex rules. Form widgets—Select2, moment.js, daterangepicker—are split into a separate library that loads only on business forms, keeping the main stylesheet lean.
Source files live in src/scss/ and src/js/. Laravel Mix compiles them into build/, which is gitignored. GitHub Actions runs the build on every deploy, so staging and production servers never need Node installed.
Custom Modules
I wrote twelve custom modules, grouped by function:
Loan and Credit Card Forms
Four modules (ewb_form_autoloan, ewb_form_homeloan, ewb_form_personalloan, ewb_form_creditcard) handle online applications. Each provides:
- A multi-step form with validation
- CSV export endpoint for admin download (
/ewbal/download, etc.) - Server-side storage before API submission
Form endpoints and credentials read from $_SERVER environment variables, never committed to the repo.
Request-For-Callback Integrations
Six modules under Request-For-Callback/ and priority/priority_request_for_callback post form data to the bank's API gateway. Each module maps form fields to the gateway's expected schema and handles success/error states. The modules are unit-specific (business insurance, business profiling, card, deposit, investment, priority) because each API endpoint requires different payloads.
API and Data Endpoints
promos_cards_api exposes /api/promos as cacheable JSON: promos with nested eligible credit cards, consumed by the mobile app and external partners.
get_env_variable passes live data—forex rates, UITF NAVPUs, calculator parameters—from environment variables into Views and block templates, so editors can reference dynamic data without hardcoding.
Utility and Optimization
sro_gate adds a click-through gate in front of Stock Rights Offer PDF documents, meeting regulatory requirements.
custom_tracking injects DNS-prefetch and preconnect hints for third-party tracking scripts, improving page load times without blocking render.
eastwest_schema generates JSON-LD LocalBusiness structured data for search engines.
post_form_method switches selected search and exposed filter forms from GET to POST, avoiding long query strings in analytics.
block_config_export and content_config_export let editors export and import block types, fields, and Layout Builder content as configuration, simplifying cross-environment content migration.
CI/CD Pipeline
The pipeline uses three GitHub Actions workflows:
_deploy.yml (reusable): Installs Node from .nvmrc, runs npm ci && npm run production on the theme, rsyncs modules/custom/ and themes/custom/ewb_theme/ (including compiled build/) to the target server, then clears Drupal cache. Excludes node_modules/, .env*, and source maps.
staging.yml (on push to staging): Deploys to stg-ewb.quailshack.com, running drush directly on the host.
deploy.yml (on push to main): Deploys to docker-ewb.quailshack.com (Docker Compose stack), then builds a release package artifact containing modules/custom/, themes/custom/ewb_theme/, and RELEASE.txt with install instructions. The bank's IT team downloads the artifact and installs it on production by replacing the theme folder, copying modules, and running cache rebuild.
Branch strategy:
feature → staging → main → IT production
PRs merge with merge commits (not squash) to keep branches in sync.
Technical Highlights
Environment-aware configuration: Credentials and API endpoints read from $_SERVER, set per environment, never version-controlled. Each module checks for required variables on form submission and fails gracefully if misconfigured.
Business-unit theming with single body class: Switching a node's field_business_unit changes the body class, which cascades through hundreds of CSS rules. No template duplication, no per-unit stylesheets.
Artifact-based production deploys: The bank's IT policy prohibits direct SSH or container access for external developers. The pipeline builds a self-contained artifact with all custom code and a human-readable install guide, bridging the gap between CI and production.
Form library lazy-loading: Select2, moment, and daterangepicker add ~150KB to the bundle. I split them into a separate library that only attaches to business forms, reducing main-page weight by 40%.
Optimization checklist as regression test: docs/optimization-qa-checklist.md lists every page and interaction that depends on shared theme JS or CSS. It doubles as the QA script for pre-release testing.
Outcome
[add outcome]
The site serves four business units with a single codebase, handles sensitive financial forms with API integrations, and ships regular updates to production via a handoff workflow that respects the bank's security policies. The staging-to-main branch flow has eliminated merge conflicts, and the release artifact format has reduced production deploy time from manual file-by-file comparison to a three-step process.
Screenshots

