Body: <p>With this kind of setup:</p>
  <pre class="lang-php prettyprint-override"><code>function random()
  {
      return array_reduce(range(0, 9), fn ($x) =&gt; $x . range('a', 'z')[rand(0, 25)], '');
  }
  
  $salt = random();
  
  $result = Craft::$app-&gt;getFields()-&gt;saveField($field = new craft\fields\Number([
      'handle' =&gt; $salt,
      'name' =&gt; $salt,
  ]));
  assert($result === true);
  </code></pre>
  <p>I can add this field to a <code>User</code>:</p>
  <pre class="lang-php prettyprint-override"><code>$result = Craft::$app-&gt;getFields()-&gt;saveLayout(new craft\models\FieldLayout([
      'fields' =&gt; [$field],
      'type' =&gt; craft\elements\User::class,
  ]));
  assert($result === true);
  
  $result = Craft::$app-&gt;getElements()-&gt;saveElement($user = new craft\elements\User([
      'email' =&gt; &quot;$salt@domain.com&quot;,
      'username' =&gt; $salt,
      $salt =&gt; 42,
  ]));
  assert($result === true);
  assert($user-&gt;{$salt} === 42);
  assert(Craft::$app-&gt;getElements()-&gt;getElementById($user-&gt;id, craft\elements\User::class)-&gt;{$salt} === 42);
  </code></pre>
  <p>and to an <code>Order</code>:</p>
  <pre class="lang-php prettyprint-override"><code>$result = Craft::$app-&gt;getFields()-&gt;saveLayout(new craft\models\FieldLayout([
      'fields' =&gt; [$field],
      'type' =&gt; craft\commerce\elements\Order::class,
  ]));
  assert($result === true);
  
  $result = Craft::$app-&gt;getElements()-&gt;saveElement($order = new craft\commerce\elements\Order([
      $salt =&gt; 42,
  ]));
  assert($result);
  assert($order-&gt;{$salt} === 42);
  assert(Craft::$app-&gt;getElements()-&gt;getElementById($order-&gt;id, craft\commerce\elements\Order::class)-&gt;{$salt} === 42);
  </code></pre>
  <p>but I cannot do the same with a <code>Product</code>:</p>
  <pre class="lang-php prettyprint-override"><code>$result = Craft::$app-&gt;getFields()-&gt;saveLayout(new craft\models\FieldLayout([
      'fields' =&gt; [$field],
      'type' =&gt; craft\commerce\elements\Product::class,
  ]));
  assert($result === true);
  
  $result = craft\commerce\Plugin::getInstance()-&gt;getProductTypes()-&gt;saveProductType($productType = new craft\commerce\models\ProductType([
      'handle' =&gt; $salt,
      'hasDimensions' =&gt; false,
      'hasVariants' =&gt; false,
      'name' =&gt; $salt,
      'siteSettings' =&gt; [
          Craft::$app-&gt;getSites()-&gt;getCurrentSite()-&gt;id =&gt; new craft\commerce\models\ProductTypeSite(),
      ],
  ]));
  assert($result === true);
  
  $result = Craft::$app-&gt;getElements()-&gt;saveElement($product = new craft\commerce\elements\Product([
      'slug' =&gt; $salt,
      'title' =&gt; $salt,
      'typeId' =&gt; $productType-&gt;id,
      'variants' =&gt; [
          [
              'hasUnlimitedStock' =&gt; false,
              'price' =&gt; 0,
              'maxQty' =&gt; 0,
              'minQty' =&gt; 0,
          ],
      ],
      $salt =&gt; 42,
  ]));
  assert($result === true);
  assert($product-&gt;{$salt} === 42);
  assert(Craft::$app-&gt;getElements()-&gt;getElementById($product-&gt;id, craft\commerce\elements\Product::class)-&gt;{$salt} === 42);
  </code></pre>
  <p>Both assertions fail (though the <code>Product</code> is saved without errors), so I'm assuming I am missing some additional piece of ceremony in the creation. What could that be?</p>
  
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2023-08-07T12:52:00.827Z
Comments.Text: Whats the result of `Plugin::getInstance()->getProductTypes()->saveProductType`?
Comments.UserDisplayName: Luke Holder
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2023-08-07T14:20:22.727Z
Comments.Text: @LukeHolder a fair question. I've added assertions on the results of each call; they all return `true`. In preparing the test case, I also checked the errors (`assert(!$x->hasErrors());`) for each created item, they also passed.
Comments.UserDisplayName: msbit
Tags: <plugin-craftcommerce>
Title: Commerce 3: How to programmatically create a custom field and assign it to a Product
ThreadText: # Commerce 3: How to programmatically create a custom field and assign it to a Product
  
  (Asked by: username_0 on 2023-08-07)
  
  With this kind of setup:
  
  ```php
  function random()
  {
      return array_reduce(range(0, 9), fn ($x) => $x . range('a', 'z')[rand(0, 25)], '');
  }
  
  $salt = random();
  
  $result = Craft::$app->getFields()->saveField($field = new craft\fields\Number([
      'handle' => $salt,
      'name' => $salt,
  ]));
  assert($result === true);
  
  ```
  
  I can add this field to a `User`:
  
  ```php
  $result = Craft::$app->getFields()->saveLayout(new craft\models\FieldLayout([
      'fields' => [$field],
      'type' => craft\elements\User::class,
  ]));
  assert($result === true);
  
  $result = Craft::$app->getElements()->saveElement($user = new craft\elements\User([
      'email' => "$salt@domain.com",
      'username' => $salt,
      $salt => 42,
  ]));
  assert($result === true);
  assert($user->{$salt} === 42);
  assert(Craft::$app->getElements()->getElementById($user->id, craft\elements\User::class)->{$salt} === 42);
  
  ```
  
  and to an `Order`:
  
  ```php
  $result = Craft::$app->getFields()->saveLayout(new craft\models\FieldLayout([
      'fields' => [$field],
      'type' => craft\commerce\elements\Order::class,
  ]));
  assert($result === true);
  
  $result = Craft::$app->getElements()->saveElement($order = new craft\commerce\elements\Order([
      $salt => 42,
  ]));
  assert($result);
  assert($order->{$salt} === 42);
  assert(Craft::$app->getElements()->getElementById($order->id, craft\commerce\elements\Order::class)->{$salt} === 42);
  
  ```
  
  but I cannot do the same with a `Product`:
  
  ```php
  $result = Craft::$app->getFields()->saveLayout(new craft\models\FieldLayout([
      'fields' => [$field],
      'type' => craft\commerce\elements\Product::class,
  ]));
  assert($result === true);
  
  $result = craft\commerce\Plugin::getInstance()->getProductTypes()->saveProductType($productType = new craft\commerce\models\ProductType([
      'handle' => $salt,
      'hasDimensions' => false,
      'hasVariants' => false,
      'name' => $salt,
      'siteSettings' => [
          Craft::$app->getSites()->getCurrentSite()->id => new craft\commerce\models\ProductTypeSite(),
      ],
  ]));
  assert($result === true);
  
  $result = Craft::$app->getElements()->saveElement($product = new craft\commerce\elements\Product([
      'slug' => $salt,
      'title' => $salt,
      'typeId' => $productType->id,
      'variants' => [
          [
              'hasUnlimitedStock' => false,
              'price' => 0,
              'maxQty' => 0,
              'minQty' => 0,
          ],
      ],
      $salt => 42,
  ]));
  assert($result === true);
  assert($product->{$salt} === 42);
  assert(Craft::$app->getElements()->getElementById($product->id, craft\commerce\elements\Product::class)->{$salt} === 42);
  
  ```
  
  Both assertions fail (though the `Product` is saved without errors), so I'm assuming I am missing some additional piece of ceremony in the creation. What could that be?
  
  **Tags:** plugin-craftcommerce
  
  > **Comments:**
  > **username_1**: Whats the result of `Plugin::getInstance()->getProductTypes()->saveProductType`?  
  > **username_0**: @username_1 a fair question. I've added assertions on the results of each call; they all return `true`. In preparing the test case, I also checked the errors (`assert(!$x->hasErrors());`) for each created item, they also passed.  
  
  ---
  
  ## 0 answers
  
  