[{"data":1,"prerenderedAt":2154},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fattribute-forms-and-layer-actions\u002Fconfigure-editor-widgets-pyqgis":3},{"id":4,"title":5,"body":6,"description":2143,"extension":2144,"meta":2145,"navigation":291,"path":2150,"seo":2151,"stem":2152,"__hash__":2153},"docs\u002Fqgis-plugin-development\u002Fattribute-forms-and-layer-actions\u002Fconfigure-editor-widgets-pyqgis\u002Findex.md","Configure Editor Widgets in PyQGIS",{"type":7,"value":8,"toc":2129},"minimark",[9,13,43,52,225,230,247,251,258,499,517,521,524,726,735,856,860,863,1033,1067,1071,1074,1272,1312,1316,1323,1437,1462,1466,1469,1587,1937,1955,1959,1985,1989,2042,2046,2052,2056,2067,2076,2082,2092,2096,2125],[10,11,5],"h1",{"id":12},"configure-editor-widgets-in-pyqgis",[14,15,16,17,21,22,25,26,29,30,33,34,37,38,42],"p",{},"An attribute table filled in by hand through plain text boxes decays fast. Somebody types ",[18,19,20],"code",{},"Oak",", somebody else ",[18,23,24],{},"oak",", a third person ",[18,27,28],{},"Quercus robur","; a condition score of ",[18,31,32],{},"7"," appears on a scale that stops at 5; a date goes in as ",[18,35,36],{},"14\u002F9"," with no year. Every one of those is preventable at the point of entry, because every field in QGIS can carry an ",[39,40,41],"em",{},"editor widget"," — a drop-down, a lookup against another layer, a bounded spin box, a calendar — that only accepts sensible values.",[14,44,45,46,51],{},"This recipe belongs to ",[47,48,50],"a",{"href":49},"\u002Fqgis-plugin-development\u002Fattribute-forms-and-layer-actions\u002F","Attribute Forms & Layer Actions in PyQGIS",". It sets widgets from Python field by field, covers the configuration keys for the widgets that matter most, and turns the result into a reusable scheme you can apply across a whole project of survey layers.",[14,53,54],{},[55,56,61,65,69,76,85,95,101,108,117,122,125,128,131,135,138,141,145,148,153,158,163,168,171,174,177,181,183,185,188,190,192,195,205,207,214,218,222],"svg",{"viewBox":57,"role":58,"ariaLabel":59,"xmlns":60},"0 0 760 300","img","The same attribute form before and after editor widgets: free text boxes for species, condition, surveyed date and photo become a value map drop-down, a range spin box, a date picker and an attachment widget","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[62,63,64],"title",{},"Text boxes versus editor widgets",[66,67,68],"desc",{},"On the left, a form with four plain text boxes containing inconsistent entries: oak in lower case, condition 7, a date without a year, and a pasted file path. On the right, the same four fields using a value map drop-down showing Oak, a range spin box limited to 1 to 5 showing 4, a calendar date picker showing 2026-09-14, and an attachment widget showing a photo thumbnail with a browse button.",[70,71],"rect",{"x":72,"y":72,"width":73,"height":74,"fill":75},"0","760","300","#f6f3ea",[77,78,84],"text",{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","The widget decides what can be typed",[70,86],{"x":87,"y":88,"width":89,"height":90,"rx":91,"fill":92,"stroke":93,"style":94},"24","48","344","232","10","#fdf2e2","#b91c1c","stroke-width:2.5",[77,96,100],{"x":97,"y":98,"style":99,"fill":93,"textAnchor":83},"196","72","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","default text edits",[77,102,107],{"x":103,"y":104,"style":105,"fill":106},"44","108","font-size:10.5px;font-family:sans-serif","#2f3b35","species",[70,109],{"x":110,"y":111,"width":112,"height":87,"rx":113,"fill":114,"stroke":115,"style":116},"140","92","206","4","#fffdf7","#9aa39d","stroke-width:1.2",[77,118,24],{"x":119,"y":120,"style":121,"fill":106},"150","109","font-size:10px;font-family:sans-serif",[77,123,124],{"x":103,"y":119,"style":105,"fill":106},"condition",[70,126],{"x":110,"y":127,"width":112,"height":87,"rx":113,"fill":114,"stroke":115,"style":116},"134",[77,129,32],{"x":119,"y":130,"style":121,"fill":93},"151",[77,132,134],{"x":103,"y":133,"style":105,"fill":106},"192","surveyed",[70,136],{"x":110,"y":137,"width":112,"height":87,"rx":113,"fill":114,"stroke":115,"style":116},"176",[77,139,36],{"x":119,"y":140,"style":121,"fill":93},"193",[77,142,144],{"x":103,"y":143,"style":105,"fill":106},"234","photo",[70,146],{"x":110,"y":147,"width":112,"height":87,"rx":113,"fill":114,"stroke":115,"style":116},"218",[77,149,152],{"x":119,"y":150,"style":151,"fill":106},"235","font-size:9.5px;font-family:monospace","C:\\Users\\…\\IMG_41.jpg",[77,154,157],{"x":97,"y":155,"style":156,"fill":93,"textAnchor":83},"266","text-anchor:middle;font-size:10px;font-family:sans-serif","anything goes, so anything arrives",[70,159],{"x":160,"y":88,"width":89,"height":90,"rx":91,"fill":161,"stroke":162,"style":94},"392","#e8efe6","#15803d",[77,164,167],{"x":165,"y":98,"style":99,"fill":166,"textAnchor":83},"564","#166534","editor widgets",[77,169,107],{"x":170,"y":104,"style":105,"fill":106},"412",[70,172],{"x":173,"y":111,"width":112,"height":87,"rx":113,"fill":114,"stroke":162,"style":116},"508",[77,175,20],{"x":176,"y":120,"style":121,"fill":106},"518",[178,179],"path",{"d":180,"fill":106},"M696 100 L702 108 L708 100 z",[77,182,124],{"x":170,"y":119,"style":105,"fill":106},[70,184],{"x":173,"y":127,"width":112,"height":87,"rx":113,"fill":114,"stroke":162,"style":116},[77,186,187],{"x":176,"y":130,"style":121,"fill":106},"4   (1–5)",[77,189,134],{"x":170,"y":133,"style":105,"fill":106},[70,191],{"x":173,"y":137,"width":112,"height":87,"rx":113,"fill":114,"stroke":162,"style":116},[77,193,194],{"x":176,"y":140,"style":121,"fill":106},"2026-09-14",[70,196],{"x":197,"y":198,"width":199,"height":200,"rx":201,"fill":202,"stroke":203,"style":204},"690","181","16","14","2","#dbeafe","#2563eb","stroke-width:1",[77,206,144],{"x":170,"y":143,"style":105,"fill":106},[70,208],{"x":173,"y":209,"width":210,"height":211,"rx":212,"fill":213,"stroke":162,"style":204},"214","42","32","3","#c9d6cf",[70,215],{"x":216,"y":147,"width":217,"height":87,"rx":113,"fill":114,"stroke":162,"style":116},"560","154",[77,219,221],{"x":220,"y":150,"style":121,"fill":106},"570","photos\u002FIMG_41.jpg …",[77,223,224],{"x":165,"y":155,"style":156,"fill":166,"textAnchor":83},"only valid values can be entered",[226,227,229],"h2",{"id":228},"prerequisites","Prerequisites",[231,232,233,241,244],"ul",{},[234,235,236,240],"li",{},[237,238,239],"strong",{},"QGIS 3.40 LTR"," or newer, or the QGIS 4 series.",[234,242,243],{},"An editable vector layer, ideally in GeoPackage or PostGIS. Widgets work on any provider, but a read-only layer never shows an edit form.",[234,245,246],{},"For value relations, a lookup layer in the same project.",[226,248,250],{"id":249},"how-a-widget-setup-works","How a widget setup works",[14,252,253,254,257],{},"Each field has one ",[18,255,256],{},"QgsEditorWidgetSetup",": a widget type name and a configuration dictionary. The layer stores it by field index, it is saved in the project and in the layer's QML style, and it is what both the attribute form and the attribute table use when a user edits a value.",[259,260,265],"pre",{"className":261,"code":262,"language":263,"meta":264,"style":264},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject, QgsEditorWidgetSetup\n\ntrees = QgsProject.instance().mapLayersByName(\"street_trees\")[0]\n\nfor idx, field in enumerate(trees.fields()):\n    setup = trees.editorWidgetSetup(idx)\n    print(f\"{field.name():\u003C16} {setup.type() or '(default)':\u003C16} {setup.config()}\")\n\ndef set_widget(layer, field_name, widget_type, config):\n    idx = layer.fields().indexOf(field_name)\n    if idx \u003C 0:\n        raise KeyError(f\"{layer.name()} has no field {field_name!r}\")\n    layer.setEditorWidgetSetup(idx, QgsEditorWidgetSetup(widget_type, config))\n","python","",[18,266,267,286,293,318,323,341,352,407,412,425,436,454,493],{"__ignoreMap":264},[268,269,272,276,280,283],"span",{"class":270,"line":271},"line",1,[268,273,275],{"class":274},"snl16","from",[268,277,279],{"class":278},"s95oV"," qgis.core ",[268,281,282],{"class":274},"import",[268,284,285],{"class":278}," QgsProject, QgsEditorWidgetSetup\n",[268,287,289],{"class":270,"line":288},2,[268,290,292],{"emptyLinePlaceholder":291},true,"\n",[268,294,296,299,302,305,309,312,315],{"class":270,"line":295},3,[268,297,298],{"class":278},"trees ",[268,300,301],{"class":274},"=",[268,303,304],{"class":278}," QgsProject.instance().mapLayersByName(",[268,306,308],{"class":307},"sU2Wk","\"street_trees\"",[268,310,311],{"class":278},")[",[268,313,72],{"class":314},"sDLfK",[268,316,317],{"class":278},"]\n",[268,319,321],{"class":270,"line":320},4,[268,322,292],{"emptyLinePlaceholder":291},[268,324,326,329,332,335,338],{"class":270,"line":325},5,[268,327,328],{"class":274},"for",[268,330,331],{"class":278}," idx, field ",[268,333,334],{"class":274},"in",[268,336,337],{"class":314}," enumerate",[268,339,340],{"class":278},"(trees.fields()):\n",[268,342,344,347,349],{"class":270,"line":343},6,[268,345,346],{"class":278},"    setup ",[268,348,301],{"class":274},[268,350,351],{"class":278}," trees.editorWidgetSetup(idx)\n",[268,353,355,358,361,364,367,370,373,376,379,382,385,388,391,393,395,397,400,402,404],{"class":270,"line":354},7,[268,356,357],{"class":314},"    print",[268,359,360],{"class":278},"(",[268,362,363],{"class":274},"f",[268,365,366],{"class":307},"\"",[268,368,369],{"class":314},"{",[268,371,372],{"class":278},"field.name()",[268,374,375],{"class":274},":\u003C16",[268,377,378],{"class":314},"}",[268,380,381],{"class":314}," {",[268,383,384],{"class":278},"setup.type() ",[268,386,387],{"class":274},"or",[268,389,390],{"class":307}," '(default)'",[268,392,375],{"class":274},[268,394,378],{"class":314},[268,396,381],{"class":314},[268,398,399],{"class":278},"setup.config()",[268,401,378],{"class":314},[268,403,366],{"class":307},[268,405,406],{"class":278},")\n",[268,408,410],{"class":270,"line":409},8,[268,411,292],{"emptyLinePlaceholder":291},[268,413,415,418,422],{"class":270,"line":414},9,[268,416,417],{"class":274},"def",[268,419,421],{"class":420},"svObZ"," set_widget",[268,423,424],{"class":278},"(layer, field_name, widget_type, config):\n",[268,426,428,431,433],{"class":270,"line":427},10,[268,429,430],{"class":278},"    idx ",[268,432,301],{"class":274},[268,434,435],{"class":278}," layer.fields().indexOf(field_name)\n",[268,437,439,442,445,448,451],{"class":270,"line":438},11,[268,440,441],{"class":274},"    if",[268,443,444],{"class":278}," idx ",[268,446,447],{"class":274},"\u003C",[268,449,450],{"class":314}," 0",[268,452,453],{"class":278},":\n",[268,455,457,460,463,465,467,469,471,474,476,479,481,484,487,489,491],{"class":270,"line":456},12,[268,458,459],{"class":274},"        raise",[268,461,462],{"class":314}," KeyError",[268,464,360],{"class":278},[268,466,363],{"class":274},[268,468,366],{"class":307},[268,470,369],{"class":314},[268,472,473],{"class":278},"layer.name()",[268,475,378],{"class":314},[268,477,478],{"class":307}," has no field ",[268,480,369],{"class":314},[268,482,483],{"class":278},"field_name",[268,485,486],{"class":274},"!r",[268,488,378],{"class":314},[268,490,366],{"class":307},[268,492,406],{"class":278},[268,494,496],{"class":270,"line":495},13,[268,497,498],{"class":278},"    layer.setEditorWidgetSetup(idx, QgsEditorWidgetSetup(widget_type, config))\n",[14,500,501,504,505,508,509,512,513,516],{},[237,502,503],{},"Breakdown:"," An empty type string means QGIS picks a default from the field type — a text edit for strings, a spin box for integers, a date edit for dates. The configuration keys are specific to each widget type and are not validated: a misspelt key is silently ignored and the widget falls back to its default for that option, which is why printing the setup of a field configured by hand in the Layer Properties dialog is the most reliable way to discover the exact keys your QGIS version writes. The small helper raises on a missing field, because ",[18,506,507],{},"indexOf"," returns ",[18,510,511],{},"-1"," and ",[18,514,515],{},"setEditorWidgetSetup(-1, …)"," does nothing without complaint.",[226,518,520],{"id":519},"value-maps-for-coded-values","Value maps for coded values",[14,522,523],{},"A value map shows a readable label and stores a code. It is the right widget for short, fixed lists — condition classes, land-use codes, yes\u002Fno\u002Funknown — that belong to the data model rather than to a separate table.",[259,525,527],{"className":261,"code":526,"language":263,"meta":264,"style":264},"set_widget(trees, \"condition\", \"ValueMap\", {\n    \"map\": [\n        {\"1 – dead or dying\": 1},\n        {\"2 – poor\": 2},\n        {\"3 – fair\": 3},\n        {\"4 – good\": 4},\n        {\"5 – excellent\": 5},\n    ]\n})\n\nset_widget(trees, \"ownership\", \"ValueMap\", {\n    \"map\": [\n        {\"Council\": \"CNL\"},\n        {\"Private\": \"PRV\"},\n        {\"Highways authority\": \"HWY\"},\n        {\"\u003CNULL>\": \"{2839923C-8B7D-419E-B84B-CA2FE9B80EC7}\"},\n    ]\n})\n",[18,528,529,546,554,571,584,597,610,624,629,634,638,651,657,671,686,701,716,721],{"__ignoreMap":264},[268,530,531,534,537,540,543],{"class":270,"line":271},[268,532,533],{"class":278},"set_widget(trees, ",[268,535,536],{"class":307},"\"condition\"",[268,538,539],{"class":278},", ",[268,541,542],{"class":307},"\"ValueMap\"",[268,544,545],{"class":278},", {\n",[268,547,548,551],{"class":270,"line":288},[268,549,550],{"class":307},"    \"map\"",[268,552,553],{"class":278},": [\n",[268,555,556,559,562,565,568],{"class":270,"line":295},[268,557,558],{"class":278},"        {",[268,560,561],{"class":307},"\"1 – dead or dying\"",[268,563,564],{"class":278},": ",[268,566,567],{"class":314},"1",[268,569,570],{"class":278},"},\n",[268,572,573,575,578,580,582],{"class":270,"line":320},[268,574,558],{"class":278},[268,576,577],{"class":307},"\"2 – poor\"",[268,579,564],{"class":278},[268,581,201],{"class":314},[268,583,570],{"class":278},[268,585,586,588,591,593,595],{"class":270,"line":325},[268,587,558],{"class":278},[268,589,590],{"class":307},"\"3 – fair\"",[268,592,564],{"class":278},[268,594,212],{"class":314},[268,596,570],{"class":278},[268,598,599,601,604,606,608],{"class":270,"line":343},[268,600,558],{"class":278},[268,602,603],{"class":307},"\"4 – good\"",[268,605,564],{"class":278},[268,607,113],{"class":314},[268,609,570],{"class":278},[268,611,612,614,617,619,622],{"class":270,"line":354},[268,613,558],{"class":278},[268,615,616],{"class":307},"\"5 – excellent\"",[268,618,564],{"class":278},[268,620,621],{"class":314},"5",[268,623,570],{"class":278},[268,625,626],{"class":270,"line":409},[268,627,628],{"class":278},"    ]\n",[268,630,631],{"class":270,"line":414},[268,632,633],{"class":278},"})\n",[268,635,636],{"class":270,"line":427},[268,637,292],{"emptyLinePlaceholder":291},[268,639,640,642,645,647,649],{"class":270,"line":438},[268,641,533],{"class":278},[268,643,644],{"class":307},"\"ownership\"",[268,646,539],{"class":278},[268,648,542],{"class":307},[268,650,545],{"class":278},[268,652,653,655],{"class":270,"line":456},[268,654,550],{"class":307},[268,656,553],{"class":278},[268,658,659,661,664,666,669],{"class":270,"line":495},[268,660,558],{"class":278},[268,662,663],{"class":307},"\"Council\"",[268,665,564],{"class":278},[268,667,668],{"class":307},"\"CNL\"",[268,670,570],{"class":278},[268,672,674,676,679,681,684],{"class":270,"line":673},14,[268,675,558],{"class":278},[268,677,678],{"class":307},"\"Private\"",[268,680,564],{"class":278},[268,682,683],{"class":307},"\"PRV\"",[268,685,570],{"class":278},[268,687,689,691,694,696,699],{"class":270,"line":688},15,[268,690,558],{"class":278},[268,692,693],{"class":307},"\"Highways authority\"",[268,695,564],{"class":278},[268,697,698],{"class":307},"\"HWY\"",[268,700,570],{"class":278},[268,702,704,706,709,711,714],{"class":270,"line":703},16,[268,705,558],{"class":278},[268,707,708],{"class":307},"\"\u003CNULL>\"",[268,710,564],{"class":278},[268,712,713],{"class":307},"\"{2839923C-8B7D-419E-B84B-CA2FE9B80EC7}\"",[268,715,570],{"class":278},[268,717,719],{"class":270,"line":718},17,[268,720,628],{"class":278},[268,722,724],{"class":270,"line":723},18,[268,725,633],{"class":278},[14,727,728,730,731,734],{},[237,729,503],{}," The configuration is a list of single-entry dictionaries mapping label to stored value; a list preserves order, which a plain dictionary would too on modern Python but older QGIS releases did not rely on. Stored values should match the field type — integers for an integer field — or the widget shows the raw value instead of the label. The long brace-wrapped string is the sentinel QGIS uses to offer an explicit ",[39,732,733],{},"NULL"," entry in the drop-down; copy it exactly. The attribute table displays the labels too, so users never see the codes.",[14,736,737],{},[55,738,741,744,747,750,763,766,771,776,783,788,793,797,802,806,811,815,818,823,827,830,836,839,843,846,850,853],{"viewBox":739,"role":58,"ariaLabel":740,"xmlns":60},"0 0 760 264","Value map versus value relation: a value map stores a fixed list inside the layer's form configuration, a value relation reads its choices from a lookup layer and can filter them with an expression",[62,742,743],{},"Fixed list or lookup layer?",[66,745,746],{},"Left: a value map keeps five condition labels and codes inside the street_trees form configuration; changing the list means editing every layer that uses it. Right: a value relation points at a species lookup layer with key and value fields; adding a species to the lookup table updates every form immediately, and a filter expression can restrict the list, for example to species where native is true.",[70,748],{"x":72,"y":72,"width":73,"height":749,"fill":75},"264",[751,752,753],"defs",{},[754,755,760],"marker",{"id":756,"viewBox":757,"refX":758,"refY":621,"markerWidth":32,"markerHeight":32,"orient":759},"ewRelArrow","0 0 10 10","8","auto-start-reverse",[178,761],{"d":762,"fill":106},"M0 0 L10 5 L0 10 z",[77,764,765],{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"Where does the list of choices live?",[70,767],{"x":87,"y":88,"width":768,"height":97,"rx":91,"fill":769,"stroke":770,"style":94},"330","#eef7f4","#0f766e",[77,772,775],{"x":773,"y":774,"style":99,"fill":770,"textAnchor":83},"189","74","ValueMap",[70,777],{"x":88,"y":778,"width":779,"height":780,"rx":781,"fill":114,"stroke":770,"style":782},"90","282","86","6","stroke-width:1.5",[77,784,787],{"x":773,"y":785,"style":156,"fill":786,"textAnchor":83},"112","#59645f","inside the layer's form config",[77,789,792],{"x":773,"y":790,"style":791,"fill":106,"textAnchor":83},"136","text-anchor:middle;font-size:10px;font-family:monospace","\"2 – poor\" → 2",[77,794,796],{"x":773,"y":795,"style":791,"fill":106,"textAnchor":83},"156","\"4 – good\" → 4",[77,798,801],{"x":773,"y":799,"style":800,"fill":106,"textAnchor":83},"204","text-anchor:middle;font-size:10.5px;font-family:sans-serif","short, fixed, part of the schema",[77,803,805],{"x":773,"y":804,"style":156,"fill":786,"textAnchor":83},"226","change it = edit every layer",[70,807],{"x":808,"y":88,"width":809,"height":97,"rx":91,"fill":810,"stroke":203,"style":94},"378","358","#eff3ff",[77,812,814],{"x":813,"y":774,"style":99,"fill":203,"textAnchor":83},"557","ValueRelation",[70,816],{"x":817,"y":778,"width":790,"height":780,"rx":781,"fill":114,"stroke":203,"style":782},"398",[77,819,822],{"x":820,"y":785,"style":821,"fill":203,"textAnchor":83},"466","text-anchor:middle;font-size:10px;font-weight:bold;font-family:sans-serif","species (lookup)",[77,824,826],{"x":820,"y":790,"style":825,"fill":106,"textAnchor":83},"text-anchor:middle;font-size:9.5px;font-family:monospace","QURO  Oak",[77,828,829],{"x":820,"y":795,"style":825,"fill":106,"textAnchor":83},"TICO  Lime",[270,831],{"x1":832,"y1":833,"x2":834,"y2":833,"stroke":106,"style":835},"534","133","572","stroke-width:1.8;marker-end:url(#ewRelArrow)",[70,837],{"x":838,"y":778,"width":790,"height":780,"rx":781,"fill":114,"stroke":203,"style":782},"580",[77,840,842],{"x":841,"y":785,"style":821,"fill":203,"textAnchor":83},"648","tree form",[77,844,845],{"x":841,"y":110,"style":156,"fill":106,"textAnchor":83},"shows: Oak",[77,847,849],{"x":841,"y":848,"style":156,"fill":106,"textAnchor":83},"160","stores: QURO",[77,851,852],{"x":813,"y":799,"style":800,"fill":106,"textAnchor":83},"long or changing lists, shared",[77,854,855],{"x":813,"y":804,"style":156,"fill":786,"textAnchor":83},"add a row = every form updates",[226,857,859],{"id":858},"value-relations-to-a-lookup-layer","Value relations to a lookup layer",[14,861,862],{},"When the list is long, shared between layers, or maintained by someone else — a species list, a register of assets, a table of staff — keep it in a layer and point a value relation at it. Adding a row to the lookup updates every form that uses it.",[259,864,866],{"className":261,"code":865,"language":263,"meta":264,"style":264},"species = QgsProject.instance().mapLayersByName(\"species_lookup\")[0]\n\nset_widget(trees, \"species_code\", \"ValueRelation\", {\n    \"Layer\": species.id(),\n    \"LayerName\": species.name(),\n    \"LayerSource\": species.source(),\n    \"LayerProviderName\": species.providerType(),\n    \"Key\": \"code\",\n    \"Value\": \"common_name\",\n    \"OrderByValue\": True,\n    \"AllowNull\": False,\n    \"AllowMulti\": False,\n    \"UseCompleter\": True,\n    \"FilterExpression\": \"\\\"planting_approved\\\" = true\",\n})\n",[18,867,868,886,890,904,912,920,928,936,949,961,973,985,996,1007,1029],{"__ignoreMap":264},[268,869,870,873,875,877,880,882,884],{"class":270,"line":271},[268,871,872],{"class":278},"species ",[268,874,301],{"class":274},[268,876,304],{"class":278},[268,878,879],{"class":307},"\"species_lookup\"",[268,881,311],{"class":278},[268,883,72],{"class":314},[268,885,317],{"class":278},[268,887,888],{"class":270,"line":288},[268,889,292],{"emptyLinePlaceholder":291},[268,891,892,894,897,899,902],{"class":270,"line":295},[268,893,533],{"class":278},[268,895,896],{"class":307},"\"species_code\"",[268,898,539],{"class":278},[268,900,901],{"class":307},"\"ValueRelation\"",[268,903,545],{"class":278},[268,905,906,909],{"class":270,"line":320},[268,907,908],{"class":307},"    \"Layer\"",[268,910,911],{"class":278},": species.id(),\n",[268,913,914,917],{"class":270,"line":325},[268,915,916],{"class":307},"    \"LayerName\"",[268,918,919],{"class":278},": species.name(),\n",[268,921,922,925],{"class":270,"line":343},[268,923,924],{"class":307},"    \"LayerSource\"",[268,926,927],{"class":278},": species.source(),\n",[268,929,930,933],{"class":270,"line":354},[268,931,932],{"class":307},"    \"LayerProviderName\"",[268,934,935],{"class":278},": species.providerType(),\n",[268,937,938,941,943,946],{"class":270,"line":409},[268,939,940],{"class":307},"    \"Key\"",[268,942,564],{"class":278},[268,944,945],{"class":307},"\"code\"",[268,947,948],{"class":278},",\n",[268,950,951,954,956,959],{"class":270,"line":414},[268,952,953],{"class":307},"    \"Value\"",[268,955,564],{"class":278},[268,957,958],{"class":307},"\"common_name\"",[268,960,948],{"class":278},[268,962,963,966,968,971],{"class":270,"line":427},[268,964,965],{"class":307},"    \"OrderByValue\"",[268,967,564],{"class":278},[268,969,970],{"class":314},"True",[268,972,948],{"class":278},[268,974,975,978,980,983],{"class":270,"line":438},[268,976,977],{"class":307},"    \"AllowNull\"",[268,979,564],{"class":278},[268,981,982],{"class":314},"False",[268,984,948],{"class":278},[268,986,987,990,992,994],{"class":270,"line":456},[268,988,989],{"class":307},"    \"AllowMulti\"",[268,991,564],{"class":278},[268,993,982],{"class":314},[268,995,948],{"class":278},[268,997,998,1001,1003,1005],{"class":270,"line":495},[268,999,1000],{"class":307},"    \"UseCompleter\"",[268,1002,564],{"class":278},[268,1004,970],{"class":314},[268,1006,948],{"class":278},[268,1008,1009,1012,1014,1016,1019,1022,1024,1027],{"class":270,"line":673},[268,1010,1011],{"class":307},"    \"FilterExpression\"",[268,1013,564],{"class":278},[268,1015,366],{"class":307},[268,1017,1018],{"class":314},"\\\"",[268,1020,1021],{"class":307},"planting_approved",[268,1023,1018],{"class":314},[268,1025,1026],{"class":307}," = true\"",[268,1028,948],{"class":278},[268,1030,1031],{"class":270,"line":688},[268,1032,633],{"class":278},[14,1034,1035,1037,1038,1041,1042,1045,1046,1049,1050,1053,1054,1057,1058,1061,1062,1066],{},[237,1036,503],{}," ",[18,1039,1040],{},"Layer"," holds the lookup layer's id, which is the link QGIS actually follows; the name, source and provider are stored alongside so the relation can be re-established if the project is opened with a different layer id — for instance after the lookup layer was removed and re-added. ",[18,1043,1044],{},"Key"," is the stored column, ",[18,1047,1048],{},"Value"," the displayed one. ",[18,1051,1052],{},"UseCompleter"," turns the combo box into a type-ahead search, which matters once the list is longer than a screen. ",[18,1055,1056],{},"FilterExpression"," restricts choices without changing the lookup; it can even reference the feature being edited with ",[18,1059,1060],{},"current_value('field')",", which is how cascading drop-downs — region, then district within region — are built. For a genuine one-to-many relationship rather than a lookup, ",[47,1063,1065],{"href":1064},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002Fdefine-layer-relations-pyqgis\u002F","defining layer relations"," is the better model.",[226,1068,1070],{"id":1069},"ranges-checkboxes-and-dates","Ranges, checkboxes and dates",[14,1072,1073],{},"Numbers, booleans and dates each have a widget that makes invalid input impossible rather than merely discouraged.",[259,1075,1077],{"className":261,"code":1076,"language":263,"meta":264,"style":264},"set_widget(trees, \"height_m\", \"Range\", {\n    \"Min\": 0.5, \"Max\": 45.0, \"Step\": 0.5, \"Precision\": 1,\n    \"Style\": \"SpinBox\", \"AllowNull\": True,\n})\n\nset_widget(trees, \"tpo\", \"CheckBox\", {\n    \"CheckedState\": \"Y\",\n    \"UncheckedState\": \"N\",\n})\n\nset_widget(trees, \"surveyed_on\", \"DateTime\", {\n    \"field_format\": \"yyyy-MM-dd\",\n    \"display_format\": \"d MMM yyyy\",\n    \"calendar_popup\": True,\n    \"allow_null\": False,\n})\n",[18,1078,1079,1093,1133,1154,1158,1162,1176,1188,1200,1204,1208,1222,1234,1246,1257,1268],{"__ignoreMap":264},[268,1080,1081,1083,1086,1088,1091],{"class":270,"line":271},[268,1082,533],{"class":278},[268,1084,1085],{"class":307},"\"height_m\"",[268,1087,539],{"class":278},[268,1089,1090],{"class":307},"\"Range\"",[268,1092,545],{"class":278},[268,1094,1095,1098,1100,1103,1105,1108,1110,1113,1115,1118,1120,1122,1124,1127,1129,1131],{"class":270,"line":288},[268,1096,1097],{"class":307},"    \"Min\"",[268,1099,564],{"class":278},[268,1101,1102],{"class":314},"0.5",[268,1104,539],{"class":278},[268,1106,1107],{"class":307},"\"Max\"",[268,1109,564],{"class":278},[268,1111,1112],{"class":314},"45.0",[268,1114,539],{"class":278},[268,1116,1117],{"class":307},"\"Step\"",[268,1119,564],{"class":278},[268,1121,1102],{"class":314},[268,1123,539],{"class":278},[268,1125,1126],{"class":307},"\"Precision\"",[268,1128,564],{"class":278},[268,1130,567],{"class":314},[268,1132,948],{"class":278},[268,1134,1135,1138,1140,1143,1145,1148,1150,1152],{"class":270,"line":295},[268,1136,1137],{"class":307},"    \"Style\"",[268,1139,564],{"class":278},[268,1141,1142],{"class":307},"\"SpinBox\"",[268,1144,539],{"class":278},[268,1146,1147],{"class":307},"\"AllowNull\"",[268,1149,564],{"class":278},[268,1151,970],{"class":314},[268,1153,948],{"class":278},[268,1155,1156],{"class":270,"line":320},[268,1157,633],{"class":278},[268,1159,1160],{"class":270,"line":325},[268,1161,292],{"emptyLinePlaceholder":291},[268,1163,1164,1166,1169,1171,1174],{"class":270,"line":343},[268,1165,533],{"class":278},[268,1167,1168],{"class":307},"\"tpo\"",[268,1170,539],{"class":278},[268,1172,1173],{"class":307},"\"CheckBox\"",[268,1175,545],{"class":278},[268,1177,1178,1181,1183,1186],{"class":270,"line":354},[268,1179,1180],{"class":307},"    \"CheckedState\"",[268,1182,564],{"class":278},[268,1184,1185],{"class":307},"\"Y\"",[268,1187,948],{"class":278},[268,1189,1190,1193,1195,1198],{"class":270,"line":409},[268,1191,1192],{"class":307},"    \"UncheckedState\"",[268,1194,564],{"class":278},[268,1196,1197],{"class":307},"\"N\"",[268,1199,948],{"class":278},[268,1201,1202],{"class":270,"line":414},[268,1203,633],{"class":278},[268,1205,1206],{"class":270,"line":427},[268,1207,292],{"emptyLinePlaceholder":291},[268,1209,1210,1212,1215,1217,1220],{"class":270,"line":438},[268,1211,533],{"class":278},[268,1213,1214],{"class":307},"\"surveyed_on\"",[268,1216,539],{"class":278},[268,1218,1219],{"class":307},"\"DateTime\"",[268,1221,545],{"class":278},[268,1223,1224,1227,1229,1232],{"class":270,"line":456},[268,1225,1226],{"class":307},"    \"field_format\"",[268,1228,564],{"class":278},[268,1230,1231],{"class":307},"\"yyyy-MM-dd\"",[268,1233,948],{"class":278},[268,1235,1236,1239,1241,1244],{"class":270,"line":495},[268,1237,1238],{"class":307},"    \"display_format\"",[268,1240,564],{"class":278},[268,1242,1243],{"class":307},"\"d MMM yyyy\"",[268,1245,948],{"class":278},[268,1247,1248,1251,1253,1255],{"class":270,"line":673},[268,1249,1250],{"class":307},"    \"calendar_popup\"",[268,1252,564],{"class":278},[268,1254,970],{"class":314},[268,1256,948],{"class":278},[268,1258,1259,1262,1264,1266],{"class":270,"line":688},[268,1260,1261],{"class":307},"    \"allow_null\"",[268,1263,564],{"class":278},[268,1265,982],{"class":314},[268,1267,948],{"class":278},[268,1269,1270],{"class":270,"line":703},[268,1271,633],{"class":278},[14,1273,1274,1037,1276,1279,1280,1283,1284,1287,1288,1291,1292,1295,1296,1291,1298,1300,1301,539,1304,1307,1308,1311],{},[237,1275,503],{},[18,1277,1278],{},"Range"," limits spin boxes and sliders to the bounds given; with ",[18,1281,1282],{},"AllowNull"," the widget also offers an empty state, which is important for measurements that were not taken as opposed to measurements of zero. ",[18,1285,1286],{},"CheckBox"," stores whatever values the field uses — ",[18,1289,1290],{},"Y","\u002F",[18,1293,1294],{},"N"," text, ",[18,1297,567],{},[18,1299,72],{}," integers, or a real boolean when the states are left empty on a boolean field. For ",[18,1302,1303],{},"DateTime",[18,1305,1306],{},"field_format"," describes how the value is stored in a text field and is ignored for true date fields, while ",[18,1309,1310],{},"display_format"," only affects what users see. Storing ISO dates in a text field keeps sorting correct even in formats that have no date type.",[226,1313,1315],{"id":1314},"attachments-for-photos-and-documents","Attachments for photos and documents",[14,1317,1318,1319,1322],{},"The ",[18,1320,1321],{},"ExternalResource"," widget stores a path and shows the file — a photo preview, or a link that opens a PDF. Paired with relative storage it keeps working when the project folder moves.",[259,1324,1326],{"className":261,"code":1325,"language":263,"meta":264,"style":264},"set_widget(trees, \"photo\", \"ExternalResource\", {\n    \"FileWidget\": True,\n    \"FileWidgetButton\": True,\n    \"DocumentViewer\": 1,\n    \"DocumentViewerHeight\": 240,\n    \"DocumentViewerWidth\": 320,\n    \"RelativeStorage\": 1,\n    \"StorageMode\": 0,\n    \"FileWidgetFilter\": \"Images (*.jpg *.jpeg *.png)\",\n})\n",[18,1327,1328,1342,1353,1364,1375,1387,1399,1410,1421,1433],{"__ignoreMap":264},[268,1329,1330,1332,1335,1337,1340],{"class":270,"line":271},[268,1331,533],{"class":278},[268,1333,1334],{"class":307},"\"photo\"",[268,1336,539],{"class":278},[268,1338,1339],{"class":307},"\"ExternalResource\"",[268,1341,545],{"class":278},[268,1343,1344,1347,1349,1351],{"class":270,"line":288},[268,1345,1346],{"class":307},"    \"FileWidget\"",[268,1348,564],{"class":278},[268,1350,970],{"class":314},[268,1352,948],{"class":278},[268,1354,1355,1358,1360,1362],{"class":270,"line":295},[268,1356,1357],{"class":307},"    \"FileWidgetButton\"",[268,1359,564],{"class":278},[268,1361,970],{"class":314},[268,1363,948],{"class":278},[268,1365,1366,1369,1371,1373],{"class":270,"line":320},[268,1367,1368],{"class":307},"    \"DocumentViewer\"",[268,1370,564],{"class":278},[268,1372,567],{"class":314},[268,1374,948],{"class":278},[268,1376,1377,1380,1382,1385],{"class":270,"line":325},[268,1378,1379],{"class":307},"    \"DocumentViewerHeight\"",[268,1381,564],{"class":278},[268,1383,1384],{"class":314},"240",[268,1386,948],{"class":278},[268,1388,1389,1392,1394,1397],{"class":270,"line":343},[268,1390,1391],{"class":307},"    \"DocumentViewerWidth\"",[268,1393,564],{"class":278},[268,1395,1396],{"class":314},"320",[268,1398,948],{"class":278},[268,1400,1401,1404,1406,1408],{"class":270,"line":354},[268,1402,1403],{"class":307},"    \"RelativeStorage\"",[268,1405,564],{"class":278},[268,1407,567],{"class":314},[268,1409,948],{"class":278},[268,1411,1412,1415,1417,1419],{"class":270,"line":409},[268,1413,1414],{"class":307},"    \"StorageMode\"",[268,1416,564],{"class":278},[268,1418,72],{"class":314},[268,1420,948],{"class":278},[268,1422,1423,1426,1428,1431],{"class":270,"line":414},[268,1424,1425],{"class":307},"    \"FileWidgetFilter\"",[268,1427,564],{"class":278},[268,1429,1430],{"class":307},"\"Images (*.jpg *.jpeg *.png)\"",[268,1432,948],{"class":278},[268,1434,1435],{"class":270,"line":427},[268,1436,633],{"class":278},[14,1438,1439,1037,1441,1444,1445,1448,1449,1452,1453,1456,1457,1461],{},[237,1440,503],{},[18,1442,1443],{},"DocumentViewer"," 1 shows an image preview inside the form; 2 embeds a web view, useful for PDFs on platforms that render them; 0 shows the path only. ",[18,1446,1447],{},"RelativeStorage"," 1 stores paths relative to the project file and 2 relative to a ",[18,1450,1451],{},"DefaultRoot"," you set, which suits a shared photo directory on a server. ",[18,1454,1455],{},"StorageMode"," 0 picks files, 1 picks folders. Photos imported with ",[47,1458,1460],{"href":1459},"\u002Fspatial-data-processing-automation\u002Flayer-data-sources-and-formats\u002Fimport-geotagged-photos-as-points-pyqgis\u002F","native:importphotos"," already have a path field that this widget displays directly.",[226,1463,1465],{"id":1464},"roll-the-scheme-out-to-many-layers","Roll the scheme out to many layers",[14,1467,1468],{},"A survey project often has several layers with the same fields — one per ward, one per year, one per crew. Define the widget scheme once as data and apply it wherever the fields exist.",[14,1470,1471],{},[55,1472,1475,1478,1481,1483,1490,1494,1500,1505,1510,1514,1517,1521,1527,1530,1533,1536,1543,1548,1551,1555,1559,1563,1566,1570,1574,1580,1584],{"viewBox":1473,"role":58,"ariaLabel":1474,"xmlns":60},"0 0 760 240","One widget scheme applied to several layers: a dictionary of field names to widget setups is applied to four survey layers, skipping fields a layer does not have, and each layer is saved with its form style",[62,1476,1477],{},"Define once, apply everywhere",[66,1479,1480],{},"A scheme dictionary on the left maps field names such as condition, species_code, height_m and photo to widget setups. Arrows lead to four layers: trees_north, trees_south, trees_2025 and trees_2026. Each layer receives the widgets for the fields it has; missing fields are reported, not errors. Each layer then saves the Forms style category so the configuration travels with the data.",[70,1482],{"x":72,"y":72,"width":73,"height":1384,"fill":75},[751,1484,1485],{},[754,1486,1488],{"id":1487,"viewBox":757,"refX":758,"refY":621,"markerWidth":32,"markerHeight":32,"orient":759},"ewRollArrow",[178,1489],{"d":762,"fill":106},[77,1491,1493],{"x":79,"y":1492,"style":81,"fill":82,"textAnchor":83},"26","A scheme is data — apply it in a loop",[70,1495],{"x":87,"y":1496,"width":1497,"height":1498,"rx":91,"fill":114,"stroke":786,"style":1499},"52","220","164","stroke-width:2",[77,1501,1504],{"x":127,"y":1502,"style":1503,"fill":82,"textAnchor":83},"78","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","SCHEME",[77,1506,1509],{"x":103,"y":1507,"style":1508,"fill":106},"106","font-size:10px;font-family:monospace","condition    ValueMap",[77,1511,1513],{"x":103,"y":1512,"style":1508,"fill":106},"130","species_code ValueRelation",[77,1515,1516],{"x":103,"y":217,"style":1508,"fill":106},"height_m     Range",[77,1518,1520],{"x":103,"y":1519,"style":1508,"fill":106},"178","photo        ExternalResource",[270,1522],{"x1":1523,"y1":127,"x2":1524,"y2":1525,"stroke":106,"style":1526},"244","296","70","stroke-width:1.6;marker-end:url(#ewRollArrow)",[270,1528],{"x1":1523,"y1":127,"x2":1524,"y2":1529,"stroke":106,"style":1526},"116",[270,1531],{"x1":1523,"y1":127,"x2":1524,"y2":1532,"stroke":106,"style":1526},"162",[270,1534],{"x1":1523,"y1":127,"x2":1524,"y2":1535,"stroke":106,"style":1526},"208",[70,1537],{"x":1538,"y":1539,"width":1540,"height":1541,"rx":781,"fill":161,"stroke":162,"style":1542},"304","50","200","36","stroke-width:1.8",[77,1544,1547],{"x":1545,"y":1546,"style":800,"fill":106,"textAnchor":83},"404","73","trees_north · 4 of 4",[70,1549],{"x":1538,"y":1550,"width":1540,"height":1541,"rx":781,"fill":161,"stroke":162,"style":1542},"96",[77,1552,1554],{"x":1545,"y":1553,"style":800,"fill":106,"textAnchor":83},"119","trees_south · 4 of 4",[70,1556],{"x":1538,"y":1557,"width":1540,"height":1541,"rx":781,"fill":92,"stroke":1558,"style":1542},"142","#b45309",[77,1560,1562],{"x":1545,"y":1561,"style":800,"fill":106,"textAnchor":83},"165","trees_2025 · 3 of 4",[70,1564],{"x":1538,"y":1565,"width":1540,"height":1541,"rx":781,"fill":161,"stroke":162,"style":1542},"188",[77,1567,1569],{"x":1545,"y":1568,"style":800,"fill":106,"textAnchor":83},"211","trees_2026 · 4 of 4",[70,1571],{"x":1572,"y":1550,"width":799,"height":1573,"rx":91,"fill":769,"stroke":770,"style":1499},"532","82",[77,1575,1579],{"x":1576,"y":1577,"style":1578,"fill":770,"textAnchor":83},"634","124","text-anchor:middle;font-size:10.5px;font-weight:bold;font-family:sans-serif","then save the style",[77,1581,1583],{"x":1576,"y":1582,"style":156,"fill":106,"textAnchor":83},"146","Forms category only,",[77,1585,1586],{"x":1576,"y":1498,"style":156,"fill":106,"textAnchor":83},"to the GeoPackage",[259,1588,1590],{"className":261,"code":1589,"language":263,"meta":264,"style":264},"from qgis.core import QgsMapLayer\n\nSCHEME = {\n    \"condition\": (\"ValueMap\", {\"map\": [{\"1 – dead or dying\": 1}, {\"2 – poor\": 2},\n                                        {\"3 – fair\": 3}, {\"4 – good\": 4},\n                                        {\"5 – excellent\": 5}]}),\n    \"height_m\": (\"Range\", {\"Min\": 0.5, \"Max\": 45.0, \"Step\": 0.5,\n                           \"Style\": \"SpinBox\", \"AllowNull\": True}),\n    \"surveyed_on\": (\"DateTime\", {\"display_format\": \"d MMM yyyy\",\n                                 \"calendar_popup\": True}),\n}\n\nfor layer in QgsProject.instance().mapLayers().values():\n    if layer.type() != QgsMapLayer.VectorLayer or not layer.name().startswith(\"trees_\"):\n        continue\n    missing = []\n    for field_name, (widget_type, config) in SCHEME.items():\n        if layer.fields().indexOf(field_name) \u003C 0:\n            missing.append(field_name)\n            continue\n        set_widget(layer, field_name, widget_type, config)\n    layer.saveStyleToDatabase(\"default\", \"survey form\", True, \"\",\n                              QgsMapLayer.StyleCategory.Forms)\n    print(layer.name(), \"missing:\", missing or \"none\")\n",[18,1591,1592,1603,1607,1617,1653,1674,1687,1723,1743,1763,1774,1779,1783,1795,1822,1827,1837,1853,1867,1873,1879,1885,1910,1916],{"__ignoreMap":264},[268,1593,1594,1596,1598,1600],{"class":270,"line":271},[268,1595,275],{"class":274},[268,1597,279],{"class":278},[268,1599,282],{"class":274},[268,1601,1602],{"class":278}," QgsMapLayer\n",[268,1604,1605],{"class":270,"line":288},[268,1606,292],{"emptyLinePlaceholder":291},[268,1608,1609,1611,1614],{"class":270,"line":295},[268,1610,1504],{"class":314},[268,1612,1613],{"class":274}," =",[268,1615,1616],{"class":278}," {\n",[268,1618,1619,1622,1625,1627,1630,1633,1636,1638,1640,1642,1645,1647,1649,1651],{"class":270,"line":320},[268,1620,1621],{"class":307},"    \"condition\"",[268,1623,1624],{"class":278},": (",[268,1626,542],{"class":307},[268,1628,1629],{"class":278},", {",[268,1631,1632],{"class":307},"\"map\"",[268,1634,1635],{"class":278},": [{",[268,1637,561],{"class":307},[268,1639,564],{"class":278},[268,1641,567],{"class":314},[268,1643,1644],{"class":278},"}, {",[268,1646,577],{"class":307},[268,1648,564],{"class":278},[268,1650,201],{"class":314},[268,1652,570],{"class":278},[268,1654,1655,1658,1660,1662,1664,1666,1668,1670,1672],{"class":270,"line":325},[268,1656,1657],{"class":278},"                                        {",[268,1659,590],{"class":307},[268,1661,564],{"class":278},[268,1663,212],{"class":314},[268,1665,1644],{"class":278},[268,1667,603],{"class":307},[268,1669,564],{"class":278},[268,1671,113],{"class":314},[268,1673,570],{"class":278},[268,1675,1676,1678,1680,1682,1684],{"class":270,"line":343},[268,1677,1657],{"class":278},[268,1679,616],{"class":307},[268,1681,564],{"class":278},[268,1683,621],{"class":314},[268,1685,1686],{"class":278},"}]}),\n",[268,1688,1689,1692,1694,1696,1698,1701,1703,1705,1707,1709,1711,1713,1715,1717,1719,1721],{"class":270,"line":354},[268,1690,1691],{"class":307},"    \"height_m\"",[268,1693,1624],{"class":278},[268,1695,1090],{"class":307},[268,1697,1629],{"class":278},[268,1699,1700],{"class":307},"\"Min\"",[268,1702,564],{"class":278},[268,1704,1102],{"class":314},[268,1706,539],{"class":278},[268,1708,1107],{"class":307},[268,1710,564],{"class":278},[268,1712,1112],{"class":314},[268,1714,539],{"class":278},[268,1716,1117],{"class":307},[268,1718,564],{"class":278},[268,1720,1102],{"class":314},[268,1722,948],{"class":278},[268,1724,1725,1728,1730,1732,1734,1736,1738,1740],{"class":270,"line":409},[268,1726,1727],{"class":307},"                           \"Style\"",[268,1729,564],{"class":278},[268,1731,1142],{"class":307},[268,1733,539],{"class":278},[268,1735,1147],{"class":307},[268,1737,564],{"class":278},[268,1739,970],{"class":314},[268,1741,1742],{"class":278},"}),\n",[268,1744,1745,1748,1750,1752,1754,1757,1759,1761],{"class":270,"line":414},[268,1746,1747],{"class":307},"    \"surveyed_on\"",[268,1749,1624],{"class":278},[268,1751,1219],{"class":307},[268,1753,1629],{"class":278},[268,1755,1756],{"class":307},"\"display_format\"",[268,1758,564],{"class":278},[268,1760,1243],{"class":307},[268,1762,948],{"class":278},[268,1764,1765,1768,1770,1772],{"class":270,"line":427},[268,1766,1767],{"class":307},"                                 \"calendar_popup\"",[268,1769,564],{"class":278},[268,1771,970],{"class":314},[268,1773,1742],{"class":278},[268,1775,1776],{"class":270,"line":438},[268,1777,1778],{"class":278},"}\n",[268,1780,1781],{"class":270,"line":456},[268,1782,292],{"emptyLinePlaceholder":291},[268,1784,1785,1787,1790,1792],{"class":270,"line":495},[268,1786,328],{"class":274},[268,1788,1789],{"class":278}," layer ",[268,1791,334],{"class":274},[268,1793,1794],{"class":278}," QgsProject.instance().mapLayers().values():\n",[268,1796,1797,1799,1802,1805,1808,1810,1813,1816,1819],{"class":270,"line":673},[268,1798,441],{"class":274},[268,1800,1801],{"class":278}," layer.type() ",[268,1803,1804],{"class":274},"!=",[268,1806,1807],{"class":278}," QgsMapLayer.VectorLayer ",[268,1809,387],{"class":274},[268,1811,1812],{"class":274}," not",[268,1814,1815],{"class":278}," layer.name().startswith(",[268,1817,1818],{"class":307},"\"trees_\"",[268,1820,1821],{"class":278},"):\n",[268,1823,1824],{"class":270,"line":688},[268,1825,1826],{"class":274},"        continue\n",[268,1828,1829,1832,1834],{"class":270,"line":703},[268,1830,1831],{"class":278},"    missing ",[268,1833,301],{"class":274},[268,1835,1836],{"class":278}," []\n",[268,1838,1839,1842,1845,1847,1850],{"class":270,"line":718},[268,1840,1841],{"class":274},"    for",[268,1843,1844],{"class":278}," field_name, (widget_type, config) ",[268,1846,334],{"class":274},[268,1848,1849],{"class":314}," SCHEME",[268,1851,1852],{"class":278},".items():\n",[268,1854,1855,1858,1861,1863,1865],{"class":270,"line":723},[268,1856,1857],{"class":274},"        if",[268,1859,1860],{"class":278}," layer.fields().indexOf(field_name) ",[268,1862,447],{"class":274},[268,1864,450],{"class":314},[268,1866,453],{"class":278},[268,1868,1870],{"class":270,"line":1869},19,[268,1871,1872],{"class":278},"            missing.append(field_name)\n",[268,1874,1876],{"class":270,"line":1875},20,[268,1877,1878],{"class":274},"            continue\n",[268,1880,1882],{"class":270,"line":1881},21,[268,1883,1884],{"class":278},"        set_widget(layer, field_name, widget_type, config)\n",[268,1886,1888,1891,1894,1896,1899,1901,1903,1905,1908],{"class":270,"line":1887},22,[268,1889,1890],{"class":278},"    layer.saveStyleToDatabase(",[268,1892,1893],{"class":307},"\"default\"",[268,1895,539],{"class":278},[268,1897,1898],{"class":307},"\"survey form\"",[268,1900,539],{"class":278},[268,1902,970],{"class":314},[268,1904,539],{"class":278},[268,1906,1907],{"class":307},"\"\"",[268,1909,948],{"class":278},[268,1911,1913],{"class":270,"line":1912},23,[268,1914,1915],{"class":278},"                              QgsMapLayer.StyleCategory.Forms)\n",[268,1917,1919,1921,1924,1927,1930,1932,1935],{"class":270,"line":1918},24,[268,1920,357],{"class":314},[268,1922,1923],{"class":278},"(layer.name(), ",[268,1925,1926],{"class":307},"\"missing:\"",[268,1928,1929],{"class":278},", missing ",[268,1931,387],{"class":274},[268,1933,1934],{"class":307}," \"none\"",[268,1936,406],{"class":278},[14,1938,1939,1941,1942,1945,1946,1949,1950,1954],{},[237,1940,503],{}," Keeping the scheme as a dictionary means the form design lives in one reviewable place and can be version-controlled next to the plugin or script that applies it. Missing fields are reported rather than treated as errors, because older layers legitimately lack newer attributes. Saving with the ",[18,1943,1944],{},"Forms"," style category writes only the form configuration into the GeoPackage's ",[18,1947,1948],{},"layer_styles"," table as the default style — symbology is untouched — so the next person to add the layer from that file gets the widgets automatically. For layers that should also share symbology, ",[47,1951,1953],{"href":1952},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fcopy-style-between-layers-pyqgis\u002F","copying a style between layers"," covers the full style route.",[226,1956,1958],{"id":1957},"qgis-version-compatibility","QGIS version compatibility",[14,1960,1961,1962,1964,1965,1968,1969,1972,1973,1976,1977,1980,1981,1984],{},"Widget type names and the configuration keys shown have been stable since QGIS 3.0; ",[18,1963,1052],{}," was added to value relations in 3.4 and ",[18,1966,1967],{},"FileWidgetFilter"," in 3.10. ",[18,1970,1971],{},"QgsMapLayer.StyleCategory.Forms"," is the scoped spelling used on the QGIS 4 series; on 3.x the unscoped ",[18,1974,1975],{},"QgsMapLayer.Forms"," also works. ",[18,1978,1979],{},"saveStyleToDatabase"," gained a ",[18,1982,1983],{},"categories"," argument in 3.26 — on older releases it saves every category.",[226,1986,1988],{"id":1987},"troubleshooting","Troubleshooting",[231,1990,1991,2004,2013,2022,2033],{},[234,1992,1993,1996,1997,2000,2001,2003],{},[237,1994,1995],{},"The drop-down shows codes instead of labels."," The stored values in the map do not match the field type; ",[18,1998,1999],{},"\"1\""," is not ",[18,2002,567],{},".",[234,2005,2006,2009,2010,2012],{},[237,2007,2008],{},"A value relation is empty."," The ",[18,2011,1040],{}," id points at a layer that is not in the project, or the filter expression excludes everything.",[234,2014,2015,2018,2019,2021],{},[237,2016,2017],{},"Widgets vanish when the layer is loaded elsewhere."," They were saved in the project, not in the layer style; save the ",[18,2020,1944],{}," category to the data source.",[234,2023,2024,1037,2030,2032],{},[237,2025,2026,2027,2029],{},"The date widget shows ",[18,2028,733],{}," for every row.",[18,2031,1306],{}," does not match how dates are stored in a text field.",[234,2034,2035,2038,2039,2041],{},[237,2036,2037],{},"Photo preview is blank."," The path is relative to a different root than ",[18,2040,1447],{}," expects.",[226,2043,2045],{"id":2044},"conclusion","Conclusion",[14,2047,2048,2049,2051],{},"Give every field that people type into a widget that only accepts valid values: value maps for short fixed lists, value relations for shared or changing ones, ranges, checkboxes and date pickers for typed data, and attachment widgets for files. Keep the configuration as a scheme in code, apply it across layers, and save the ",[18,2050,1944],{}," style category with the data so the form follows the layer wherever it goes.",[226,2053,2055],{"id":2054},"frequently-asked-questions","Frequently Asked Questions",[14,2057,2058,2061,2062,2066],{},[237,2059,2060],{},"Do widgets validate values written by scripts?","\nNo. They constrain user input in forms and the attribute table; values written through the API bypass them. Use ",[47,2063,2065],{"href":2064},"\u002Fqgis-plugin-development\u002Fattribute-forms-and-layer-actions\u002Fset-default-values-and-field-constraints-pyqgis\u002F","field constraints"," for rules that must hold regardless.",[14,2068,2069,2072,2075],{},[237,2070,2071],{},"Can I see which widget types are available?",[18,2073,2074],{},"QgsGui.editorWidgetRegistry().factories()"," returns a dictionary keyed by type name.",[14,2077,2078,2081],{},[237,2079,2080],{},"Do widgets work in QField and Mergin Maps?","\nYes — both read the form configuration from the project, so widgets set here carry over to mobile data collection.",[14,2083,2084,2087,2088,2091],{},[237,2085,2086],{},"How do I hide a field from the form?","\nUse the ",[18,2089,2090],{},"Hidden"," widget type with an empty configuration.",[226,2093,2095],{"id":2094},"related","Related",[231,2097,2098,2103,2108,2114,2119],{},[234,2099,2100,2102],{},[47,2101,50],{"href":49}," — the guide this recipe belongs to",[234,2104,2105],{},[47,2106,2107],{"href":2064},"Set Default Values and Field Constraints in PyQGIS",[234,2109,2110],{},[47,2111,2113],{"href":2112},"\u002Fqgis-plugin-development\u002Fattribute-forms-and-layer-actions\u002Fbuild-tabbed-attribute-form-layout-pyqgis\u002F","Build a Tabbed Attribute Form Layout in PyQGIS",[234,2115,2116],{},[47,2117,2118],{"href":1064},"Define Layer Relations in PyQGIS",[234,2120,2121],{},[47,2122,2124],{"href":2123},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fadd-field-to-layer-pyqgis\u002F","Add a Field to a Layer in PyQGIS",[2126,2127,2128],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":264,"searchDepth":288,"depth":288,"links":2130},[2131,2132,2133,2134,2135,2136,2137,2138,2139,2140,2141,2142],{"id":228,"depth":288,"text":229},{"id":249,"depth":288,"text":250},{"id":519,"depth":288,"text":520},{"id":858,"depth":288,"text":859},{"id":1069,"depth":288,"text":1070},{"id":1314,"depth":288,"text":1315},{"id":1464,"depth":288,"text":1465},{"id":1957,"depth":288,"text":1958},{"id":1987,"depth":288,"text":1988},{"id":2044,"depth":288,"text":2045},{"id":2054,"depth":288,"text":2055},{"id":2094,"depth":288,"text":2095},"Replace free-text attribute entry with drop-downs, lookups, sliders, date pickers, checkboxes and photo attachments by setting QgsEditorWidgetSetup on each field — and roll the same form configuration out to many layers from one script.","md",{"slug":2146,"type":2147,"breadcrumb":2148,"datePublished":2149,"dateModified":2149},"configure-editor-widgets-pyqgis","article","Configure Editor Widgets","2026-09-17","\u002Fqgis-plugin-development\u002Fattribute-forms-and-layer-actions\u002Fconfigure-editor-widgets-pyqgis",{"title":5,"description":2143},"qgis-plugin-development\u002Fattribute-forms-and-layer-actions\u002Fconfigure-editor-widgets-pyqgis\u002Findex","KuZg9xO_F3-3OXrlA_y8LmAGVv6YKeH8K-kFL8EjxBc",1789632907424]