[{"data":1,"prerenderedAt":425},["ShallowReactive",2],{"blog-guides/schema-on-write":3},{"id":4,"title":5,"body":6,"category":397,"date":398,"dateModified":399,"description":400,"draft":401,"extension":402,"faq":403,"featured":401,"keywords":411,"meta":412,"navigation":413,"ogDescription":414,"ogTitle":415,"path":416,"readTime":417,"schemaOrg":399,"schemaType":418,"seo":419,"sitemap":420,"stem":421,"tags":422,"twitterCard":423,"__hash__":424},"blog/blog/guides/schema-on-write.md","Schema-on-Write: Why Your Forms Should Infer Their Own Structure",{"type":7,"value":8,"toc":386},"minimark",[9,13,16,19,30,35,38,51,54,65,84,90,94,97,100,103,106,112,116,122,125,135,138,146,162,167,170,185,188,192,198,201,212,225,234,238,315,318,361],[10,11,12],"p",{},"You open a form builder, drag in eight fields, pick dropdown options, configure validation rules, and save. Then you realize you want to collect one piece of information the tool does not support as a built-in type. You look for a custom field option. It is behind a paid plan. Forty minutes in, you have not collected a single response.",[10,14,15],{},"This is the schema-on-read problem: you define the structure first, then collect data that fits it.",[10,17,18],{},"OperatorStack forms flip this. Submit data, and the schema builds itself.",[20,21,22],"tldr",{},[10,23,24,25,29],{},"Schema-on-write means structure emerges from what you submit, not from a builder UI. POST any JSON payload to ",[26,27,28],"code",{},"POST /v1/f/{form_key}",", and OperatorStack infers field names and types automatically. Add a field to your HTML and it appears in the dashboard on the next submission. No reconfiguration, no plan upgrades.",[31,32,34],"h2",{"id":33},"what-schema-on-write-means","What Schema-on-Write Means",[10,36,37],{},"Traditional form tools follow a schema-on-read pattern:",[39,40,41,45,48],"ol",{},[42,43,44],"li",{},"Define fields in a builder (names, types, validation)",[42,46,47],{},"Collect submissions that match that schema",[42,49,50],{},"Query data that already fits the structure you declared",[10,52,53],{},"Schema-on-write reverses steps 1 and 2:",[39,55,56,59,62],{},[42,57,58],{},"Submit data",[42,60,61],{},"The system infers field names and types from what arrived",[42,63,64],{},"The schema is stored alongside the data, known and consistent going forward",[10,66,67,68,71,72,75,76,79,80,83],{},"The first time you submit ",[26,69,70],{},"{\"plan\": \"pro\"}",", OperatorStack records that ",[26,73,74],{},"plan"," is a text field. The second time you submit ",[26,77,78],{},"{\"plan\": \"free\", \"company\": \"Acme\"}",", ",[26,81,82],{},"company"," is added to the schema. Old submissions show it as empty. The schema grows without any dashboard action.",[85,86,87],"info-box",{},[10,88,89],{},"Schema-on-write is not schema-less. Your data is typed and queryable from day one. The difference is who infers the types: a human clicking through a builder UI, or the system reading what you actually sent.",[31,91,93],{"id":92},"why-this-matters-during-pre-launch","Why This Matters During Pre-Launch",[10,95,96],{},"Early-stage data collection has a specific shape: you do not yet know what questions to ask.",[10,98,99],{},"You launch a beta signup form with name, email, and \"what problem are you trying to solve?\" Two weeks in, you want to add \"what tool are you using now?\" You add the input to your HTML. The next visitor who submits gives you that field automatically. That is the full workflow.",[10,101,102],{},"Schema-on-read tools make this a four-step detour: open the builder, add the field, configure validation, deploy. For a founder iterating weekly on their landing page, that friction is not theoretical -- it is twenty minutes of tool maintenance per change.",[10,104,105],{},"The speed difference compounds. Over six weeks of validation, schema-on-write lets you run ten field iterations where schema-on-read allows four.",[107,108,109],"tip-box",{},[10,110,111],{},"Schema-on-write is ideal when you are still learning what information matters. Once you have settled on a stable form structure that needs validation rules or conditional logic, a dedicated form builder may be the right tool.",[31,113,115],{"id":114},"how-it-works-in-operatorstack","How It Works in OperatorStack",[10,117,118,119,121],{},"Every OperatorStack project gets one endpoint per form: ",[26,120,28],{},". The endpoint accepts JSON, form-urlencoded, and multipart.",[10,123,124],{},"A plain HTML form submission:",[126,127,133],"pre",{"className":128,"code":130,"language":131,"meta":132},[129],"language-html","\u003Cform method=\"POST\" action=\"https://api.operatorstack.dev/v1/f/frm_abc123\">\n  \u003Cinput type=\"text\" name=\"name\" placeholder=\"Name\" />\n  \u003Cinput type=\"email\" name=\"email\" placeholder=\"Email\" />\n  \u003Ctextarea name=\"problem\" placeholder=\"What are you trying to do?\">\u003C/textarea>\n  \u003Cinput type=\"hidden\" name=\"_redirect\" value=\"https://yoursite.com/thanks\" />\n  \u003Cbutton type=\"submit\">Submit\u003C/button>\n\u003C/form>\n","html","",[26,134,130],{"__ignoreMap":132},[10,136,137],{},"The same submission via the SDK:",[126,139,144],{"className":140,"code":142,"language":143,"meta":132},[141],"language-javascript","await OperatorStack.ready;\n\nawait OperatorStack.submitForm(\"frm_abc123\", {\n  name: \"Jane\",\n  email: \"jane@example.com\",\n  problem: \"I want to track which referrers actually convert\",\n});\n","javascript",[26,145,142],{"__ignoreMap":132},[10,147,148,149,79,152,79,155,158,159,161],{},"After the first submission, your dashboard shows three columns: ",[26,150,151],{},"name",[26,153,154],{},"email",[26,156,157],{},"problem",". Add a ",[26,160,82],{}," field to the HTML and submit again. Four columns. No dashboard visit required.",[163,164,166],"h3",{"id":165},"reserved-fields","Reserved Fields",[10,168,169],{},"Fields prefixed with an underscore are metadata and are not stored:",[171,172,173,179],"ul",{},[42,174,175,178],{},[26,176,177],{},"_redirect"," -- the URL to send the visitor to after submission",[42,180,181,184],{},[26,182,183],{},"_subject"," -- a label for categorizing the submission",[10,186,187],{},"Everything else is stored as form data and added to the schema if it is new.",[31,189,191],{"id":190},"where-the-data-goes","Where the Data Goes",[10,193,194,195,197],{},"When a submission includes an ",[26,196,154],{}," field, OperatorStack creates or updates a unified Contact record. A person who joins your waitlist, submits a feedback form, and sends a contact message appears as one contact in your Audience tab, not three separate records across three tools.",[10,199,200],{},"The form endpoint and the waitlist endpoint share the same Contact model. Both write to the same unified contact list.",[202,203,205],"step",{"number":204},"1",[10,206,207,211],{},[208,209,210],"strong",{},"Visitor submits a form."," The endpoint receives the payload, infers field types from the field names and values, and stores the submission with the inferred schema.",[202,213,215],{"number":214},"2",[10,216,217,220,221,224],{},[208,218,219],{},"Email field detected."," OperatorStack calls ",[26,222,223],{},"upsert_contact()"," with the project ID and email. If the contact already exists, the form submission links to it. If not, a new contact is created.",[202,226,228],{"number":227},"3",[10,229,230,233],{},[208,231,232],{},"Dashboard updates."," The Forms tab shows the new submission with all fields. If a field is new, it appears as a column going forward, and old submissions show it as empty.",[31,235,237],{"id":236},"schema-on-write-vs-traditional-form-builders","Schema-on-Write vs. Traditional Form Builders",[239,240,241,256],"table",{},[242,243,244],"thead",{},[245,246,247,250,253],"tr",{},[248,249],"th",{},[248,251,252],{},"Schema-on-Write (OperatorStack)",[248,254,255],{},"Traditional Form Builder",[257,258,259,271,282,293,304],"tbody",{},[245,260,261,265,268],{},[262,263,264],"td",{},"Add a field",[262,266,267],{},"Edit HTML, submit once",[262,269,270],{},"Open builder, add field, save, deploy",[245,272,273,276,279],{},[262,274,275],{},"Custom field types",[262,277,278],{},"Any JSON-serializable value",[262,280,281],{},"Limited to the tool's type list",[245,283,284,287,290],{},[262,285,286],{},"Cost for custom fields",[262,288,289],{},"Included",[262,291,292],{},"Often gated behind paid plans",[245,294,295,298,301],{},[262,296,297],{},"Submission format",[262,299,300],{},"JSON, form-urlencoded, multipart",[262,302,303],{},"Tool-specific",[245,305,306,309,312],{},[262,307,308],{},"Contact unification",[262,310,311],{},"Automatic",[262,313,314],{},"Requires integration or manual export",[10,316,317],{},"Schema-on-write does not handle every case. If you need conditional logic, multi-step flows, or strict validation that blocks bad submissions, a dedicated form builder is more appropriate. For early-stage data collection where iteration speed matters more than strict validation, schema-on-write wins.",[319,320,321,334,340,346,355],"faq-section",{},[322,323,325],"faq-item",{"question":324},"What is schema-on-write?",[10,326,327,328,330,331,333],{},"Schema-on-write means a system infers data structure from what you submit, rather than requiring you to define the structure up front. In OperatorStack's forms, the first submission that includes a field named ",[26,329,82],{}," causes ",[26,332,82],{}," to appear as a column in your dashboard. You did not declare it beforehand.",[322,335,337],{"question":336},"What is the difference between schema-on-write and schema-on-read?",[10,338,339],{},"Schema-on-read stores raw data and applies a schema when you query it. Schema-on-write infers and records the schema at write time, so field types are known and queryable in the dashboard immediately. OperatorStack uses schema-on-write so form data is typed and usable without post-processing.",[322,341,343],{"question":342},"Can I add fields to a form without reconfiguring the dashboard?",[10,344,345],{},"Yes. Add an input to your HTML and the next submission automatically adds that field to the schema. Old submissions without the new field show it as empty. No dashboard changes required.",[322,347,349],{"question":348},"Does schema-on-write work with plain HTML forms?",[10,350,351,352,354],{},"Yes. The form endpoint at ",[26,353,28],{}," accepts standard HTML form submissions, JSON, and multipart. No JavaScript or SDK required.",[322,356,358],{"question":357},"What happens to old submissions when I add a new field?",[10,359,360],{},"Old submissions keep their data intact. The new field appears as empty for those records. The schema only grows forward -- fields are never removed when a submission omits them.",[362,363,364,365],"content-related-articles",{},"\n  ",[366,367,364,371],"contentrelatedcard",{"href":368,"title":369,"description":370},"/blog/how-to/custom-forms-without-builder","How to Add Custom Forms Without a Form Builder","Step-by-step: create a form, get a form key, and collect submissions with plain HTML or the SDK.",[366,372,364,376],{"href":373,"title":374,"description":375},"/blog/guides/unified-contact-list","One Contact List to Rule Them All","Every form submission with an email field automatically creates or updates a unified contact record.",[366,377,381],{"href":378,"title":379,"description":380},"/blog/guides/embed-script-architecture","Embed Script Architecture: How One Script Tag Powers 10 Tools","How os.js loads, isolates widgets with Shadow DOM, and exposes the SDK that drives schema-on-write forms.",[382,383],"cta-box",{"href":384,"label":385},"/","Get Started Free",{"title":132,"searchDepth":387,"depth":387,"links":388},2,[389,390,391,395,396],{"id":33,"depth":387,"text":34},{"id":92,"depth":387,"text":93},{"id":114,"depth":387,"text":115,"children":392},[393],{"id":165,"depth":394,"text":166},3,{"id":190,"depth":387,"text":191},{"id":236,"depth":387,"text":237},"guides","2026-05-29",null,"Schema-on-write means your form's field structure emerges from the data you submit, not from a builder UI. Here is why that matters for early-stage products and how OperatorStack implements it.",false,"md",[404,406,407,408,410],{"question":324,"answer":405},"Schema-on-write means a system infers data structure from what you submit, rather than requiring you to define the structure up front. In OperatorStack's forms, the first submission that includes a field named 'company' causes 'company' to appear as a column in your dashboard. You did not declare it beforehand.",{"question":336,"answer":339},{"question":342,"answer":345},{"question":348,"answer":409},"Yes. The form endpoint at POST /v1/f/{form_key} accepts standard HTML form submissions, JSON, and multipart. No JavaScript or SDK required.",{"question":357,"answer":360},"schema-on-write,form schema,dynamic form fields,form builder alternative,json form api,custom forms no backend",{},true,"Skip the form builder config. Submit JSON and the schema builds itself. Here is how schema-on-write works and why it beats traditional form tools during validation.","Schema-on-Write Explained for Founders","/blog/guides/schema-on-write","6 min","Article",{"title":5,"description":400},{"loc":416},"blog/guides/schema-on-write",[],"summary_large_image","-k0azihiSU4TkwjdVceok2sjhHG_ATaP9abtpW4kvA8",1780165505739]