diff --git a/src/Model.php b/src/Model.php
index 75e2da3aee..046fa802f0 100644
--- a/src/Model.php
+++ b/src/Model.php
@@ -1074,7 +1074,7 @@ public function withId($id)
      */
     public function addWith(self $model, string $alias, array $mapping = [], bool $recursive = false)
     {
-        if (isset($this->with[$alias])) {
+        if ($alias === $this->table || $alias === $this->table_alias || isset($this->with[$alias])) {
             throw (new Exception('With cursor already set with given alias'))
                 ->addMoreInfo('alias', $alias);
         }
diff --git a/src/Model/Join.php b/src/Model/Join.php
index f5da760d3e..961f88dd00 100644
--- a/src/Model/Join.php
+++ b/src/Model/Join.php
@@ -31,10 +31,9 @@ class Join
     }
 
     /**
-     * Name of the table (or collection) that can be used to retrieve data from.
-     * For SQL, This can also be an expression or sub-select.
+     * Foreign model or WITH/CTE alias when used with SQL persistence.
      *
-     * @var string
+     * @var Model|string
      */
     protected $foreign_table;
 
@@ -120,18 +119,32 @@ class Join
     /** @var array<int, array<string, mixed>> Data indexed by spl_object_id(entity) which is populated here as the save/insert progresses. */
     private $saveBufferByOid = [];
 
-    public function __construct(string $foreign_table = null)
+    /**
+     * @param Model|string $foreignTable
+     */
+    public function __construct($foreignTable = null)
     {
-        $this->foreign_table = $foreign_table;
+        $this->foreign_table = $foreignTable;
 
         // handle foreign table containing a dot - that will be reverse join
-        if (strpos($this->foreign_table, '.') !== false) {
+        if (is_string($this->foreign_table) && strpos($this->foreign_table, '.') !== false) {
             // split by LAST dot in foreign_table name
             [$this->foreign_table, $this->foreign_field] = preg_split('~\.+(?=[^.]+$)~', $this->foreign_table);
             $this->reverse = true;
         }
     }
 
+    public function getForeignModel(): Model
+    {
+        if (is_string($this->foreign_table)) {
+            return $this->getOwner()->with[$this->foreign_table]['model'];
+        }
+
+        $this->foreign_table->assertIsModel();
+
+        return $this->foreign_table;
+    }
+
     /**
      * @param Model $owner
      *
@@ -203,6 +216,8 @@ protected function init(): void
     {
         $this->_init();
 
+        $this->getForeignModel(); // assert valid foreign_table
+
         // owner model should have id_field set
         $id_field = $this->getOwner()->id_field;
         if (!$id_field) {
@@ -278,25 +293,27 @@ public function addFields(array $fields = [], array $defaults = [])
     /**
      * Another join will be attached to a current join.
      *
+     * @param Model|string         $foreignTable
      * @param array<string, mixed> $defaults
      */
-    public function join(string $foreign_table, array $defaults = []): self
+    public function join($foreignTable, array $defaults = []): self
     {
         $defaults['joinName'] = $this->short_name;
 
-        return $this->getOwner()->join($foreign_table, $defaults);
+        return $this->getOwner()->join($foreignTable, $defaults);
     }
 
     /**
      * Another leftJoin will be attached to a current join.
      *
+     * @param Model|string         $foreignTable
      * @param array<string, mixed> $defaults
      */
-    public function leftJoin(string $foreign_table, array $defaults = []): self
+    public function leftJoin($foreignTable, array $defaults = []): self
     {
         $defaults['joinName'] = $this->short_name;
 
-        return $this->getOwner()->leftJoin($foreign_table, $defaults);
+        return $this->getOwner()->leftJoin($foreignTable, $defaults);
     }
 
     /**
diff --git a/src/Model/JoinsTrait.php b/src/Model/JoinsTrait.php
index 02ed7d7127..ead134f8a6 100644
--- a/src/Model/JoinsTrait.php
+++ b/src/Model/JoinsTrait.php
@@ -5,6 +5,7 @@
 namespace Atk4\Data\Model;
 
 use Atk4\Data\Exception;
+use Atk4\Data\Model;
 
 /**
  * Provides native Model methods for join functionality.
@@ -21,9 +22,10 @@ trait JoinsTrait
      * join will also query $foreignTable in order to find additional fields. When inserting
      * the record will be also added inside $foreignTable and relationship will be maintained.
      *
+     * @param Model|string         $foreignTable
      * @param array<string, mixed> $defaults
      */
-    public function join(string $foreignTable, array $defaults = []): Join
+    public function join($foreignTable, array $defaults = []): Join
     {
         $this->assertIsModel();
 
@@ -47,9 +49,10 @@ public function join(string $foreignTable, array $defaults = []): Join
      *
      * @see join()
      *
+     * @param Model|string         $foreignTable
      * @param array<string, mixed> $defaults
      */
-    public function leftJoin(string $foreignTable, array $defaults = []): Join
+    public function leftJoin($foreignTable, array $defaults = []): Join
     {
         $defaults['weak'] = true;