as part of a migration script I'm trying to:

  1. create 2 new fields,
  2. add them to a field group
  3. add the new fields to a specific tab on an entry type
  4. Update the values of the new fields

What I have so far works up until point 4, in the database the fields exist on the entry, I can see them there when I browse to an entry in the CMS, I can save new values to them, but, I cannot set a value with setFieldValues straight after. Can anyone suggest what I've done wrong here?

Here is my code so far:

public function safeUp()
    // Get contact field group
    $groupId = 31; // contact field group
    $fields = Craft::$app->getFields(); // Get fields
    $fieldGroup = $fields->getGroupById($groupId); // Get contact field group

    // Create heading field
    $headingFieldHandle = 'contactThanksHeading';
    $headingField = new PlainText([
        'groupId' => $fieldGroup['id'],
        'name' => 'Contact Thanks Heading',
        'handle' => $headingFieldHandle,
        "instructions" => "Larger heading text for the contact form's thanks message",
        "required" => false,
        "placeholder" => "Thank you",
        "charLimit" => 300,
        "multiline" => false,
        "initialRows" => "4",
    // Save heading field

    // Create copy field
    $copyFieldHandle = 'contactThanksCopy';
    $copyField = new PlainText([
        'groupId' => $fieldGroup['id'],
        'name' => 'Contact Thanks Copy',
        'handle' => $copyFieldHandle,
        "instructions" => "Smaller copy text for the contact form's thanks message",
        "required" => false,
        "placeholder" => "One of our team will get back shortly...",
        "charLimit" => 300,
        "multiline" => true,
        "initialRows" => "4",
    // Save copy field

    // Get contact single section
    $contactSection = Craft::$app->getSections()->getSectionById(2);
    // Get the section's entry types
    $contactEntryTypes = $contactSection->getEntryTypes();

    // Find entry type 2
    foreach ($contactEntryTypes as $entryType) {
        if ($entryType->id === '2') {
            $contactEntryType = $entryType;

    // Get the field layout for entry type 2
    $contactFieldLayout = $contactEntryType->getFieldLayout(2);
    // Loop on tabs to find common
    foreach($contactFieldLayout->tabs as $tab) {
        if ($tab->name === 'Common') {
            $contactCommonTab = $tab;

    // get the common tab's existing fields
    $tabFields = $contactCommonTab->getFields();

    // Set the sort order of the new fields to append them to the tab
    $headingField->sortOrder = count($tabFields);
    $copyField->sortOrder = count($tabFields)+1;

    // Add new fields to Common tab array
    $newFields = array_merge($tabFields,[$headingField, $copyField]);
    // Save fields to tab

    // Set the tabs on the field layout

    // Save the contact field layout

    // get the contact entry
    $contactEntry = Craft::$app->entries->getEntryById(8);
    // Set the contact entry values
        $headingFieldHandle => '1',
        $copyFieldHandle => '1',
    // save the contact entry

The main issue here ended up being a Craft bug (craftcms/cms#6651), which has been fixed for the next release (3.5.5).

There are a couple ways your code could be improved as well, though:

  • $fieldGroup is a craft\models\FieldGroup object, not an array, so you should access its ID via $fieldGroup->id, not $fieldGroup['id']. (Or just use the $groupId property.)

  • Avoid using === for numeric properties like id, unless you know for certain what the type is going to be. IDs should be casted as integers, not strings, and places where they are strings should be considered a minor bug. (It’s due to how MySQL returns the data, but not an issue in PostgreSQL, and we are working toward always ensuring that they are typed as ints correctly even for MySQL.) Safest thing for you to do is replace === with == on this line:

    if ($entryType->id === '2') {
  • Your two continue statements should be break. continue just tells the loop to move onto the next cycle of the loop; break tells it to completely break out of the loop.

  • $tabsForSaving is never defined. I’m surprised that’s not causing a PHP error for you.

  • Cheers for the code review. Ah yes I stripped $tabsForSaving out, discovered that I did not need to call setTabs again on the layout for my new fields to be saved to the layout.
    – joep
    Aug 18 '20 at 17:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.