# Merging PDFs breaks page links

**URL:** <https://discourse.devontechnologies.com/t/merging-pdfs-breaks-page-links/87930>\
**Category:** DEVONthink\
**Tags:** bug-report\
**Created:** [October 29, 2026, 12:40pm UTC](https://discourse.devontechnologies.com/t/merging-pdfs-breaks-page-links/87930 "2026-10-29T12:40:43Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![rkaplan](https://discourse.devontechnologies.com/letter_avatar_proxy/v4/letter/r/a3d4f5/32.png) [@rkaplan](https://discourse.devontechnologies.com/u/rkaplan)\
**Post date:** [October 29, 2026, 12:40pm UTC](https://discourse.devontechnologies.com/t/merging-pdfs-breaks-page-links/87930/1 "2026-10-29T12:40:43Z")

</div>

If I have a PDF with internal page links and then I merge it with another PDF, the page links no longer work.

Is it possible for the page links to be retained as working?

---

<div class="post-metadata">

**Author:** ![chrillek](https://discourse.devontechnologies.com/user_avatar/discourse.devontechnologies.com/chrillek/32/29340_2.png) [@chrillek](https://discourse.devontechnologies.com/u/chrillek)\
**Post date:** [October 29, 2026, 2:24pm UTC](https://discourse.devontechnologies.com/t/merging-pdfs-breaks-page-links/87930/2 "2026-10-29T14:24:14Z")

</div>

> [@rkaplan](#):
>
> Is it possible for the page links to be retained as working?

Possible – I believe so. If you need it right now, a script could possibly do that:  
Assuming that you append PDF B to PDF A:

- get number of pages in A `noPages`
- for each link in B, add `noPages` to its page number
- append PDF B to PDF A

Just brainstorming here, I didn’ test that. And the details are a bit tedious: you have to process B page by page, get the link annotations for each page and adjust them. But it should be feasible.

---

<div class="post-metadata">

**Author:** ![rkaplan](https://discourse.devontechnologies.com/letter_avatar_proxy/v4/letter/r/a3d4f5/32.png) [@rkaplan](https://discourse.devontechnologies.com/u/rkaplan)\
**Post date:** [October 29, 2026, 3:48pm UTC](https://discourse.devontechnologies.com/t/merging-pdfs-breaks-page-links/87930/3 "2026-10-29T15:48:47Z")

</div>

I guess I should be more clear

Yes I know it is feasible if I manually edit the PDF.

My question is - if I combine them using the DT “Merge” feature, could DT automatically keep the page links intact as a feature.

---

<div class="post-metadata">

**Author:** ![chrillek](https://discourse.devontechnologies.com/user_avatar/discourse.devontechnologies.com/chrillek/32/29340_2.png) [@chrillek](https://discourse.devontechnologies.com/u/chrillek)\
**Post date:** [October 29, 2026, 4:33pm UTC](https://discourse.devontechnologies.com/t/merging-pdfs-breaks-page-links/87930/4 "2026-10-29T16:33:02Z")

</div>

I suppose that’s currently not possible. The reason why I suggested the scripting approach

---

<div class="post-metadata">

**Author:** ![rkaplan](https://discourse.devontechnologies.com/letter_avatar_proxy/v4/letter/r/a3d4f5/32.png) [@rkaplan](https://discourse.devontechnologies.com/u/rkaplan)\
**Post date:** [October 29, 2026, 4:56pm UTC](https://discourse.devontechnologies.com/t/merging-pdfs-breaks-page-links/87930/5 "2026-10-29T16:56:07Z")

</div>

Yours is an efficient approach.

Claude suggested this - which is more complex including a Python library/dependency called pikepdf. Interestingly Claude’s approach resulted in a 1MB combined PDF whereas DT gave a 3MB result. According to Claude that is because the macOS PDFKit repackages fonts page by page.

[The script might work generically but has some specific features for my use case so may need to be tweaked for others to use it.]

```auto
-- Combine PDFs (keep links)

-- DEVONthink contextual-menu script (installed in

-- ~/Library/Application Scripts/com.devon-technologies.think/Contextual Menu/; also works from

-- the Scripts menu folder ".../Menu/"). Select the main LCP report PDF (Word "Save as PDF"),

-- the Life Expectancy graphs PDF and the LCP spreadsheet PDF, right-click them and choose

-- Combine PDFs (keep links).

--

-- Order: main report first (name contains "LCP" and not "Graph"/"Spreadsheet"/"Sheet"),

-- then the Life Expectancy graphs, then the spreadsheet, then anything else in the

-- order selected. A prompt appears only if the main report cannot be told apart.

--

-- The PDFs are COPIED out of the database (read-only on the package) to

-- ~/Library/Logs/Combine-PDFs-Keep-Links/<timestamp>/, merged there by the Python helper

-- (pikepdf, link-preserving, TOC repair, bookmarks, link check log), and the result is

-- imported into the main report's group as "<main report name> (Combined)", label kLabel,

-- no tags. Originals are untouched. A previous "(Combined)" record of the same name is

-- MOVED to the group's "Temp AI Files" (never trashed). Never opens Word or Preview.

--

-- Also callable headlessly: (load script ...)'s combineUUIDs({"UUID1","UUID2","UUID3"}).

property kLabel : 3 -- 3 = AI work (default); set to 0 for no label

property kHelperDir : "Library/Application Support/Combine-PDFs-Keep-Links/"

property kWorkRoot : "Library/Logs/Combine-PDFs-Keep-Links/"

property kTitle : "Combine PDFs (keep links)"

property gInteractive : false

on run

	set gInteractive to true

	tell application id "DNtp"

		try

			set sel to selected records

		on error

			set sel to selection

		end try

	end tell

	return my combineRecords(sel)

end run

on combineUUIDs(uuidList)

	set gInteractive to false

	set recs to {}

	tell application id "DNtp"

		repeat with u in uuidList

			set end of recs to (get record with uuid (u as string))

		end repeat

	end tell

	return my combineRecords(recs)

end combineUUIDs

on combineRecords(recs)

	set mains to {}

	set les to {}

	set sheets to {}

	set others to {}

	set pdfs to {}

	tell application id "DNtp"

		repeat with i from 1 to count of recs

			set r to item i of recs

			if (type of r) is PDF document then set end of pdfs to r

		end repeat

		if (count of pdfs) < 2 then return my fail("Select the main report PDF plus the PDFs to append (Life Expectancy graphs, LCP spreadsheet).")

		repeat with i from 1 to count of pdfs

			set r to item i of pdfs

			set n to name of r

			if n contains "LCP" and not (n contains "Graph" or n contains "Spreadsheet" or n contains "Sheet") then

				set end of mains to r

			else if n contains "Spreadsheet" or n contains "Sheet" or n contains "Future Cost" then

				set end of sheets to r

			else if n contains "Graph" or n contains "Life Expectancy" then

				set end of les to r

			else

				set end of others to r

			end if

		end repeat

	end tell

	-- pick the main report (prompt only when ambiguous)

	set mainRec to missing value

	if (count of mains) = 1 then

		set mainRec to item 1 of mains

	else if (count of mains) = 0 and (count of others) = 1 then

		set mainRec to item 1 of others

		set others to {}

	else

		if (count of mains) > 1 then

			set candidates to mains

		else

			set candidates to pdfs

		end if

		if not gInteractive then return my fail("Cannot tell which PDF is the main report; run it from DEVONthink's menu to choose.")

		set names to {}

		tell application id "DNtp"

			repeat with i from 1 to count of candidates

				set end of names to name of (item i of candidates)

			end repeat

		end tell

		tell application id "DNtp" to set pick to choose from list names with prompt "Which PDF is the main report (goes first)?" with title kTitle

		if pick is false then return "Cancelled"

		set pick to item 1 of pick

		set newMains to {}

		set newLes to {}

		set newSheets to {}

		set newOthers to {}

		tell application id "DNtp"

			repeat with i from 1 to count of pdfs

				set r to item i of pdfs

				if mainRec is missing value and (name of r) is pick then

					set mainRec to r

				else if les contains {r} then

					set end of newLes to r

				else if sheets contains {r} then

					set end of newSheets to r

				else

					set end of newOthers to r

				end if

			end repeat

		end tell

		set les to newLes

		set sheets to newSheets

		set others to newOthers

	end if

	-- work folder + read-only copies

	set stamp to do shell script "date +%Y%m%d-%H%M%S"

	set workDir to (POSIX path of (path to home folder)) & kWorkRoot & stamp & "/"

	set helper to (POSIX path of (path to home folder)) & kHelperDir & "run_combine.sh"

	do shell script "mkdir -p " & quoted form of workDir

	tell application id "DNtp"

		set mainName to name of mainRec

		set mainPath to path of mainRec

		try

			set theGroup to location group of mainRec

		on error

			set theGroup to parent 1 of mainRec

		end try

	end tell

	set newName to mainName & " (Combined)"

	set mainCopy to my copyIn(mainPath, workDir, "0 main")

	set args to " --main " & quoted form of mainCopy & " --title " & quoted form of mainName

	set partNo to 1

	repeat with spec in {{"le", les}, {"sheet", sheets}, {"other", others}}

		set k to item 1 of spec

		set lst to item 2 of spec

		repeat with j from 1 to count of lst

			set r to item j of lst

			tell application id "DNtp"

				set p to path of r

				set n to name of r

			end tell

			set c to my copyIn(p, workDir, (partNo as string) & " " & k)

			set args to args & " --part " & k & " " & quoted form of c

			if k is "other" then set args to args & " --label-other " & quoted form of n

			set partNo to partNo + 1

		end repeat

	end repeat

	set outPath to workDir & my safeName(newName) & ".pdf"

	set res to do shell script quoted form of helper & " --out " & quoted form of outPath & args & " 2>&1; echo EXIT=$?"

	set status to ""

	set summary to ""

	set logPath to ""

	repeat with ln in paragraphs of res

		set ln to ln as string

		if ln starts with "STATUS=" then set status to text 8 thru -1 of ln

		if ln starts with "SUMMARY=" then set summary to text 9 thru -1 of ln

		if ln starts with "MESSAGE=" then set summary to text 9 thru -1 of ln

		if ln starts with "LOG=" then set logPath to text 5 thru -1 of ln

	end repeat

	if status is not "OK" then return my fail("Link check failed - nothing imported. " & summary & " (log: " & workDir & ")")

	-- import next to the main report

	tell application id "DNtp"

		set olds to (children of theGroup whose name is newName)

		if (count of olds) > 0 then

			set tg to missing value

			set kids to children of theGroup

			repeat with j from 1 to count of kids

				set c to item j of kids

				if (name of c) is "Temp AI Files" and (type of c) is group then set tg to c

			end repeat

			if tg is missing value then set tg to create record with {name:"Temp AI Files", type:group} in theGroup

			repeat with j from 1 to count of olds

				move record (item j of olds) to tg

			end repeat

		end if

		set newRec to import path outPath to theGroup name newName

		set label of newRec to kLabel

		set comment of newRec to kTitle & ": " & summary

		set newURL to reference URL of newRec

	end tell

	display notification summary with title kTitle subtitle newName

	return "OK" & linefeed & newName & linefeed & newURL & linefeed & summary & linefeed & logPath

end combineRecords

on copyIn(srcPath, workDir, prefix)

	set fname to do shell script "basename " & quoted form of srcPath

	set dst to workDir & prefix & " - " & fname

	do shell script "cp " & quoted form of srcPath & " " & quoted form of dst

	return dst

end copyIn

on safeName(s)

	return do shell script "printf %s " & quoted form of s & " | tr '/:' '--'"

end safeName

on fail(msg)

	display notification msg with title kTitle

	if gInteractive then

		tell application id "DNtp" to display alert kTitle message msg

	end if

	return "ERROR: " & msg

end fail

```

---

<div class="post-metadata">

**Author:** ![chrillek](https://discourse.devontechnologies.com/user_avatar/discourse.devontechnologies.com/chrillek/32/29340_2.png) [@chrillek](https://discourse.devontechnologies.com/u/chrillek)\
**Post date:** [October 31, 2026, 10:51am UTC](https://discourse.devontechnologies.com/t/merging-pdfs-breaks-page-links/87930/6 "2026-10-31T10:51:08Z")

</div>

I looked into this trying to write a better script. But I noticed that it’s more complicated than I thought, for a more general case. Let’s assume three PDFs, A, B and C.  
B and C shall be merged to A by appending them.  
B contains one page link to A and one to C  
A contains one page link to C  
C contains one page link to A and one to another PDF (D) in DT, that is not merged.

To adjust the page links in B:

- one must check _which_ PDF the link points to. For the one pointing to A, nothing changes. For the one pointing to C, however, one must adjust the page number by adding the page counts of A and B to it

To adjust the page link in A:

- one must check which PDF the link points to and then add the page counts of A and B to it

To adjust the page links in C

- one must again check which PDF the link points to and then do nothing for PDF A nor PDF D

**All this** for just 3 PDFs and a fairly simple situation. I’m not saying that it is not feasible. But I’d not volunteer to write the code for that. It would have to build a data structure first that registered the page count for each PDF in the order the documents are specified (which is, BTW, not something it can deduce from `selectedRecords()` or `selection()`). For every PDF in that structure, it would than have to check for every page link in it if it points to the very first one (nothing to do) or to one not in the list (nothing to do). In any other case, it has to add the page counts for all _previous_ PDFs in the list to the page link’s page number and rewrite that annotation.

Now Claude knows what to do, I guess. But why stop at merging – shouldn’t the page links be fixed also when _splitting_ the PDFs?

---

<div class="post-metadata">

**Author:** ![rkaplan](https://discourse.devontechnologies.com/letter_avatar_proxy/v4/letter/r/a3d4f5/32.png) [@rkaplan](https://discourse.devontechnologies.com/u/rkaplan)\
**Post date:** [October 31, 2026, 11:35am UTC](https://discourse.devontechnologies.com/t/merging-pdfs-breaks-page-links/87930/7 "2026-10-31T11:35:25Z")

</div>

Those are useful points.

Perhaps this is best left as a personal script rather than a general feature. My use case is consistent but you may be right that it would be too complex to create it as a generic feature applicable to all situations,

---

<div class="post-metadata">

**Author:** ![cgrunenberg](https://discourse.devontechnologies.com/user_avatar/discourse.devontechnologies.com/cgrunenberg/32/7172_2.png) [@cgrunenberg](https://discourse.devontechnologies.com/u/cgrunenberg)\
**Post date:** [November 2, 2026, 2:35pm UTC](https://discourse.devontechnologies.com/t/merging-pdfs-breaks-page-links/87930/8 "2026-11-02T14:35:56Z")

</div>

The next release will support this.

---

<div class="post-metadata">

**Author:** ![rkaplan](https://discourse.devontechnologies.com/letter_avatar_proxy/v4/letter/r/a3d4f5/32.png) [@rkaplan](https://discourse.devontechnologies.com/u/rkaplan)\
**Post date:** [November 2, 2026, 3:31pm UTC](https://discourse.devontechnologies.com/t/merging-pdfs-breaks-page-links/87930/9 "2026-11-02T15:31:39Z")

</div>

Thanks
