Hidden and server-only children
Some rows carry data that a person should not edit, such as an entity id. Add a child of type value, or a child with #access => FALSE. It gets no column and no input.
$form['people'] = [ '#type' => 'element_multiple', '#title' => $this->t('People'), '#header' => TRUE, '#element' => [ 'id' => [ '#type' => 'value', ], 'first_name' => [ '#type' => 'textfield', '#title' => $this->t('First name'), '#title_display' => 'invisible', '#placeholder' => $this->t('Enter first name'), ], 'last_name' => [ '#type' => 'textfield', '#title' => $this->t('Last name'), '#title_display' => 'invisible', '#placeholder' => $this->t('Enter last name'), ], ], '#default_value' => [ ['id' => 'john', 'first_name' => 'John', 'last_name' => 'Smith'], ['id' => 'jane', 'first_name' => 'Jane', 'last_name' => 'Doe'], ],];
The submitted value still holds the id of each row:
[ ['id' => 'john', 'first_name' => 'John', 'last_name' => 'Smith'], ['id' => 'jane', 'first_name' => 'Jane', 'last_name' => 'Doe'],]Where the value lives
Section titled “Where the value lives”valuechildren and children with#access => FALSEare stored on the server, in the form state, and tied to their row. The page does not contain them. A forged request cannot change them.- A child of type
hiddenis a real hidden input in the page. The browser sends it back, so treat it as user input. - A row added in the browser has empty server-only values.
- A child that is disabled with
'#disabled' => TRUEis kept the same way. A browser does not post it, so the module gives it the value of its own row from the#default_value, also after the rows are reordered.
A form that is out of date
Section titled “A form that is out of date”Each row carries a token that names it. The token of a row that comes from #default_value depends on the position of the row and on the values the server keeps for it. The token of a row the user added is signed for the element.
When a submission carries a token that is neither the token of a current default row nor a signed token of a new row, the form is out of date: the #default_value changed since the page was built, or the token was forged. The element does not drop the values of the server in silence, it fails the validation with the message “The form is out of date. Reload the page and try again.” and the form is not submitted. A row posted without any token is a row added by a script, it has no server-only values and no error.
If your #default_value changes between two requests, for example because it depends on the time, rows with server-only values can make a form out of date. Keep the defaults the same while the form is open.