How to build a SurveyMonkey alternative

How to build a SurveyMonkey alternative as one page of questions, a weblink, and a CSV that leaves anonymous answers unnamed. Step-by-step prompts inside.

  • guide
  • vibe-coding
  • surveymonkey

How to build a SurveyMonkey alternative is a short page of questions, a link you can paste, and a table of the answers. SurveyMonkey is that product. The catalog line is “Ask clear questions. Get a table. Make a decision.”

You pay $39–$75 a person a month. The card scales with the team. The first estimate is 4 hours. The verdict is “Most surveys are simple”.

The SurveyMonkey post is the teardown. It maps collectors, anonymity, and branching. This guide builds the 4-hour row, with the screens and the prompts in order.

One question at a time is the Typeform guide, at $29–$99 a month, about 2 hours. A document of blocks is the Tally guide, at $29–$89 a month, about 8 hours. Votes on a feature request are Canny, at $79–$99 a month. Askit is the page and the CSV.

How to build a SurveyMonkey alternative as a page and a CSV

The SurveyMonkey page splits the work into three rows.

The first row is 4 hours. A question form, a response table, and a CSV export. This guide keeps that row. The catalog names the project Askit.

The second row is 12 hours. Skip logic, NPS, charts, and a shareable results link. The third row is a month and up, 160 hours. Respondent panels, quotas, statistical analysis, and enterprise compliance. The non-goal names panel recruitment, advanced statistics, quotas, and payment collection together.

If you tell an agent to “clone SurveyMonkey”, it aims at the third row. Paste the prompts below. One survey. One page. One weblink.

The example is Oak onboarding. Ada owns it. Question 1 is “Which plan did you start on?”, required, with Starter and Team. Question 2 is “What almost stopped you?”, free text, optional. Cam answers Team and “The price page was unclear.”

Where the page and the table split

Every product prompt ships in two flavors: Node.js plus Astro, and TanStack Start. The card lists Astro, HTMX, and D1. This site is an Astro app on Cloudflare, so that is the default here. Use TanStack Start if React is your weekday language. The stack post is which tab you copy. The prompt post is how you pin the first estimate.

The browser shows a builder, a public survey, and a results table. Astro on the request saves the draft, freezes a version on Open, and writes one response on submit. D1 holds the survey, the questions, the choices, the responses, and the answers. The CSV reads those rows on the same request. Nothing in this v1 waits on a cron.

TanStack Start keeps the screens, with server functions and a SQL database. The public survey is still one route that stores one response.

BROWSER Builder, public page, table The link needs no login ASTRO ON THE REQUEST Freeze a version on Open One token, one response D1 Questions, choices, answers CSV ON THE REQUEST Labels from that version No identity when anonymous OUTSIDE V1 Email, SMS, and popup links Skip logic, NPS, and charts Panels, quotas, payments
Open freezes the page. Submit writes one response. The CSV reads those rows.

What one answer carries

The card lists Survey, Question, Choice, Response, and Answer. This v1 uses all five. Charts and skip rules stay on the 12-hour row.

A survey has a title, a status, and a mode. Status is draft, open, or closed. Mode is anonymous or identified, and it is frozen with the version. Anonymous stores no email, no invitation id, and no IP address. The SurveyMonkey post notes that the real product still keeps IP addresses in backend logs and deletes them after 13 months. Askit does not write that log. Identified mode stores the email the person types, and still stores no IP.

A question belongs to one version. It has text, an order, a required flag, and a family. single has choices. text stores what they typed. The plan question is required. The comment is optional.

A choice has a stable id and a label. Starter is plan-starter. Team is plan-team. The answer stores the id. The table prints the label from the version on that response, so a later reorder leaves old rows alone.

A response has the token, a status of completed, a time, and the version id. The page receives the token when it loads. The same token on a second submit returns thank-you and writes nothing new. An answer points at one response and one question. Cam’s row is the choice plus the comment.

const answer = {
  choiceId: 'plan-team',
}
OAK ONBOARDING Open · anonymous · v1 QUESTION 1 Which plan? Starter · Team Required QUESTION 2 What stopped you? Free text Optional RESPONSE sub-oak-cam v1 · completed · no email plan-team + the comment VERSION 2 New labels, old row stays
The answer points at plan-team on version 1. A later label edit does not move it.

The screens you ship

Three surfaces. The builder and the table are signed in. The survey page is public.

The builder is /surveys/oak-onboarding. It shows the title, the status, and the mode. Under that, the two questions in order. Save writes the draft. Open stays disabled until there is one required question and the mode is chosen. Copy link appears after Open, and it copies the weblink only.

WIREFRAME · BUILDER Oak onboarding Draft Anonymous 1 · Which plan? · required Starter · Team 2 · What stopped you? · optional Save Open Copy link appears after Open
The mode sits next to the status. Open is what freezes version 1.

The public page is /s/oak-onboarding. It shows the title, a one-line privacy note, both questions, and Submit. Anonymous mode has no email field, and the note says so. Identified mode shows the email field and says that address is stored with the answers. A missing plan stays on the page with the error under question 1. A good submit ends on thank-you, and the token is spent. A closed survey shows a closed message and hides the form.

WIREFRAME · PUBLIC No login Oak onboarding No email and no IP stored 1 · Which plan? · required Starter Team selected 2 · What stopped you? The price page was unclear. Submit
Anonymous mode has a privacy line and no email field. Team is the selected choice.

The results table is /surveys/oak-onboarding/responses. One row per completed response. Columns are the time, the plan label, and the comment. Anonymous surveys have no email column. Export downloads a CSV of the same columns. A cell that starts with =, +, -, or @ is prefixed so a comment cannot become a spreadsheet formula.

WIREFRAME · RESULTS Oak onboarding · 1 row Anonymous · no email column Time Plan Comment 4 Nov Team The price page was unclear. Export CSV
The plan column is the label Team. The file has the same columns as this table.

The path from Open to a CSV row

1 · ADA SAVES THE DRAFT Two questions, anonymous 2 · OPEN FREEZES V1 The weblink starts working NO PLAN Stays on the page CAM SUBMITS Team + comment 3 · SAME TOKEN AGAIN Thank-you, still one row 4 · ADA EXPORTS Time, Team, the comment 5 · CLOSE Row stays NEW VISIT Closed page 6 · RENAME TEAM TO STUDIO Publish writes version 2 Cam's CSV cell still says Team New answers use Studio
The token stops the double click. The version stops a rename from rewriting Cam.

Ada opens the draft, Cam submits Team once, and the export shows that label with no email column. Close keeps the row and blocks a new post. Renaming Team to Studio publishes version 2, and Cam’s cell stays Team.

Prompt 1. A draft with two questions

Build Askit, a survey with
one page of questions.

Use Node.js and Astro.
Server-rendered pages.
Ordinary HTML forms.
Cloudflare D1 for the rows.
If you use TanStack Start
instead, use server functions
and a SQL database with
migrations. Same screens.

Ada is the only owner.
The survey list shows title,
status, and response count.
Create Oak onboarding as
a draft.

The builder edits the title,
the mode, and the questions.
Mode is anonymous or
identified. Families are
single choice and free text.
Seed question 1 as
"Which plan did you start
on?", required, choices
Starter (plan-starter) and
Team (plan-team). Seed
question 2 as "What almost
stopped you?", optional text.

Save writes the draft.
The public URL shows a
not-open page while status
is draft. A signed-out
visitor cannot open the
builder.

No skip logic. No charts.
No second page. No payments.

Done when both questions
save, reload in order, and
the public URL refuses
a submit.

What to check after the builder

Create the survey and reload. Question 1 is still required, and the choice ids are still plan-starter and plan-team.

Open /s/oak-onboarding in a private window. You see a not-open message. Posting the form does not insert a response.

Sign out and request the builder. The response is a login wall, and the HTML does not include the question text.

Add Open on the builder.

Open requires a chosen mode
and at least one required
question. It sets status to
open and freezes version 1:
title, mode, questions,
choice ids, and labels.

The public page at
/s/oak-onboarding shows the
title, a privacy note, both
questions, and Submit.
Anonymous mode has no email
field. The note says the
response stores no email
and no IP address.
Identified mode shows an
email field and says that
address is stored.

Issue a submission token
when the page loads. A
completed response is unique
on that token. A repeat
submit shows the thank-you
page and writes no second
response.

Validate on the server.
A missing required choice
stays on the page with an
error under that question.
A choice id must belong to
the frozen question. Show a
text limit under question 2
and reject anything longer.
Store no IP address on
either mode.

Done when Cam can submit
Team and the comment once,
and a second post with
sub-oak-cam adds nothing.

What to check after the first submit

Submit with no plan. The page names question 1, and the count stays 0. Submit Team and the comment, then submit the same token again. You have one response, two answers, version 1, and no email stored. View source on the public page and confirm there is no email input.

Prompt 3. A table and a CSV

Add the results table at
/surveys/oak-onboarding/responses.

One row per completed
response, newest first.
Columns are completed time,
then one column per question
in version order. Choice
cells show the label from
the response's version.
Text cells show the text.

Anonymous surveys omit any
email column. Identified
surveys include the email
the person typed. Never add
an IP column.

Export CSV uses the same
columns and the same labels.
Prefix a cell that starts
with =, +, -, or @ so a
comment cannot run as a
spreadsheet formula.

The owner is the only person
who can open the table or
the file. A signed-out
request is denied and the
body does not include
answers.

Done when the sample export
contains Team and the
comment, and contains no
email header.

What to check after the export

Open the CSV in a text editor. The header is time, the plan question, and the comment. The row contains Team and The price page was unclear.

Submit a comment of =1+1. The exported cell starts with a prefix, so the spreadsheet sees text. Request the file signed out and confirm the body is the login wall, not the CSV.

Prompt 4. Close, and keep old labels

Add Close and Publish
new version.

Close sets status to closed.
The public page shows a
closed message and renders
no questions. Existing
responses stay exportable.
Open again is allowed and
uses the current version.
It does not rewrite rows.

Editing questions, labels,
or the mode after version 1
exists does not change that
version. Publish new version
freezes version 2 and points
the public page at it.
Responses keep the version
they were submitted against.
The table looks up labels
on that version.

Renaming plan-team from
Team to Studio on version 2
leaves Cam's cell as Team.

A mode change is a new
version too. Old anonymous
rows gain no email column.

Done when Cam's export still
says Team after the rename,
and a closed link rejects
a new post.

What to check after a new version

Close the survey and load the public URL in a private window. The form is gone, and Cam’s CSV still downloads. Publish version 2 with the label Studio. Cam’s plan cell is still Team, and a new Studio answer stores version 2. Cam’s row still has no email.

Failures the agent ships anyway

An IP address on an anonymous row

The privacy note says the row has no IP, and the table still has a column called ip or respondent. Drop it, including any copy kept off the page. The teardown already separates SurveyMonkey’s results setting from their 13-month log. Askit skips the log.

Choice cells that follow the display order

The CSV says 1 because Team was the second radio. Ada reorders the choices and old files change meaning. Store plan-team, and print the label from the version on the response.

Two rows for one click

The thank-you page is slow, Cam clicks again, and the count becomes 2. The token needs a unique constraint that the insert and the retry both hit. One token, one completed response.

A rename that rewrites history

You change Team to Studio and every old CSV cell updates. The answer stored the label string, or the table joins the live choice. Point each response at a version and read labels from that snapshot.

A chart, a branch, or a bought panel

The builder grows an NPS block, a skip rule, or a field for bought respondents. Charts and branching are the 12-hour row. Panels and quotas are the month row. The SurveyMonkey post already separates the weblink from email, SMS, and popup collectors. Delete those screens.

Deploy the survey

Askit wants Astro and D1. There is no cron and no file bucket in this v1. The hosting post is the adapter, the D1 binding, and the domain. The public path can stay /s/oak-onboarding.

Before you paste the link into a real thread, submit once in a private window and read the CSV. Confirm the plan label, the comment, and no email or IP column. Close the survey, confirm a new visit cannot post, then open it again and confirm Cam’s row is unchanged.

Run one real survey on it. Charts and branches are the 12-hour row. After that, cancel the SurveyMonkey seats you no longer open, at $39–$75 a person a month.

What to skip on purpose

Leave out skip logic, NPS, charts, a public results link, email invitations, panels, quotas, statistical analysis, and payments.

THIS V1 One page, two questions A weblink and one token A CSV with version labels SKIP THIS HALF Branching, NPS, and charts Email and SMS collectors Panels, quotas, payments
Ship the page and the file. Leave the research platform on the later rows.

Start now

Go to Build your own SurveyMonkey. Copy the Astro prompt, or TanStack Start if that is what you write.

Tell the agent the first estimate, plus the version on each response. A question form, a response table, and a CSV. Leave the branch rules out.

Open Oak onboarding as anonymous, submit one answer, and read the CSV before you add a chart.

Related articles

Read all articles →