#13746·outline

Markdown ZIP import: attachment records created with name/key resolving to literal "null" and size=0 (files never written)

Author: Taavi95Created Sep 15, 2026Updated Sep 15, 2026
Labelsbugimporters

Is there an existing issue for this?

  • I have searched the existing issues

This is not related to configuring Outline

  • The issue is not related to self-hosting config

Current Behavior

When importing a Markdown ZIP archive (containing a nested folder structure of .md files plus a flat attachments/ folder) via Settings → Import → Markdown, a subset of the created Attachment records end up broken:

  • key resolves to uploads/{userId}/{attachmentId}/null (literal string "null" instead of the real filename)
  • size = 0
  • contentType = application/octet-stream (the mime-type fallback, not the real detected type)

The referencing document's text/content is otherwise completely correct — the page structure, hierarchy, and /api/attachments.redirect?id=... placeholders are all generated correctly, it's specifically the underlying Attachment row and the uploaded file bytes that are missing/corrupt.

The bug is deterministic and reproducible on the same input every time, but only affects a subset of the imported attachments per run (not all of them) — the exact subset seems to vary between otherwise-identical runs of the same ZIP.

Expected Behavior

Every attachment referenced in the imported Markdown should have a corresponding Attachment row with a correct key (matching the real uploaded filename), correct size, and correct contentType, and the underlying file should actually exist in storage — regardless of how many other attachments are being imported in the same batch.

Steps To Reproduce

  1. Prepare a Markdown ZIP with the following structure (matching what [confluence-markdown-exporter](https://github.com/Spenhouet/confluence-markdown-exporter) produces, using export.attachment_href=relative):
    my-space/
      page-a.md
      page-a/
        child-page.md
    attachments/
      123456.jpg
      123457.png
      ...
  2. Reference the attachments from the .md files using relative Markdown image syntax, e.g. ![](../../attachments/123456.jpg).
  3. Import the ZIP via Settings → Import → Markdown (this matters — importing via a Collection's own "Import" button produces a flattened hierarchy and is a separate, likely related issue; only the Settings-level import correctly preserves hierarchy).
  4. Wait for the import to complete ("Import completed" in the worker logs).
  5. Inspect the attachments table:
    sql
    SELECT id, key, size, "contentType"
    FROM attachments
    WHERE size = 0 OR key LIKE '%/null';
    → a non-trivial subset of rows show this broken pattern.

This reproduces reliably even with a minimal test case: a single document referencing just 14 local image/PDF attachments, isolated in its own fresh Collection (no other imports/attachments present), still triggers the bug for 100% of that document's attachments on some runs. This rules out a pure scale/concurrency explanation, though the failure rate across a full space (~1100 attachments, ~10% broken) does suggest something batch/ordering-related rather than a per-file content issue.

Diagnostic notes (may help narrow it down)

  • The import.scratch.manifest (captured live via polling before the import finishes and clears it) shows the correct name for the affected files at bootstrap time, e.g.:

    json
    {
      "id": "8c569749-b386-492f-a79a-ddfa8f33f145",
      "name": "6848573.jpg",
      "mimeType": "image/jpeg",
      "pathInZip": "attachments/6848573.jpg"
    }

    So the ZIP walk / manifest-building step (processBootstrap in MarkdownAPIImportTask) is not at fault.

  • The corruption therefore happens somewhere between manifest creation and the final attachmentCreator() call in onAllTasksCompleted — i.e. in the completion/re-walk phase, or in whatever resolves item.name at that point.

  • We were able to fully work around the bug with a script that:

    1. Re-derives the correct original filename for each broken attachment ID by matching the document's rendered /api/attachments.redirect?id=... placeholders (in order) against the original source Markdown's attachments/<file> references (in order, for the same document, matched by title).
    2. Copies the real file into the computed correct key path on local storage.
    3. Runs UPDATE attachments SET key = ..., size = ..., "contentType" = ....

    This confirms the root files/manifest are correct throughout — repairing purely at the attachments table + storage-file level, with no changes to documents.text, is sufficient to fully restore the images.

Environment

- Outline: 1.10.1 (self-hosted, docker image outlinewiki/outline:latest)
- Deployment: Docker Compose, Postgres 16.15, Redis 7, FILE_STORAGE=local
- Browser: (not relevant — server-side import processing)

Anything else?

Happy to provide the full repair script or more of the scratch/manifest polling data if useful for reproduction. This was found while migrating ~107 pages / ~520 attachments from a Confluence Data Center export (via confluence-markdown-exporter) into Outline Community Edition.

Expected Behavior

No response

Steps To Reproduce

No response

Environment

markdown
- Outline:
- Browser:

Anything else?

No response