diff --git a/app/Exports/ZipExportBuilder.php b/app/Exports/ZipExportBuilder.php
index 2b8b45d0d..720b4997d 100644
--- a/app/Exports/ZipExportBuilder.php
+++ b/app/Exports/ZipExportBuilder.php
@@ -2,10 +2,9 @@
 
 namespace BookStack\Exports;
 
-use BookStack\Activity\Models\Tag;
 use BookStack\Entities\Models\Page;
 use BookStack\Exceptions\ZipExportException;
-use BookStack\Uploads\Attachment;
+use BookStack\Exports\ZipExportModels\ZipExportPage;
 use ZipArchive;
 
 class ZipExportBuilder
@@ -13,7 +12,8 @@ class ZipExportBuilder
     protected array $data = [];
 
     public function __construct(
-        protected ZipExportFiles $files
+        protected ZipExportFiles $files,
+        protected ZipExportReferences $references,
     ) {
     }
 
@@ -22,57 +22,21 @@ class ZipExportBuilder
      */
     public function buildForPage(Page $page): string
     {
-        $this->data['page'] = $this->convertPage($page);
+        $exportPage = ZipExportPage::fromModel($page, $this->files);
+        $this->data['page'] = $exportPage;
+
+        $this->references->addPage($exportPage);
+
         return $this->build();
     }
 
-    protected function convertPage(Page $page): array
-    {
-        $tags = array_map($this->convertTag(...), $page->tags()->get()->all());
-        $attachments = array_map($this->convertAttachment(...), $page->attachments()->get()->all());
-
-        return [
-            'id'          => $page->id,
-            'name'        => $page->name,
-            'html'        => '', // TODO
-            'markdown'    => '', // TODO
-            'priority'    => $page->priority,
-            'attachments' => $attachments,
-            'images'      => [], // TODO
-            'tags'        => $tags,
-        ];
-    }
-
-    protected function convertAttachment(Attachment $attachment): array
-    {
-        $data = [
-            'name'  => $attachment->name,
-            'order' => $attachment->order,
-        ];
-
-        if ($attachment->external) {
-            $data['link'] = $attachment->path;
-        } else {
-            $data['file'] = $this->files->referenceForAttachment($attachment);
-        }
-
-        return $data;
-    }
-
-    protected function convertTag(Tag $tag): array
-    {
-        return [
-            'name'  => $tag->name,
-            'value' => $tag->value,
-            'order' => $tag->order,
-        ];
-    }
-
     /**
      * @throws ZipExportException
      */
     protected function build(): string
     {
+        $this->references->buildReferences();
+
         $this->data['exported_at'] = date(DATE_ATOM);
         $this->data['instance'] = [
             'version'       => trim(file_get_contents(base_path('version'))),
diff --git a/app/Exports/ZipExportModels/ZipExportAttachment.php b/app/Exports/ZipExportModels/ZipExportAttachment.php
new file mode 100644
index 000000000..d6d674a91
--- /dev/null
+++ b/app/Exports/ZipExportModels/ZipExportAttachment.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace BookStack\Exports\ZipExportModels;
+
+use BookStack\Exports\ZipExportFiles;
+use BookStack\Uploads\Attachment;
+
+class ZipExportAttachment implements ZipExportModel
+{
+    public ?int $id = null;
+    public string $name;
+    public ?int $order = null;
+    public ?string $link = null;
+    public ?string $file = null;
+
+    public static function fromModel(Attachment $model, ZipExportFiles $files): self
+    {
+        $instance = new self();
+        $instance->id = $model->id;
+        $instance->name = $model->name;
+
+        if ($model->external) {
+            $instance->link = $model->path;
+        } else {
+            $instance->file = $files->referenceForAttachment($model);
+        }
+
+        return $instance;
+    }
+
+    public static function fromModelArray(array $attachmentArray, ZipExportFiles $files): array
+    {
+        return array_values(array_map(function (Attachment $attachment) use ($files) {
+            return self::fromModel($attachment, $files);
+        }, $attachmentArray));
+    }
+}
diff --git a/app/Exports/ZipExportModels/ZipExportImage.php b/app/Exports/ZipExportModels/ZipExportImage.php
new file mode 100644
index 000000000..73fe3bbf5
--- /dev/null
+++ b/app/Exports/ZipExportModels/ZipExportImage.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace BookStack\Exports\ZipExportModels;
+
+use BookStack\Activity\Models\Tag;
+
+class ZipExportImage implements ZipExportModel
+{
+    public string $name;
+    public string $file;
+}
diff --git a/app/Exports/ZipExportModels/ZipExportModel.php b/app/Exports/ZipExportModels/ZipExportModel.php
new file mode 100644
index 000000000..e1cb616de
--- /dev/null
+++ b/app/Exports/ZipExportModels/ZipExportModel.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace BookStack\Exports\ZipExportModels;
+
+use BookStack\App\Model;
+use BookStack\Exports\ZipExportFiles;
+
+interface ZipExportModel
+{
+//    public static function fromModel(Model $model, ZipExportFiles $files): self;
+}
diff --git a/app/Exports/ZipExportModels/ZipExportPage.php b/app/Exports/ZipExportModels/ZipExportPage.php
new file mode 100644
index 000000000..6589ce60a
--- /dev/null
+++ b/app/Exports/ZipExportModels/ZipExportPage.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace BookStack\Exports\ZipExportModels;
+
+use BookStack\Entities\Models\Page;
+use BookStack\Entities\Tools\PageContent;
+use BookStack\Exports\ZipExportFiles;
+
+class ZipExportPage implements ZipExportModel
+{
+    public ?int $id = null;
+    public string $name;
+    public ?string $html = null;
+    public ?string $markdown = null;
+    public ?int $priority = null;
+    /** @var ZipExportAttachment[] */
+    public array $attachments = [];
+    /** @var ZipExportImage[] */
+    public array $images = [];
+    /** @var ZipExportTag[] */
+    public array $tags = [];
+
+    public static function fromModel(Page $model, ZipExportFiles $files): self
+    {
+        $instance = new self();
+        $instance->id = $model->id;
+        $instance->name = $model->name;
+        $instance->html = (new PageContent($model))->render();
+
+        if (!empty($model->markdown)) {
+            $instance->markdown = $model->markdown;
+        }
+
+        $instance->tags = ZipExportTag::fromModelArray($model->tags()->get()->all());
+        $instance->attachments = ZipExportAttachment::fromModelArray($model->attachments()->get()->all(), $files);
+
+        return $instance;
+    }
+}
diff --git a/app/Exports/ZipExportModels/ZipExportTag.php b/app/Exports/ZipExportModels/ZipExportTag.php
new file mode 100644
index 000000000..636c9ff6d
--- /dev/null
+++ b/app/Exports/ZipExportModels/ZipExportTag.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace BookStack\Exports\ZipExportModels;
+
+use BookStack\Activity\Models\Tag;
+
+class ZipExportTag implements ZipExportModel
+{
+    public string $name;
+    public ?string $value = null;
+    public ?int $order = null;
+
+    public static function fromModel(Tag $model): self
+    {
+        $instance = new self();
+        $instance->name = $model->name;
+        $instance->value = $model->value;
+        $instance->order = $model->order;
+
+        return $instance;
+    }
+
+    public static function fromModelArray(array $tagArray): array
+    {
+        return array_values(array_map(self::fromModel(...), $tagArray));
+    }
+}
diff --git a/app/Exports/ZipExportReferences.php b/app/Exports/ZipExportReferences.php
new file mode 100644
index 000000000..89deb7eda
--- /dev/null
+++ b/app/Exports/ZipExportReferences.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace BookStack\Exports;
+
+use BookStack\App\Model;
+use BookStack\Exports\ZipExportModels\ZipExportAttachment;
+use BookStack\Exports\ZipExportModels\ZipExportPage;
+
+class ZipExportReferences
+{
+    /** @var ZipExportPage[] */
+    protected array $pages = [];
+    protected array $books = [];
+    protected array $chapters = [];
+
+    /** @var ZipExportAttachment[] */
+    protected array $attachments = [];
+
+    public function __construct(
+        protected ZipReferenceParser $parser,
+    ) {
+    }
+
+    public function addPage(ZipExportPage $page): void
+    {
+        if ($page->id) {
+            $this->pages[$page->id] = $page;
+        }
+
+        foreach ($page->attachments as $attachment) {
+            if ($attachment->id) {
+                $this->attachments[$attachment->id] = $attachment;
+            }
+        }
+    }
+
+    public function buildReferences(): void
+    {
+        // TODO - References to images, attachments, other entities
+
+        // TODO - Parse page MD & HTML
+        foreach ($this->pages as $page) {
+            $page->html = $this->parser->parse($page->html ?? '', function (Model $model): ?string {
+                // TODO - Handle found link to $model
+                //   - Validate we can see/access $model, or/and that it's
+                //     part of the export in progress.
+                return '[CAT]';
+            });
+            // TODO - markdown
+        }
+
+        // TODO - Parse chapter desc html
+        // TODO - Parse book desc html
+    }
+}
diff --git a/app/Exports/ZipReferenceParser.php b/app/Exports/ZipReferenceParser.php
new file mode 100644
index 000000000..6ca826bc3
--- /dev/null
+++ b/app/Exports/ZipReferenceParser.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace BookStack\Exports;
+
+use BookStack\App\Model;
+use BookStack\Entities\Queries\EntityQueries;
+use BookStack\References\ModelResolvers\BookLinkModelResolver;
+use BookStack\References\ModelResolvers\ChapterLinkModelResolver;
+use BookStack\References\ModelResolvers\CrossLinkModelResolver;
+use BookStack\References\ModelResolvers\PageLinkModelResolver;
+use BookStack\References\ModelResolvers\PagePermalinkModelResolver;
+
+class ZipReferenceParser
+{
+    /**
+     * @var CrossLinkModelResolver[]
+     */
+    protected array $modelResolvers;
+
+    public function __construct(EntityQueries $queries)
+    {
+        $this->modelResolvers = [
+            new PagePermalinkModelResolver($queries->pages),
+            new PageLinkModelResolver($queries->pages),
+            new ChapterLinkModelResolver($queries->chapters),
+            new BookLinkModelResolver($queries->books),
+            // TODO - Image
+            // TODO - Attachment
+        ];
+    }
+
+    /**
+     * Parse and replace references in the given content.
+     * @param callable(Model):(string|null) $handler
+     */
+    public function parse(string $content, callable $handler): string
+    {
+        $escapedBase = preg_quote(url('/'), '/');
+        $linkRegex = "/({$escapedBase}.*?)[\\t\\n\\f>\"'=?#]/";
+        $matches = [];
+        preg_match_all($linkRegex, $content, $matches);
+
+        if (count($matches) < 2) {
+            return $content;
+        }
+
+        foreach ($matches[1] as $link) {
+            $model = $this->linkToModel($link);
+            if ($model) {
+                $result = $handler($model);
+                if ($result !== null) {
+                    $content = str_replace($link, $result, $content);
+                }
+            }
+        }
+
+        return $content;
+    }
+
+
+    /**
+     * Attempt to resolve the given link to a model using the instance model resolvers.
+     */
+    protected function linkToModel(string $link): ?Model
+    {
+        foreach ($this->modelResolvers as $resolver) {
+            $model = $resolver->resolve($link);
+            if (!is_null($model)) {
+                return $model;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/dev/docs/portable-zip-file-format.md b/dev/docs/portable-zip-file-format.md
index d5635bd39..7a99563d1 100644
--- a/dev/docs/portable-zip-file-format.md
+++ b/dev/docs/portable-zip-file-format.md
@@ -128,6 +128,7 @@ File must be an image type accepted by BookStack (png, jpg, gif, webp)
 
 #### Attachment
 
+- `id` - Number, optional, original ID for the attachment from exported system.
 - `name` - String, required, name of attachment.
 - `link` - String, semi-optional, URL of attachment.
 - `file` - String reference, semi-optional, reference to attachment file.