Add a PDF bookmark
In a 32-page PDF with Cover on page 1, Methods on 8 and Appendix on 27, a Results bookmark for page 18 lands third, between Methods and Appendix. Enter the page count, current bookmarks, title and page to get the updated outline. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Page count
- 32
- Title
- Results
- Page
- 18
- Quantity
- 4
- Inserted index
- 2
Add one reliable navigation entry to a PDF outline without guessing whether its destination exists or where it belongs. Provide the document page count, its current top-level bookmarks, a title, and a one-based target page. The capability validates the new destination against the actual page range, checks the existing outline, and inserts the bookmark in page order. It returns a fresh structured outline plus the insertion index, making the result straightforward to serialize into a later PDF-writing step or verify in an automated document workflow.
Describe the PDF and the bookmark you want to add
Start with the PDF’s total page count, because that value defines every valid bookmark destination. Then supply the current top-level outline as an array of title and page records in the order used by the document. The page numbers are one-based: page 1 means the first visible PDF page, not an internal zero offset.
Finally, provide the title and target page for the bookmark you want to add. Titles are trimmed at their outer boundaries, so accidental spaces from a form or spreadsheet do not become part of the visible label. The capability works on the outline representation rather than decoding or rewriting binary PDF bytes.
That boundary keeps the operation transparent and makes it suitable after a parser has read a file or before a PDF library writes the updated outline back into a document. An empty outline is valid, which means the same request shape can create the first bookmark or extend a mature table of contents without a separate mode.
Validate page bounds and preserve a predictable order
A bookmark is useful only when its destination can exist. Page zero, negative numbers, fractions, and values beyond the final page return an invalid-input error with the accepted range. Existing bookmarks are checked by the same rule, preventing an apparently successful edit from carrying forward broken destinations.
Their titles must also contain visible text. The existing outline must be ordered by ascending target page; accepting an unordered list would make the phrase “right position” ambiguous and could silently rearrange an author’s intentional navigation. Once validation succeeds, the algorithm scans until it reaches the first bookmark on a later page and inserts there.
If several bookmarks already target the requested page, the new entry follows them, preserving their relative order. The input array and its records are not modified.
Apply the returned outline in a document workflow
The returned data is intentionally library-neutral: a PDF application can map each title and one-based page to the destination objects required by its chosen writer. This capability does not alter PDF bytes, infer headings from page text, create nested children, or repair an unordered source outline. Those tasks require parsing, hierarchy decisions, or file serialization outside this focused operation.
Keeping insertion separate makes failures easier to diagnose: page-range mistakes stop here, before a writer produces a document with dead navigation. For large pipelines, retain the PDF page count from the same parsed version you eventually write so later page additions or removals cannot make the checked destination stale.
What you can do with it
Add a newly written chapter
Insert a chapter label at its verified starting page before a document editor writes the revised PDF.
Automate report navigation
Add a bookmark for a generated appendix or summary while rejecting templates that calculate an impossible destination.
Review outline changes
Show the returned insertion index and updated outline in an approval interface before changing the original file.
FAQ
Does this modify the PDF file itself?
No. It returns an updated outline representation for a PDF writer or document workflow to apply.
Where is a bookmark placed when another bookmark uses the same page?
It is inserted after all existing bookmarks on that page, preserving their supplied order.
Can this add nested bookmarks?
No. This focused operation adds a top-level bookmark to a page-ordered outline.
Can I add the first bookmark to a PDF that has none?
Yes. An empty outline is accepted, so the same step creates the first bookmark or extends a long table of contents, with no separate mode to choose.
What if my existing bookmarks are not in page order?
They are refused. Existing bookmarks must run from the lowest page to the highest; accepting a shuffled list would make the right position ambiguous and could quietly rearrange the author's navigation.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/pdf/bookmarks-add \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"page_count":24,"outline":[{"title":"Introduction","page":1},{"title":"Results","page":12}],"title":"Methods","page":7}'const res = await fetch("https://api.kit.forhosting.com/pdf/bookmarks-add", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"page_count": 24,
"outline": [
{
"title": "Introduction",
"page": 1
},
{
"title": "Results",
"page": 12
}
],
"title": "Methods",
"page": 7
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/pdf/bookmarks-add",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"page_count": 24,
"outline": [
{
"title": "Introduction",
"page": 1
},
{
"title": "Results",
"page": 12
}
],
"title": "Methods",
"page": 7
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/pdf/bookmarks-add", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"page_count":24,"outline":[{"title":"Introduction","page":1},{"title":"Results","page":12}],"title":"Methods","page":7}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"page_count":24,"outline":[{"title":"Introduction","page":1},{"title":"Results","page":12}],"title":"Methods","page":7}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/pdf/bookmarks-add", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"page_count": 24,
"outline": [
{
"title": "Introduction",
"page": 1
},
{
"title": "Results",
"page": 12
}
],
"title": "Methods",
"page": 7
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "pdf.bookmarks_add",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_mb | 25 |
max_pages | 200 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Validate page bounds and preserve a predictable order
The requested target must therefore be an integer from 1 through page_count, inclusive. With no network calls, timestamps, random identifiers, or locale-sensitive comparisons, identical JSON input always produces identical JSON output.
Apply the returned outline in a document workflow
The response includes page_count, the resulting bookmark count, a zero-based inserted_index, and the complete updated outline. Use inserted_index when a review interface should highlight the newly created row, or ignore it and pass the outline directly to the next stage. A browser tool can help an editor check a destination quickly before publication, while an API call at $0.002 fits repeatable workflows such as adding a legal notice, appendix, release-notes section, or generated report chapter.
Technical questions
How are pages numbered?
Pages are one-based, so 1 is the first page and page_count is the last valid destination.
What happens when the target page is out of range?
The request fails with invalid_input and a message stating the valid page range.
What does an API request cost?
A successful API request costs $0.002; the same deterministic operation is free in your browser.