diff --git a/Civi/Api4/Action/LocBlock/LocBlockSaveTrait.php b/Civi/Api4/Action/LocBlock/LocBlockSaveTrait.php
index 04359efcf738fce380189dfaa6f38238b30983b9..adc8e9dfdbcb388aa8ac7bb829fe51cd1dc77e19 100644
--- a/Civi/Api4/Action/LocBlock/LocBlockSaveTrait.php
+++ b/Civi/Api4/Action/LocBlock/LocBlockSaveTrait.php
@@ -48,7 +48,7 @@ trait LocBlockSaveTrait {
         $entityId = $params[$joinField] ?? $locBlock[$joinField] ?? NULL;
         if ($item) {
           $labelField = CoreUtil::getInfoItem($joinEntity, 'label_field');
-          // If NULL was given for the main field (e.g. `email`) then delete the record IF it's not in use
+          // If NULL was given for the required field (e.g. `email`) then delete the record IF it's not in use
           if (!empty($params['id']) && $entityId && $labelField && array_key_exists($labelField, $item) && ($item[$labelField] === NULL || $item[$labelField] === '')) {
             $referenceCount = CoreUtil::getRefCountTotal($joinEntity, $entityId);
             if ($referenceCount <= 1) {
@@ -60,7 +60,8 @@ trait LocBlockSaveTrait {
               ]);
             }
           }
-          else {
+          // Otherwise save if the required field (e.g. `email`) has a value (or no fields are required)
+          elseif (!array_key_exists($labelField, $item) || (isset($item[$labelField]) &&  $item[$labelField] !== '')) {
             $item['contact_id'] = '';
             if ($entityId) {
               $item['id'] = $entityId;
diff --git a/Civi/Api4/Generic/DAOGetFieldsAction.php b/Civi/Api4/Generic/DAOGetFieldsAction.php
index 9699e2cced3765c341c22427999d60abbdc517aa..90fe7c6864b2056a2903f4fec2f2f5cbb0823c15 100644
--- a/Civi/Api4/Generic/DAOGetFieldsAction.php
+++ b/Civi/Api4/Generic/DAOGetFieldsAction.php
@@ -43,11 +43,18 @@ class DAOGetFieldsAction extends BasicGetFieldsAction {
     if ($this->loadOptions) {
       $this->loadFieldOptions($fields, $fieldsToGet ?: array_keys($fields));
     }
+    // Add fields across implicit FK joins
     foreach ($fieldsToGet ?? [] as $fieldName) {
       if (empty($fields[$fieldName]) && str_contains($fieldName, '.')) {
         $fkField = $this->getFkFieldSpec($fieldName, $fields);
         if ($fkField) {
+          $fieldPrefix = substr($fieldName, 0, 0 - strlen($fkField['name']));
           $fkField['name'] = $fieldName;
+          // Control field should get the same prefix as it belongs to the new entity now
+          if (!empty($fkField['input_attrs']['control_field'])) {
+            $fkField['input_attrs']['control_field'] = $fieldPrefix . $fkField['input_attrs']['control_field'];
+          }
+          $fkField['required'] = FALSE;
           $fields[] = $fkField;
         }
       }
@@ -69,7 +76,7 @@ class DAOGetFieldsAction extends BasicGetFieldsAction {
    * @return array|null
    * @throws \CRM_Core_Exception
    */
-  private function getFkFieldSpec($fieldName, $fields) {
+  private function getFkFieldSpec(string $fieldName, array $fields): ?array {
     $fieldPath = explode('.', $fieldName);
     // Search for the first segment alone plus the first and second
     // No field in the schema contains more than one dot in its name.
@@ -81,9 +88,11 @@ class DAOGetFieldsAction extends BasicGetFieldsAction {
         'checkPermissions' => $this->checkPermissions,
         'where' => [['name', '=', $newFieldName]],
         'loadOptions' => $this->loadOptions,
+        'values' => FormattingUtil::filterByPath($this->values, $fieldName, $newFieldName),
         'action' => $this->action,
       ])->first();
     }
+    return NULL;
   }
 
   /**
diff --git a/ext/afform/core/Civi/Api4/Action/Afform/Submit.php b/ext/afform/core/Civi/Api4/Action/Afform/Submit.php
index 11fee493392ebbfba8e5b2f5323901834807d46e..4315e35d5e4cf0de0a44346f43bb724a24eb170f 100644
--- a/ext/afform/core/Civi/Api4/Action/Afform/Submit.php
+++ b/ext/afform/core/Civi/Api4/Action/Afform/Submit.php
@@ -460,8 +460,8 @@ class Submit extends AbstractProcessor {
       // Forward FK e.g. Event.loc_block_id => LocBlock
       $forwardFkField = self::getFkField($mainEntity['type'], $joinEntityName);
       if ($forwardFkField && $values) {
-        // Add id to values for update op
-        if ($whereClause) {
+        // Add id to values for update op, but only if id is not already on the form
+        if ($whereClause && empty($mainEntity['joins'][$joinEntityName]['fields'][$joinIdField])) {
           $values[0][$joinIdField] = $whereClause[0][2];
         }
         $result = civicrm_api4($joinEntityName, 'save', [
diff --git a/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformEventUsageTest.php b/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformEventUsageTest.php
index 75149a9ef6e5b816d1275aece3a335350618a40e..225fa30552985efb1749fbf81d484b5971d5b84b 100644
--- a/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformEventUsageTest.php
+++ b/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformEventUsageTest.php
@@ -2,13 +2,14 @@
 namespace api\v4\Afform;
 
 use Civi\Api4\Afform;
+use Civi\Test\TransactionalInterface;
 
 /**
  * Test case for Afform.prefill and Afform.submit with Event records.
  *
  * @group headless
  */
-class AfformEventUsageTest extends AfformUsageTestCase {
+class AfformEventUsageTest extends AfformUsageTestCase implements TransactionalInterface {
   use \Civi\Test\Api4TestTrait;
 
   /**
@@ -75,4 +76,110 @@ EOHTML;
     $this->assertSame('2234567', $prefill['values'][0]['joins']['LocBlock'][0]['phone_id.phone']);
   }
 
+  /**
+   * Test saving & updating
+   */
+  public function testEventLocationUpdate(): void {
+    $layout = <<<EOHTML
+<af-form ctrl="afform">
+  <af-entity actions="{create: true, update: true}" type="Event" name="Event1" label="Event 1" security="FBAC" />
+  <fieldset af-fieldset="Event1" class="af-container" af-title="Event 1">
+    <af-field name="id" />
+    <af-field name="title" />
+    <af-field name="start_date" />
+    <af-field name="event_type_id" />
+    <div af-join="LocBlock">
+      <af-field name="id" />
+      <af-field name="email_id.email" />
+      <af-field name="address_id.street_address" />
+    </div>
+  </fieldset>
+  <button class="af-button btn btn-primary" crm-icon="fa-check" ng-click="afform.submit()">Submit</button>
+</af-form>
+EOHTML;
+
+    $this->useValues([
+      'layout' => $layout,
+      'permission' => \CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION,
+    ]);
+
+    // Create a new event with a new location
+    $submit = Afform::submit()
+      ->setName($this->formName)
+      ->setValues([
+        'Event1' => [
+          [
+            'fields' => [
+              'title' => 'Event Title 1',
+              'start_date' => '2021-01-01 00:00:00',
+              'event_type_id' => 1,
+            ],
+            'joins' => [
+              'LocBlock' => [
+                [
+                  'email_id.email' => 'test1@te.st',
+                  'address_id.street_address' => '12345',
+                ],
+              ],
+            ],
+          ],
+        ],
+      ])->execute();
+
+    $event1 = $submit[0]['Event1'][0]['id'];
+    $loc1 = $submit[0]['Event1'][0]['joins']['LocBlock'][0]['id'];
+
+    // Create a 2nd new event with a new location
+    $submit = Afform::submit()
+      ->setName($this->formName)
+      ->setValues([
+        'Event1' => [
+          [
+            'fields' => [
+              'title' => 'Event Title 2',
+              'start_date' => '2022-01-01 00:00:00',
+              'event_type_id' => 1,
+            ],
+            'joins' => [
+              'LocBlock' => [
+                [
+                  'id' => $loc1,
+                ],
+              ],
+            ],
+          ],
+        ],
+      ])->execute();
+
+    $event2 = $submit[0]['Event1'][0]['id'];
+    $this->assertGreaterThan($event1, $event2);
+    $this->assertSame($loc1, $submit[0]['Event1'][0]['joins']['LocBlock'][0]['id']);
+
+    // Update event 1 with a new location
+    $submit = Afform::submit()
+      ->setName($this->formName)
+      ->setValues([
+        'Event1' => [
+          [
+            'fields' => [
+              'id' => $event1,
+              'title' => 'Event Title 1 Updated',
+            ],
+            'joins' => [
+              'LocBlock' => [
+                [
+                  'id' => NULL,
+                  'address_id.street_address' => '12345',
+                ],
+              ],
+            ],
+          ],
+        ],
+      ])->execute();
+
+    $this->assertSame($event1, $submit[0]['Event1'][0]['id']);
+    $this->assertGreaterThan($loc1, $submit[0]['Event1'][0]['joins']['LocBlock'][0]['id']);
+
+  }
+
 }
diff --git a/tests/phpunit/api/v4/Action/GetExtraFieldsTest.php b/tests/phpunit/api/v4/Action/GetExtraFieldsTest.php
index 27f56c525c4c6a02bf8324d43ffad40ceffc9327..01798f9bb981fc8eb0fef4b3be737753a3513067 100644
--- a/tests/phpunit/api/v4/Action/GetExtraFieldsTest.php
+++ b/tests/phpunit/api/v4/Action/GetExtraFieldsTest.php
@@ -25,6 +25,7 @@ use Civi\Api4\Address;
 use Civi\Api4\Contact;
 use Civi\Api4\Household;
 use Civi\Api4\Individual;
+use Civi\Api4\LocBlock;
 use Civi\Api4\Tag;
 
 /**
@@ -101,6 +102,20 @@ class GetExtraFieldsTest extends Api4TestBase {
     $this->assertGreaterThan(1, count($fields['contact_id.gender_id']['options']));
   }
 
+  public function testGetLocBlockFields() {
+    $field = LocBlock::getFields(FALSE)
+      ->setLoadOptions(TRUE)
+      ->addWhere('name', '=', 'address_id.state_province_id')
+      ->addValue('address_id.country_id', 1039)
+      ->execute()->single();
+
+    $this->assertEquals('Address', $field['entity']);
+    $this->assertEquals('address_id.state_province_id', $field['name']);
+    $this->assertEquals('address_id.country_id', $field['input_attrs']['control_field']);
+    $this->assertContains('Manitoba', $field['options']);
+    $this->assertNotContains('Alabama', $field['options']);
+  }
+
   public function testGetTagsFromFilterField(): void {
     $actTag = Tag::create(FALSE)
       ->addValue('name', uniqid('act'))
diff --git a/tests/phpunit/api/v4/Entity/LocBlockTest.php b/tests/phpunit/api/v4/Entity/LocBlockTest.php
index fed4245fc97eb5c5e4c6db780847e54be294c09e..b4e8890327634cf6b6bf77d228739d216b3ebd2f 100644
--- a/tests/phpunit/api/v4/Entity/LocBlockTest.php
+++ b/tests/phpunit/api/v4/Entity/LocBlockTest.php
@@ -22,11 +22,12 @@ namespace api\v4\Entity;
 use api\v4\Api4TestBase;
 use Civi\Api4\Email;
 use Civi\Api4\LocBlock;
+use Civi\Test\TransactionalInterface;
 
 /**
  * @group headless
  */
-class LocBlockTest extends Api4TestBase {
+class LocBlockTest extends Api4TestBase implements TransactionalInterface {
 
   /**
    * @throws \CRM_Core_Exception
@@ -52,18 +53,54 @@ class LocBlockTest extends Api4TestBase {
       ->addValue('email_2_id.email', 'third@e.mail')
       ->execute()->first();
 
+    // Void both emails in block 1
     LocBlock::update(FALSE)
       ->addWhere('id', '=', $locBlock1['id'])
       ->addValue('email_id.email', '')
       ->addValue('email_2_id.email', '')
       ->execute();
 
+    // 1 email has been deleted, the other preserved (because it's shared with block 2)
     $email1 = Email::get(FALSE)
       ->addSelect('id')
       ->addWhere('id', 'IN', [$locBlock1['email_id'], $locBlock1['email_2_id']])
       ->execute()->column('id');
 
-    $this->assertEquals([$locBlock1['email_id']], (array) $email1);
+    $this->assertEquals([$locBlock1['email_id']], $email1);
+  }
+
+  public function testLocBlockWithBlankValues(): void {
+    $locBlockId = LocBlock::create(FALSE)
+      ->addValue('address_id.street_address', '')
+      ->addValue('phone_id.phone', '')
+      ->addValue('email_id.email', '')
+      ->execute()->first()['id'];
+
+    // Get locBlock
+    $locBlock = LocBlock::get(FALSE)
+      ->addWhere('id', '=', $locBlockId)
+      ->execute()->single();
+
+    $this->assertNotEmpty($locBlock['address_id']);
+    $this->assertEmpty($locBlock['phone_id']);
+    $this->assertEmpty($locBlock['email_id']);
+
+    // Update with a 2nd blank email & an address
+    LocBlock::update(FALSE)
+      ->addWhere('id', '=', $locBlockId)
+      ->addValue('email2_id.email', '')
+      ->addValue('address_id.street_address', '123')
+      ->execute();
+
+    $updatedLocBlock = LocBlock::get(FALSE)
+      ->addWhere('id', '=', $locBlockId)
+      ->addSelect('*', 'address_id.street_address', 'phone_id.phone', 'email_id.email')
+      ->execute()->single();
+
+    $this->assertEquals($locBlock['address_id'], $updatedLocBlock['address_id']);
+    $this->assertEquals('123', $updatedLocBlock['address_id.street_address']);
+    $this->assertNull($updatedLocBlock['phone_id.phone']);
+    $this->assertNull($updatedLocBlock['email_id.email']);
   }
 
 }