Build an internal feedback and survey tool without Typeform
· Avery NXR
Your company is paying for Typeform. You're using 4 percent of the features. You're paying for the part you don't use because the part you do use is bundled with everything else.
Most teams need five things from a survey tool: a form builder, conditional logic, response storage, a simple dashboard, and role-based access. That is a CRUD app. The reason it doesn't feel like a CRUD app is because Typeform's onboarding is excellent and the alternative (build your own) used to take months.
It doesn't take months anymore. With Avery, an internal feedback and survey tool that covers your daily use case is a two-day build. You own the data, you customize the workflow, and the bill goes from a recurring line item to a one-time effort.
This post walks through the actual build. The data model, the CRs you send to Avery, the integration points, and the parts that surprise people.
The case for building it custom
The Typeform pricing model scales with submissions. The more your team uses the tool, the more you pay. That is the opposite of what you want from internal software. Internal usage should reward the team, not the vendor.
There is also a subtler problem. Typeform was designed for external surveys, like customer feedback and job applications. Its UX optimizes for "one form, many respondents." Internal use cases often work in the opposite direction. One respondent (a manager) fills out the same form for multiple subjects (employees, customers, vendors). Typeform handles this badly because it wasn't built for it.
Custom solves both problems. You stop paying per submission and you can shape the form flow around how your team actually uses it.
The objection has always been time. Building a form engine from scratch was a week of work for an engineer who had better things to do. With Avery, the engineer time drops to coordination time. The build itself happens in CRs while you sleep.
What your internal feedback tool actually needs
Strip away the marketing surface area and the feature requirements are:
A form schema. Each form is a collection of fields with types (text, multi-select, rating, date, file upload). Each field has validation rules and optional conditional logic ("show this field only if the previous answer was yes").
Response storage. Every submission is a row tied to a form, a submitter, and a timestamp. The responses should be queryable and exportable.
A simple dashboard. The people who own a form should see all responses, filter by date or submitter, and export to CSV.
Role-based access. HR sees HR forms. Managers see their team's responses. The CEO sees aggregates, not individuals.
Optional but useful: notifications. When someone submits a form, the owner gets an email. When a response triggers a condition (poor rating, urgent flag), the owner gets a Slack ping.
That is the entire scope. Eight to ten Avery CRs, two days of focused work.
The Avery build path
I'll walk through the exact CR breakdown that worked for me.
CR 1: Form schema model. This is the foundation. The CR prompt I sent to Avery:
"Build a Form model with fields: id, name, description, slug (unique), created_at, owner_id. Build a FormField model with fields: id, form_id (FK), label, type (enum: text, textarea, single_select, multi_select, rating_1_5, date, file_url), required (boolean), order, options (JSON for select types), conditional_show (JSON: depends_on_field_id and depends_on_value). Add migrations and types."
Avery PRs the model, the migration, and the TypeScript types. Review, merge.
CR 2: Form builder UI. Avery generates a simple admin interface where you can create a form, add fields, configure conditional logic. The CR prompt:
"Build an admin route /admin/forms that lists all forms and allows creating a new form. On the new form page, allow adding fields with the types defined in FormField. Use a drag-to-reorder UI for field order. Save changes to the database via API."
This is the longest CR. Avery will produce a working UI that lets you create forms without writing code.
CR 3: Response storage. Each form submission goes into a Response table with the answers as JSON. CR prompt:
"Build a Response model with form_id (FK), submitter_id (nullable for anonymous), submitted_at, answers (JSON: field_id -> value). Build a public form route /f/[slug] that renders the form for submission and POSTs the response."
CR 4: Owner dashboard. Form owners need to see and filter responses. CR prompt:
"Build /admin/forms/[id]/responses that shows a table of all responses for that form. Allow filtering by date range and exporting to CSV. Show each response's field-by-field answers when expanded."
CR 5: Role-based access. Forms have owners. Owners can configure who else can view. CR prompt:
"Add a FormPermission model: form_id, user_id, role (enum: owner, editor, viewer). Update the admin routes to check permissions. Update the public form route to allow anonymous submission only if form.allow_anonymous is true."
CR 6: Email notifications. When a response comes in, ping the owner. CR prompt:
"When a Response is created, send an email to the form owner with the submitter's name (or 'anonymous') and a link to the response detail page. Use the existing email integration. Include a one-line summary of the response."
CR 7: Conditional Slack alerts. When a response triggers a condition (low rating, urgent flag), Slack-ping. CR prompt:
"Add a SlackAlert config per form: trigger_field_id, trigger_condition (enum: equals, less_than, contains), trigger_value, slack_webhook_url. On response creation, evaluate alerts and POST to Slack if triggered."
CR 8: Polish and admin landing page. Cleanup CR. Admin overview, recent activity, empty states.
That is the entire build. Eight CRs, two days if you are focused, three if you take it slow.
Things that surprise people
The form builder UI is what most people assume will be the slow part. It isn't. Avery handles it in one CR because it is mostly schema-driven UI, which is exactly the kind of code AI writes well.
The conditional logic is what people assume will be easy. It isn't. Conditional show/hide is straightforward, but conditional skip logic (skip to question 7 if answer to question 3 is X) gets messy. I'd recommend skipping that for v1. Most teams don't need it.
File upload is a real CR by itself. If your forms need file upload (employee uploads a document, customer attaches a screenshot), budget for a separate CR with S3 integration. Avery handles it cleanly but it's not free.
Anonymous responses are a permissions question, not a technical one. Decide upfront whether you want anonymity. Some companies do (engagement surveys). Some don't (manager evaluations). The data model handles both, but you need to lock the policy before you ship.
The data ownership angle
The biggest difference between Typeform and a custom build is data ownership. With Typeform, the responses live in Typeform's database. You can export, but you can't query directly. You can't run a SQL join between survey responses and your employee database. You can't build a dashboard that combines survey responses with usage data.
With a custom Avery build, the responses live in your Postgres. You can join, query, build whatever dashboards you want. Six months from now when someone asks "do team members who attended training have higher satisfaction scores," you can answer with one query.
That capability isn't theoretical. It is the difference between data you own and data you rent.
When to keep using Typeform
External-facing surveys. Job applications. Customer NPS at scale. Anywhere the form needs to be public, attractive, and you don't want to manage hosting or spam.
Typeform is genuinely good at "make a beautiful form that strangers will fill out." If that is your use case, keep paying. The price is fair for what it does well.
For internal use, the cost is rarely justified. Internal users don't need a polished public-facing form. They need a fast, structured way to capture and review responses, integrated with the rest of your stack.
What this costs to run
Hosting: Vercel or Railway, ~$20/month for low-volume internal use.
Database: Supabase or Neon free tier covers small teams. Larger orgs need a paid Postgres, ~$25/month.
Email notifications: Resend free tier covers 3,000 emails per month. Beyond that, ~$20/month.
Total monthly: $20 to $80 depending on scale. Versus Typeform Business at significantly more, growing with submissions.
The custom build pays for itself within the first month of replacing Typeform's Business tier.
What to ship next (v1.1)
Once v1 is live and your team is using it, the obvious additions:
A form template library. Pre-built templates for common use cases (engagement survey, onboarding feedback, exit interview). Each template is a Form record with pre-configured fields. One CR.
Scheduled forms. Send the same survey weekly or monthly. One CR with a cron job.
Approval workflows. Some forms need approval before they go live. One CR adding an approval state.
Public link sharing. Generate a shareable link with optional password protection. One CR.
Integration with your HRIS. Pull employee data, link responses to employee records. One CR with API integration.
Each of these is a CR. None of them are urgent for the initial replacement. Ship v1 first. Live with it. Then add the v1.1 items as your team asks for them.