Export Typeform to monday.com: Native Integration, Zapier, or the API
To export Typeform to monday.com, start with monday’s own Typeform integration: open the board, click Integrate, pick a Typeform recipe, connect both accounts, and map each question to a column. From then on every new submission becomes an item. That covers most teams. You need something else in three cases: you want to move responses that already exist, your form uses question types the integration does not carry, or some of the “responses” are scans and PDFs that never touched Typeform. This guide walks through each route and where it stops working.
If you are after raw response data rather than a board, the Export Typeform to JSON guide covers the Responses API and CSV exports in detail. This post is about getting answers into monday.com items.
Route 1: the native monday.com Typeform integration
monday.com documents the setup in its Typeform integration article. The flow is short:
- Open the board that should receive responses and click Integrate in the top right.
- Search the Automation center for Typeform and choose a recipe, for example one that creates an item when a form is submitted.
- Connect your monday.com account to your Typeform account.
- Pick the form and map each Typeform question to a board column.
monday adds an integration column to the board automatically to keep the link between the two apps. Typeform also has a guide for connecting an existing form to a board, and a separate monday.com app view that generates a new typeform from inside the board. Typeform notes that monday.com is a partner-supported integration, so support questions go to monday.
What the native integration will not do
Read the limits before you rely on it:
- Some question types do not map. monday’s page lists Logic Jumps, Multiple Choice, Statement, Question Group, Hidden fields, Video, Payment, and Ranking as unsupported, and a note on the same page also names Phone, Dropdown, and Picture. The lists are not fully consistent, so build a test form with your real question types and submit it once before going live.
- Partial submissions are not updated. If you use Typeform partial submissions, monday gets the first version only. A later edit by the respondent does not change the item.
- It is forward-looking. The recipe fires on new submissions. Responses collected before you connected the board are not imported.
If any of those is a deal breaker, move to one of the routes below.
Route 2: Zapier or another automation tool
Typeform lists Zapier templates for monday.com, such as creating a monday.com group when someone completes a typeform. An automation tool is worth it when you need steps in between: look up an existing item before creating a duplicate, split one submission into several items, or format a multiple choice answer into a text column that the native recipe will not fill.
The trade-off is another account, another place where the mapping lives, and task-based pricing on the automation side. It also still only reacts to new submissions unless you build a separate backfill.
Route 3: Typeform webhook plus the monday.com API
When you want full control over mapping, deduplication, and backfill, write the glue yourself. It is less code than it sounds.
Typeform sends each submission to your URL through webhooks. Each answer carries the question ref and a value keyed by type (text, email, date, choice, and so on). monday.com accepts items through one GraphQL endpoint, https://api.monday.com/v2, with the create_item mutation. The items reference shows that column_values is a JSON string keyed by column ID, and the column values guide explains which column types take simple strings and which need objects.
A minimal mapping step in Python:
import json
import os
import requests
MONDAY_TOKEN = os.environ["MONDAY_TOKEN"]
BOARD_ID = "1234567890"
# Typeform question ref -> monday.com column id
COLUMN_MAP = {
"company": "text_company",
"work_email": "email_contact",
"start_date": "date_start",
"plan": "status_plan",
}
def answer_value(answer):
kind = answer["type"]
if kind == "choice":
return answer["choice"].get("label")
if kind == "choices":
return ", ".join(answer["choices"].get("labels", []))
return answer.get(kind)
def to_column_values(form_response):
values = {}
for answer in form_response["answers"]:
column_id = COLUMN_MAP.get(answer["field"]["ref"])
if not column_id:
continue
value = answer_value(answer)
if column_id.startswith("email_"):
values[column_id] = {"email": value, "text": value}
elif column_id.startswith("date_"):
values[column_id] = {"date": value}
elif column_id.startswith("status_"):
values[column_id] = {"label": value}
else:
values[column_id] = value
return values
def create_item(item_name, column_values):
query = """
mutation ($board: ID!, $name: String!, $values: JSON!) {
create_item(board_id: $board, item_name: $name,
column_values: $values, create_labels_if_missing: true) { id }
}"""
variables = {
"board": BOARD_ID,
"name": item_name,
"values": json.dumps(column_values),
}
r = requests.post(
"https://api.monday.com/v2",
json={"query": query, "variables": variables},
headers={"Authorization": MONDAY_TOKEN},
timeout=30,
)
r.raise_for_status()
body = r.json()
if body.get("errors"):
raise RuntimeError(body["errors"])
return body["data"]["create_item"]["id"]
Four details that save debugging time:
- Stringify only
column_values. The variable itself must be a JSON string, but the rest of the request body is normal JSON. Do not run the whole variables object throughjson.dumpsa second time. - Use column IDs, not titles. “Work email” on the board might be
email_mkx3a9under the hood. Query the board’s columns once and store the IDs. - Match the column type. Email columns want
emailandtextkeys, as the email column reference shows. Date columns want{"date": "YYYY-MM-DD"}. Status columns accept a label, andcreate_labels_if_missingadds a new label instead of failing (it needs permission to change the board structure). - Verify the webhook. Set a secret on the Typeform webhook and check the signature before you write anything to the board.
Backfilling responses you already have
The same mapping handles history. Pull past submissions with Typeform’s Responses API, page through them, and pass each one to create_item. Store the Typeform response_id in a text column so a rerun can skip items it already created. This is the step the native integration and most Zapier templates do not give you.
When the form never reached Typeform
Plenty of intake still arrives on paper or as PDFs: a printed copy of the survey filled in at an event, a partner who sent back a scanned form, a screenshot forwarded by email. There is no Typeform response_id for those, so no recipe will see them. If they need to land on the same board, read the page into the same field shape and reuse create_item.
With OCRskill you declare the keys you want and get JSON back:
curl "https://api.ocrskill.com/ocr.json?fields=last_name,first_name,email,company_name?" \
-H "Authorization: Bearer sk-your-key-here" \
-F "file=@signup-form-scan.jpg"
Required fields fail with a 400 when the page cannot support them, so an unreadable scan does not create a half-empty item. Optional keys end in ?. Map the returned keys to your monday column IDs the same way COLUMN_MAP does for Typeform refs, and tag those items with a source column so reviewers know they came from a scan. The form data extraction API post covers form OCR in more depth, and the OCR JSON API reference lists the supported field names.
Which route to pick
| Situation | Route |
|---|---|
| New submissions, simple question types | Native monday.com integration |
| Extra steps, lookups, or reformatting | Zapier or a similar automation tool |
| Unsupported question types, deduplication, or history | Webhook plus create_item, with a Responses API backfill |
| Paper, PDF, or screenshot forms | OCR to JSON, then the same create_item call |
Conclusion
The native integration is the right default, but it only moves new, fully supported submissions and it never revisits partial ones. Before you commit to it, submit a test response with every question type you use and check the board. If anything is missing, the webhook route is a few dozen lines of code, gives you a backfill for free through the Responses API, and lets scanned forms join the same board once OCR turns them into the same keys. Keep the Typeform response_id or a scan filename on every item, and reruns stay safe.
