{"id":13724302,"url":"https://github.com/joncalhoun/form","last_synced_at":"2025-04-05T05:10:06.274Z","repository":{"id":49253989,"uuid":"139334760","full_name":"joncalhoun/form","owner":"joncalhoun","description":"Easily create HTML forms with Go structs.","archived":false,"fork":false,"pushed_at":"2024-08-13T02:28:39.000Z","size":228,"stargazers_count":397,"open_issues_count":4,"forks_count":32,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-03-29T04:11:24.018Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joncalhoun.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-01T14:15:17.000Z","updated_at":"2025-03-23T01:33:45.000Z","dependencies_parsed_at":"2024-01-06T01:03:34.937Z","dependency_job_id":"92be6ae8-b9f6-41f7-a330-0307fc9414d1","html_url":"https://github.com/joncalhoun/form","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joncalhoun%2Fform","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joncalhoun%2Fform/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joncalhoun%2Fform/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joncalhoun%2Fform/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joncalhoun","download_url":"https://codeload.github.com/joncalhoun/form/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247289429,"owners_count":20914464,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T01:01:54.094Z","updated_at":"2025-04-05T05:10:06.247Z","avatar_url":"https://github.com/joncalhoun.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# Form\n\nEasily create HTML forms with Go structs.\n\n[![go report card](https://goreportcard.com/badge/github.com/joncalhoun/form \"go report card\")](https://goreportcard.com/report/github.com/joncalhoun/form)\n[![Build Status](https://travis-ci.org/joncalhoun/form.svg?branch=master)](https://travis-ci.org/joncalhoun/form)\n[![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)\n[![GoDoc](https://godoc.org/github.com/joncalhoun/form?status.svg)](https://godoc.org/github.com/joncalhoun/form)\n\n## Overview\n\nThe `form` package makes it easy to take a Go struct and turn it into an HTML form using whatever HTML format you want. Below is an example, along with the output, but first let's just look at an example of what I mean.\n\nLet's say you have a Go struct that looks like this:\n\n```go\ntype customer struct {\n\tName    string\n\tEmail   string\n\tAddress *address\n}\n\ntype address struct {\n\tStreet1 string\n\tStreet2 string\n\tCity    string\n\tState   string\n\tZip     string `form:\"label=Postal Code\"`\n}\n```\n\nNow you want to generate an HTML form for it, but that is somewhat annoying if you want to persist user-entered values if there is an error, or if you want to support loading URL query params and auto-filling the form for the user. With this package you can very easily do both of those things simply by defining what the HTML for an input field should be:\n\n```html\n\u003cdiv class=\"mb-4\"\u003e\n\t\u003clabel class=\"block text-grey-darker text-sm font-bold mb-2\" {{with .ID}}for=\"{{.}}\"{{end}}\u003e\n\t\t{{.Label}}\n\t\u003c/label\u003e\n\t\u003cinput class=\"shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight {{if errors}}border-red{{end}}\" {{with .ID}}id=\"{{.}}\"{{end}} type=\"{{.Type}}\" name=\"{{.Name}}\" placeholder=\"{{.Placeholder}}\" {{with .Value}}value=\"{{.}}\"{{end}}\u003e\n\t{{range errors}}\n\t\t\u003cp class=\"text-red pt-2 text-xs italic\"\u003e{{.}}\u003c/p\u003e\n\t{{end}}\n\u003c/div\u003e\n```\n\nThis particular example is using [Tailwind CSS](https://tailwindcss.com/docs/what-is-tailwind/) to style the values, along with the `errors` template function which is provided via this `form` package when it creates the inputs for each field.\n\nNow we can render this entire struct as a form by simply using the `inputs_for` template function which is provided by the `form.Builder`'s `FuncMap` method:\n\n```html\n\u003cform class=\"bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4\" action=\"/\" method=\"post\"\u003e\n  {{inputs_for .Customer}}\n  \u003c!-- ... add buttons here --\u003e\n\u003c/form\u003e\n```\n\nAnd with it we will generate an HTML form like the one below:\n\n![Example output from the forms package](example.png)\n\nData set in the `.Customer` variable in our template will also be used when rendering the form, which is why you see `Michael Scott` and `michael@dunder.com` in the screenshot - these were set in the `.Customer` and were thus used to set the input's value.\n\nError rendering is also possible, but requires the usage of the `inputs_and_errors_for` template function, and you need to pass in errors that implement the `fieldError` interface (shown below, but NOT exported):\n\n```go\ntype fieldError interface {\n\tFieldError() (field, err string)\n}\n```\n\nFor instance, in [examples/errors/errors.go](examples/errors/errors.go) we pass data similar the following into our template when executing it:\n\n```go\ndata := struct {\n  Form   customer\n  Errors []error\n}{\n  Form: customer{\n    Name:    \"Michael Scott\",\n    Email:   \"michael@dunder.com\",\n    Address: nil,\n  },\n  Errors: []error{\n    fieldError{\n      Field: \"Email\",\n      Issue: \"is already taken\",\n    },\n    fieldError{\n      Field: \"Address.Street1\",\n      Issue: \"is required\",\n    },\n    ...\n  },\n}\ntpl.Execute(w, data)\n```\n\nAnd then in the template we call the `inputs_and_errors_for` function:\n\n```html\n\u003cform class=\"bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4\" action=\"/\" method=\"post\"\u003e\n  {{inputs_and_errors_for .Form .Errors}}\n  \u003c!-- ... buttons here --\u003e\n\u003c/form\u003e\n```\n\nAnd we get an output like this:\n\n![Example output from the forms package with errors](examples/errors/errors.png)\n\n\n## Installation\n\nTo install this package, simply `go get` it:\n\n```\ngo get github.com/joncalhoun/form\n```\n\n## Complete Examples\n\nThis entire example can be found in the [examples/readme](examples/readme) directory. Additional examples can also be found in the [examples/](examples/) directory and are a great way to see how this package could be used.\n\n**Source Code**\n\n```go\npackage main\n\nimport (\n\t\"html/template\"\n\t\"net/http\"\n\n\t\"github.com/joncalhoun/form\"\n)\n\nvar inputTpl = `\n\u003clabel {{with .ID}}for=\"{{.}}\"{{end}}\u003e\n\t{{.Label}}\n\u003c/label\u003e\n\u003cinput {{with .ID}}id=\"{{.}}\"{{end}} type=\"{{.Type}}\" name=\"{{.Name}}\" placeholder=\"{{.Placeholder}}\" {{with .Value}}value=\"{{.}}\"{{end}}\u003e\n{{with .Footer}}\n  \u003cp\u003e{{.}}\u003c/p\u003e\n{{end}}\n`\n\ntype Address struct {\n\tStreet1 string `form:\"label=Street;placeholder=123 Sample St\"`\n\tStreet2 string `form:\"label=Street (cont);placeholder=Apt 123\"`\n\tCity    string\n\tState   string `form:\"footer=Or your Province\"`\n\tZip     string `form:\"label=Postal Code\"`\n\tCountry string\n}\n\nfunc main() {\n\ttpl := template.Must(template.New(\"\").Parse(inputTpl))\n\tfb := form.Builder{\n\t\tInputTemplate: tpl,\n\t}\n\n\tpageTpl := template.Must(template.New(\"\").Funcs(fb.FuncMap()).Parse(`\n\t\t\u003chtml\u003e\n\t\t\u003cbody\u003e\n\t\t\t\u003cform\u003e\n\t\t\t\t{{inputs_for .}}\n\t\t\t\u003c/form\u003e\n\t\t\u003c/body\u003e\n\t\t\u003c/html\u003e`))\n\thttp.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\tpageTpl.Execute(w, Address{\n\t\t\tStreet1: \"123 Known St\",\n\t\t\tCountry: \"United States\",\n\t\t})\n\t})\n\thttp.ListenAndServe(\":3000\", nil)\n}\n```\n\n**Relevant HTML** trimmed for brevity\n\n\n```html\n\u003cform\u003e\n  \u003clabel \u003e\n    Street\n  \u003c/label\u003e\n  \u003cinput  type=\"text\" name=\"Street1\" placeholder=\"123 Sample St\" value=\"123 Known St\"\u003e\n\n  \u003clabel \u003e\n    Street (cont)\n  \u003c/label\u003e\n  \u003cinput  type=\"text\" name=\"Street2\" placeholder=\"Apt 123\" \u003e\n\n  \u003clabel \u003e\n    City\n  \u003c/label\u003e\n  \u003cinput  type=\"text\" name=\"City\" placeholder=\"City\" \u003e\n\n  \u003clabel \u003e\n    State\n  \u003c/label\u003e\n  \u003cinput  type=\"text\" name=\"State\" placeholder=\"State\" \u003e\n  \u003cp\u003eOr your Province\u003c/p\u003e\n\n  \u003clabel \u003e\n    Postal Code\n  \u003c/label\u003e\n  \u003cinput  type=\"text\" name=\"Zip\" placeholder=\"Postal Code\" \u003e\n\n  \u003clabel \u003e\n    Country\n  \u003c/label\u003e\n  \u003cinput  type=\"text\" name=\"Country\" placeholder=\"Country\" value=\"United States\"\u003e\n\u003c/form\u003e\n```\n\n## How it works\n\nThe `form.Builder` type provides a single method - `Inputs` - which will parse the provided struct to determine which fields it contains, any values set for each field, and any struct tags provided for the form package. Once that information is parsed it will execute the provided `InputTemplate` field in the builder for each field in the struct, **including nested fields**.\n\nMost of the time you will probably want to just make this helper available to your html templates via the `template.Funcs()` functions and the `template.FuncMap` type, as I did in the example above.\n\n## I don't recommend tagging domain types\n\nIt is also worth mentioning that I don't really recommend adding `form` struct tags to your domain types, and I typically create types specifically used to generate forms. Eg:\n\n```go\n// This is my domain type\ntype User struct {\n  ID           int\n  Name         string\n  Email        string\n  PasswordHash string\n}\n\n// Somewhere else I'll create my html-specific type:\ntype signupForm struct {\n  Name         string `form:\"...\"`\n  Email        string `form:\"type=email\"`\n  Password     string `form:\"type=password\"`\n  Confirmation string `form:\"type=password;label=Password Confirmation\"`\n}\n```\n\n## Parsing submitted forms\n\nIf you also need to parse forms created by this package, I recommend using the [gorilla/schema](https://github.com/gorilla/schema) package. This package *should* generate input names compliant with the `gorilla/schema` package by default, so as long as you don't change the names it should be pretty trivial to decode.\n\nThere is an example of this in the [examples/tailwind](examples/tailwind) directory.\n\n## Rendering errors\n\nIf you want to render errors, see the [examples/errors/errors.go](examples/errors/errors.go) example and most notably check out the `inputs_and_errors_for` function provided to templates via the `Builder.FuncMap()` function.\n\n*TODO: Add some better examples here, but the provided code sample **is** a complete example.*\n\n## This may have bugs\n\nThis is a very early iteration of the package, and while it appears to be working for my needs chances are it doesn't cover every use case. If you do find one that isn't covered, try to provide a PR with a breaking test.\n\n\n## Notes\n\nThis section is mostly for myself to jot down notes, but feel free to read away.\n\n### Potential features\n\n#### Parsing forms\n\nLong term this could also support parsing forms, but gorilla/schema does a great job of that already so I don't see any reason to at this time. It would likely be easier to just make the default input names line up with what gorilla/schema expects and provide examples for how to use the two together.\n\n#### Checkboxes and other data types\n\nMaybe allow for various templates for different types, but for now this is possible to do in the HTML templates so it isn't completely missing.\n\n#### Headers on nested structs\n\nLet's say we have this type:\n\n```go\ntype Nested struct {\n  Name string\n  Email string\n  Address Address\n}\n\ntype Address struct {\n  Street1 string\n  Street2 string\n  // ...\n}\n```\n\nIt might make sense to make an optional way to add headers in the form when the nested Address portion is rendered, so the form looks like:\n\n```\nName:    [    ]\nEmail:   [    ]\n\n\u003cAddress Header Here\u003e\n\nStreet1: [    ]\nStreet2: [    ]\n...\n```\n\nThis *should* be pretty easy to do with struct tags on the `Address Address` line.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoncalhoun%2Fform","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoncalhoun%2Fform","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoncalhoun%2Fform/lists"}