Architecture · Deep dive · Forms and workflows
Event-Driven Forms and Workflow Backend for WordPress on AWS
A reference architecture for moving form submissions, drafts, uploads, emails, webhooks and workflow execution out of WordPress/PHP and into a customer-owned AWS backend.
Architecture thesis: Forms are not just HTML inputs. On serious WordPress sites they become application entry points. The Flow backend treats form submission as an event-driven system with separate public and admin APIs, durable state, payload storage, async dispatchers and explicit abuse controls.
Why forms become an architecture problem
Simple contact forms can live almost anywhere. The problem starts when forms become business workflows: save-and-resume drafts, file uploads, email templates, webhook delivery, approval steps, CRM handoff, audit trails, protected admin views and retryable downstream actions.
In a traditional WordPress implementation, those responsibilities often accumulate inside plugins, PHP hooks and database tables attached to the public site. That works until traffic, spam, large attachments, external systems or static publishing force the form layer to behave like a real backend.
The Flow backend separates page rendering from form execution. WordPress still owns the form design and editorial placement, but AWS owns the runtime path that accepts submissions, stores state and dispatches work.
System boundary
WordPress source site
Gutenberg forms, workflow intent, email/template configuration
│
▼
Static or dynamic frontend
Flow blocks rendered into public pages
│
├─ submit / save draft / load draft / upload URL
▼
API Gateway REST API
/frontend/* routes: public form operations
/admin/* routes: protected management operations
│
▼
Forms API Lambda
validation, persistence, event recording, upload references
│
├─ DynamoDB: forms, submissions, events, templates, workflows, webhooks, process maps
├─ S3: payload uploads and email template assets
├─ EventBridge: submission and workflow events
├─ SES: email actions via email sender Lambda
└─ Webhook dispatcher Lambda: outbound integrations
The browser does not need WordPress/PHP to handle a submit. The page can be static, the form can still be interactive, and the backend can apply a different security, retention and retry model than the content site.
What the Flow stack creates
| Layer | AWS resources | Architectural job |
|---|---|---|
| API layer | Regional API Gateway REST API, optional custom domain, optional Route53 records | Expose separate frontend and admin route families without tying form execution to WordPress hosting. |
| Compute layer | Forms API Lambda, Workflow Dispatcher Lambda, Email Sender Lambda, Webhook Dispatcher Lambda, deployment-time Custom Resource Lambda | Keep synchronous form handling, async workflow processing, email delivery and outbound webhooks as separate operational units. |
| State layer | DynamoDB tables for submissions, submission events, templates, workflow definitions, form definitions, webhook endpoints and process maps | Store form definitions, records and workflow state in purpose-specific tables with TTL/retention instead of treating the WordPress database as the integration log. |
| Payload layer | S3 payload bucket and templates bucket, optional existing buckets | Move large file transfer and reusable email/template assets into object storage. |
| Event layer | EventBridge rules and events such as submission created/updated/status/action and AI agent completion/failure | Turn form work into observable events that can trigger workflows without blocking the visitor. |
| Security layer | Cognito authorizer for admin routes, optional IAM/NONE modes, WAF, reCAPTCHA, IP allow/block lists, SSM/KMS for secrets | Apply different protections to public form submissions and privileged management APIs. |
| Operations layer | CloudWatch log groups, SQS dead-letter queue, configurable log retention, optional GuardDuty malware protection | Give the runtime its own logs, retry/failure surface and payload scanning options. |
Frontend API versus Admin API
The most important design choice is the route split. Visitor actions belong under /frontend/*; management and inspection belong under /admin/*. That keeps the public form surface small and makes privileged operations easier to protect.
| Route family | Examples | Typical caller | Security posture |
|---|---|---|---|
| Frontend submission | /frontend/forms/{formId}/submit | A rendered Flow form on a public or protected page | Can run with no user auth, but should use reCAPTCHA/WAF/rate limits for anonymous traffic. |
| Frontend drafts | /frontend/forms/{formId}/drafts, /drafts/load, /drafts/delete, /drafts/{submissionId}/submit | A visitor saving, resuming or finalizing a long form | Draft credentials and final validation are separated so draft saves do not trigger final workflows. |
| Frontend upload preparation | /frontend/forms/{formId}/upload-url | A form component preparing a large attachment | Returns a presigned S3 upload contract; the payload does not need to pass through WordPress. |
| Admin forms/submissions | /admin/forms, /admin/forms/{formId}/submissions | WP Admin or management UI | Should be protected with Cognito/IAM and optionally IP allowlisting. |
| Admin templates/workflows/webhooks | /admin/templates, /admin/workflows, /admin/webhook-endpoints | Administrators configuring business behavior | These routes change runtime behavior and should never be treated like public frontend endpoints. |
Submission lifecycle
Render
WordPress owns the form surface
Editors build the form experience in WordPress and publish it like any other page component. The runtime contract is embedded into the page rather than handled by a public PHP endpoint.
Prepare
Uploads get a storage contract first
Large payloads can request a presigned upload URL, upload directly to S3 and pass a payload reference with the submission.
Save
Drafts are intentionally not final events
Draft endpoints let a visitor save progress without firing emails, webhooks or final workflow steps.
Submit
Final validation happens at the backend
The final submit path can enforce reCAPTCHA, validate fields, persist the record and write a submission event.
Dispatch
Events fan out into work
Workflow, email and webhook dispatchers can react asynchronously to submission events, status changes or action invocations.
Observe
Admin views query durable state
Administrators query forms, submissions and event history through protected admin endpoints, not by inspecting frontend page state.
Data model: why several tables are useful
A single submissions table is not enough once forms become workflows. Flow separates definitions, records, event history, templates, webhooks and process maps because each has a different lifecycle and access pattern.
| Table family | What it represents | Why it is separate |
|---|---|---|
| Form definitions | The structure and versioning of forms used by the frontend | A form can change while old submissions must remain understandable. |
| Submissions | Current submission state, draft/final status and core field data | This is the operational record that admin views and workflow steps query. |
| Submission events | Append-style history: created, updated, status changed, action invoked | Audit and retry behavior should not overwrite the current submission row. |
| Templates | Reusable email/template metadata | Email content changes should be managed independently from submission records. |
| Workflow definitions | Rules, actions and routing behavior | Workflow logic has its own lifecycle and should be versioned/managed explicitly. |
| Webhook endpoints | Outbound integration targets and signing settings | External systems are operational dependencies, not just form fields. |
| Process maps | Runtime mapping between processes, submissions and actions | Complex workflows need correlation state beyond a single form record. |
Security and abuse controls
The frontend API may intentionally accept anonymous traffic, because many forms are public. That does not mean it should be unprotected. The stack supports optional reCAPTCHA, WAF, IP allow/block lists and separate admin authentication so public reachability does not leak into privileged management operations.
| Control | Where it applies | Design reason |
|---|---|---|
| reCAPTCHA | Public frontend form endpoints | Reduce bot submissions before they become stored records, emails, webhooks or model/workflow costs. |
| WAF rate limits | Frontend and admin path prefixes | Throttle abuse differently for visitor-facing and admin-facing routes. |
| Admin Cognito authorizer | /admin/* routes | Keep form definitions, submissions, templates, workflows and webhook configuration behind a real identity boundary. |
| SSM/KMS secrets | reCAPTCHA secrets and webhook signing secrets | Keep shared secrets out of WordPress settings and template source. |
| GuardDuty malware protection | Payload bucket when enabled | Add a scanning option for uploaded payloads before downstream processing relies on them. |
Workflow and integration pattern
submission.created / submission.updated / action-invoked
│
▼
EventBridge rule
│
├─ Workflow Dispatcher Lambda
│ └─ evaluates workflow definitions and writes events
│
├─ Email Sender Lambda
│ └─ reads templates and sends via SES
│
└─ Webhook Dispatcher Lambda
└─ signs and delivers payloads to external systems
The key advantage is failure isolation. A visitor submit can complete once the submission is durably accepted. Email or webhook delivery can be retried, logged or inspected without pretending the original HTTP request is still alive.
Deployment parameters that change the architecture
| Parameter area | Examples | Architectural effect |
|---|---|---|
| Auth modes | FrontendApiAuthMode, AdminApiAuthMode, AdminCognitoUserPoolId, scopes | Controls whether frontend and admin surfaces are public, IAM-protected or Cognito-protected. |
| Abuse protection | EnableRecaptcha, reCAPTCHA mode/site key/threshold, EnableWAF, allowed/blocked IP lists | Determines how much anonymous traffic can reach the backend and which paths are rate-limited or allowlisted. |
| Storage ownership | TemplatesBucketName, PayloadBucketName, prefixes | Lets teams use created buckets or attach existing storage conventions. |
| Secrets | EnableKmsForSecrets, webhook signing secret, reCAPTCHA secret | Controls whether secrets are stored with a dedicated KMS key and SSM parameters. |
| Domain/DNS | ApiCustomDomainName, certificate ARN, Route53 settings | Moves the API from an execute-api URL to a branded domain when the DNS/certificate path is ready. |
| Operations | Data retention, log retention, Lambda memory/timeout/log level | Controls cost, observability and runtime headroom without changing WordPress pages. |
Operational failure modes to document
Spam
Anonymous forms can create downstream cost
reCAPTCHA and WAF should be treated as production controls, especially when forms trigger email, webhooks or AI-assisted workflows.
SES identity and template errors are runtime dependencies
Email sender failures should be visible in logs and submission events, not hidden as generic form errors.
Webhooks
External systems will fail sometimes
Webhook delivery needs signing, retry logic, clear event history and a way to distinguish transient failure from bad configuration.
Uploads
Files are larger and riskier than fields
Use S3 payload references, size/content-type validation and optional malware protection when uploads matter.
Auth
Admin APIs must not inherit frontend looseness
Cognito/IAM, scopes and IP allowlists should be documented separately from public form protections.
Retention
Submission data is not content
Set retention deliberately; forms often contain operational or personal data that should not live forever by accident.
Implementation path
- Classify forms by complexity: simple contact, long draftable form, file-upload form, workflow-trigger form or integration-heavy form.
- Decide the frontend auth mode: anonymous with WAF/reCAPTCHA, IAM, or Cognito-protected page/API flow.
- Deploy the Flow backend stack with only the protections and storage choices needed for the first production use case.
- Copy the stack outputs into the Flow plugin/admin configuration and keep the CloudFormation parameters in the site runbook.
- Define form IDs, template keys, workflow IDs and webhook keys as stable integration contracts.
- Test draft save/load/finalize separately from final submission, email and webhook dispatch.
- Add logs, alarms and manual recovery guidance before connecting critical business systems.
Related resources
Deep dive
Static WordPress with Dynamic Runtime
The broader pattern that explains why static delivery and runtime APIs should be separated.
Solution
Serverless WordPress Backend
A less technical entry point for moving runtime-heavy WordPress features to AWS.
Deep dive
AWS Deployment Wizard for WordPress
How the stack deployment model turns product choices into repeatable CloudFormation installations.
FAQ
Does this replace every WordPress form plugin?
No. Simple forms can stay simple. This architecture matters when submissions need drafts, uploads, workflow events, protected admin views, webhooks, reliable email handling or static-export compatibility.
Can a static WordPress page submit to Flow?
Yes. The page can be served as static HTML while the form component calls the Flow backend API directly from the browser.
Why separate frontend and admin endpoints?
Because public submission paths and privileged management paths have different risk profiles. The split makes auth, WAF, IP restrictions and documentation much clearer.
Is Flow only for AWS experts?
No. The architecture is AWS-native, but the point of the wizard/template approach is to hide routine provisioning while still keeping the deployed stack visible and customer-owned.
Turn WordPress forms into AWS-backed workflows
Keep Gutenberg as the form-authoring experience while submissions, drafts, uploads, emails and webhooks run in a customer-owned AWS backend.
