Manage Presentation Hyperlinks in Python via Java
Introduction
A hyperlink connects presentation content to a website or a location within the presentation. In PowerPoint, hyperlinks commonly serve two purposes:
- Open a website from text, a shape, or a media frame.
- Navigate to another slide, for example, from a table of contents.
Aspose.Slides for Python via Java lets you add these links, control their appearance and sound, update their properties, and remove them. The examples below show how to work with hyperlinks on individual elements and how to access hyperlinks at the presentation, slide, or text-frame level.
Note
You can also edit presentations with the free online Aspose PowerPoint editor.Add URL Hyperlinks
You can assign a website URL to text, a shape, or a media frame. The element to which you assign the hyperlink determines the clickable area: a text portion links the selected text, while a shape or frame links the slide object.
Add URL Hyperlinks to Text
To link text to a website, pass a Hyperlink to the text portion’s setHyperlinkClick method, as shown below. Only that portion of text becomes clickable.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Hyperlink, Presentation, SaveFormat, ShapeType
presentation = Presentation()
try:
shape = presentation.getSlides().get_Item(0).getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 600, 50, False)
shape.addTextFrame("Aspose: File Format APIs")
portion_format = shape.getTextFrame().getParagraphs().get_Item(0).getPortions().get_Item(0).getPortionFormat()
portion_format.setHyperlinkClick(Hyperlink("https://www.aspose.com/"))
portion_format.getHyperlinkClick().setTooltip("Explore Aspose file format APIs")
portion_format.setFontHeight(32)
presentation.save("presentation-out.pptx", SaveFormat.Pptx)
finally:
presentation.dispose()
Add URL Hyperlinks to Shapes and Media Frames
To make a shape or frame clickable, call its setHyperlinkClick method. The hyperlink belongs to the object itself rather than to a text portion inside it.
The same approach applies to picture, audio, and video frames: assign the hyperlink to the frame and call setTooltip if needed.
The following example makes a rectangle clickable:
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Hyperlink, Presentation, SaveFormat, ShapeType
presentation = Presentation()
try:
shape = presentation.getSlides().get_Item(0).getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 600, 50)
shape.setHyperlinkClick(Hyperlink("https://www.aspose.com/"))
shape.getHyperlinkClick().setTooltip("Explore Aspose file format APIs")
presentation.save("presentation-out.pptx", SaveFormat.Pptx)
finally:
presentation.dispose()
Use Hyperlinks to Create a Table of Contents
Internal hyperlinks let readers jump from a table of contents to a specific slide. The following example uses setInternalHyperlinkClick to link the “Page 2” text on the first slide to the second slide.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import FillType, Paragraph, Portion, Presentation, SaveFormat, ShapeType
Color = jpype.JClass("java.awt.Color")
presentation = Presentation()
try:
first_slide = presentation.getSlides().get_Item(0)
second_slide = presentation.getSlides().addEmptySlide(first_slide.getLayoutSlide())
table_of_contents = first_slide.getShapes().addAutoShape(ShapeType.Rectangle, 40, 40, 300, 100)
table_of_contents.getFillFormat().setFillType(FillType.NoFill)
table_of_contents.getLineFormat().getFillFormat().setFillType(FillType.NoFill)
table_of_contents.getTextFrame().getParagraphs().clear()
paragraph = Paragraph()
paragraph.getParagraphFormat().getDefaultPortionFormat().getFillFormat().setFillType(FillType.Solid)
paragraph.getParagraphFormat().getDefaultPortionFormat().getFillFormat().getSolidFillColor().setColor(Color.BLACK)
paragraph.setText("Title of slide 2 .......... ")
link_portion = Portion()
link_portion.setText("Page 2")
link_portion.getPortionFormat().getHyperlinkManager().setInternalHyperlinkClick(second_slide)
paragraph.getPortions().add(link_portion)
table_of_contents.getTextFrame().getParagraphs().add(paragraph)
presentation.save("link_to_slide.pptx", SaveFormat.Pptx)
finally:
presentation.dispose()
Format Hyperlinks
Color
The setColorSource method of Hyperlink determines whether a hyperlink uses the presentation’s hyperlink color or the text portion’s formatting. To apply a custom text color, select HyperlinkColorSource.PortionFormat and set the portion’s fill color. This feature was introduced in PowerPoint 2019; older versions do not apply this setting.
The following example adds two text hyperlinks to the same slide. The first uses a red text fill, while the second retains the default hyperlink color.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import FillType, Hyperlink, HyperlinkColorSource, Presentation, SaveFormat, ShapeType
Color = jpype.JClass("java.awt.Color")
presentation = Presentation()
try:
colored_link_shape = presentation.getSlides().get_Item(0).getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 450, 50, False)
colored_link_shape.addTextFrame("This hyperlink uses a custom color.")
portion_format = colored_link_shape.getTextFrame().getParagraphs().get_Item(0).getPortions().get_Item(0).getPortionFormat()
portion_format.setHyperlinkClick(Hyperlink("https://www.aspose.com/"))
portion_format.getHyperlinkClick().setColorSource(HyperlinkColorSource.PortionFormat)
portion_format.getFillFormat().setFillType(FillType.Solid)
portion_format.getFillFormat().getSolidFillColor().setColor(Color.RED)
default_link_shape = presentation.getSlides().get_Item(0).getShapes().addAutoShape(ShapeType.Rectangle, 100, 200, 450, 50, False)
default_link_shape.addTextFrame("This hyperlink uses the default color.")
default_link_shape.getTextFrame().getParagraphs().get_Item(0).getPortions().get_Item(0).getPortionFormat().setHyperlinkClick(Hyperlink("https://www.aspose.com/"))
presentation.save("presentation-out-hyperlink.pptx", SaveFormat.Pptx)
finally:
presentation.dispose()
Sound
A hyperlink can play a sound when activated or stop a sound that is already playing. Use the following methods to configure these behaviors:
- Hyperlink.setSound specifies the audio associated with the hyperlink.
- Hyperlink.setStopSoundOnClick controls whether activating the hyperlink stops the previous sound.
Add a Hyperlink Sound
The following example loads sampleaudio.wav and associates it with a button on the first slide. Clicking the button plays the sound and navigates to the next slide. A second shape on that slide stops the previous sound when clicked, without performing a navigation action.
from pathlib import Path
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Hyperlink, Presentation, SaveFormat, ShapeType
presentation = Presentation()
try:
audio_data = Path("sampleaudio.wav").read_bytes()
java_audio_data = jpype.JArray(jpype.JByte)(audio_data)
hyperlink_sound = presentation.getAudios().addAudio(java_audio_data)
first_slide = presentation.getSlides().get_Item(0)
play_button = first_slide.getShapes().addAutoShape(ShapeType.SoundButton, 100, 100, 100, 50)
play_button.setHyperlinkClick(Hyperlink.getNextSlide())
if not play_button.getHyperlinkClick().getStopSoundOnClick() and play_button.getHyperlinkClick().getSound() is None:
play_button.getHyperlinkClick().setSound(hyperlink_sound)
second_slide = presentation.getSlides().addEmptySlide(first_slide.getLayoutSlide())
stop_button = second_slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 100, 50)
stop_button.setHyperlinkClick(Hyperlink.getNoAction())
stop_button.getHyperlinkClick().setStopSoundOnClick(True)
presentation.save("hyperlink-sound.pptx", SaveFormat.Pptx)
except OSError as exception:
print(f"Unable to read the audio file: {exception}")
finally:
presentation.dispose()
Extract a Hyperlink Sound
The following example opens the presentation created above and reads the first shape’s hyperlink audio into memory through getSound and getBinaryData.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Presentation
presentation = Presentation("hyperlink-sound.pptx")
try:
if presentation.getSlides().size() > 0 and presentation.getSlides().get_Item(0).getShapes().size() > 0:
hyperlink = presentation.getSlides().get_Item(0).getShapes().get_Item(0).getHyperlinkClick()
sound = hyperlink.getSound() if hyperlink is not None else None
if sound is not None:
audio_data = bytes(sound.getBinaryData())
print(f"Extracted {len(audio_data)} bytes of hyperlink audio.")
else:
print("The first shape has no hyperlink sound.")
else:
print("The presentation has no first slide or shape to inspect.")
finally:
presentation.dispose()
Tooltip and Interaction Settings
You can call the following Hyperlink methods after assigning a hyperlink to text or a shape:
- setTooltip sets the text that a viewer can display as a hint for the link.
- setTargetFrame specifies the target frame within a parent HTML frameset, when applicable.
- setHistory controls whether activating the link adds its destination to the list of viewed hyperlinks.
- setHighlightClick controls whether the hyperlink is highlighted when clicked.
Remove Hyperlinks from Presentations
Use getAnyHyperlinks to collect hyperlink containers, including text-portion links, before changing them. The following example removes both activation types from the first slide. To remove only one type, call only removeHyperlinkClick or removeHyperlinkMouseOver; removing a click action does not remove its mouse-over counterpart.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Presentation, SaveFormat
presentation = Presentation("pres.pptx")
try:
if presentation.getSlides().size() > 0:
containers = list(presentation.getSlides().get_Item(0).getHyperlinkQueries().getAnyHyperlinks())
for container in containers:
container.getHyperlinkManager().removeHyperlinkClick()
container.getHyperlinkManager().removeHyperlinkMouseOver()
presentation.save("pres-removed-hyperlinks.pptx", SaveFormat.Pptx)
else:
print("The presentation has no slides to process.")
finally:
presentation.dispose()
For unconditional removal, removeAllHyperlinks removes both activation types in the selected scope in one call. For selective cleanup and coverage of masters, layouts, and notes, see Report, Sanitize, and Verify Hyperlinks.
Build a Complete Hyperlink Inventory
Before distributing a presentation, inventory its interactive actions as well as its web links. getAnyHyperlinks returns hyperlink containers, such as Shape and PortionFormat objects, not a flat list of URL strings. Inspect both getHyperlinkClick and getHyperlinkMouseOver on each container. They are independent: the same container can expose both actions, so a complete report needs up to two rows per container.
Scanning only shape-level hyperlinks can miss links attached to text portions. Query the appropriate scope instead, and retain the returned containers so that you can later update or remove their actions.
Query Presentation, Slide, and Text-Frame Scopes
The HyperlinkQueries class is available through Presentation.getHyperlinkQueries, BaseSlide.getHyperlinkQueries, and TextFrame.getHyperlinkQueries. Each scope supports the same queries:
- getHyperlinkClicks returns containers with a click action.
- getHyperlinkMouseOvers returns containers with a mouse-over action.
- getAnyHyperlinks returns containers with either or both actions.
The following example creates hyperlink-audit-input.pptx with an external click link, a file mouse-over link, internal slide navigation, a text mouse-over link, and a macro action. It does not execute any of these actions. The same three queries work at every scope; the counts describe containers, not action totals. The text-frame scope excludes the enclosing shape’s own links.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Presentation, SaveFormat, ShapeType
def print_counts(scope, queries):
click_count = queries.getHyperlinkClicks().size()
mouse_over_count = queries.getHyperlinkMouseOvers().size()
any_count = queries.getAnyHyperlinks().size()
print(f"{scope}: click={click_count}, mouse-over={mouse_over_count}, any={any_count}")
presentation = Presentation()
try:
slide = presentation.getSlides().get_Item(0)
destination = presentation.getSlides().addEmptySlide(slide.getLayoutSlide())
shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 20, 20, 400, 60)
shape.getTextFrame().setText("Click the text to go to slide 2")
shape.getHyperlinkManager().setExternalHyperlinkClick("https://example.com/")
shape.getHyperlinkClick().setTooltip("Public website")
shape.getHyperlinkManager().setExternalHyperlinkMouseOver("file:///C:/private/report.xlsx")
portion_format = shape.getTextFrame().getParagraphs().get_Item(0).getPortions().get_Item(0).getPortionFormat()
portion_format.getHyperlinkManager().setInternalHyperlinkClick(destination)
portion_format.getHyperlinkManager().setExternalHyperlinkMouseOver("https://example.com/help")
macro_button = slide.getShapes().addAutoShape(ShapeType.Rectangle, 20, 120, 200, 60)
macro_button.getHyperlinkManager().setMacroHyperlinkClick("ReviewPresentation")
print_counts("Presentation", presentation.getHyperlinkQueries())
print_counts("Slide 1", slide.getHyperlinkQueries())
print_counts("Text frame", shape.getTextFrame().getHyperlinkQueries())
presentation.save("hyperlink-audit-input.pptx", SaveFormat.Pptx)
finally:
presentation.dispose()
For this example, presentation and slide queries each report three click containers, two mouse-over containers, and three containers with either action. The text-frame query reports one container in each category.
Classify Actions and Destinations
Use Hyperlink.getActionType to interpret an action before interpreting its destination. The HyperlinkActionType values cover more than web navigation:
| Values | Meaning for an audit |
|---|---|
Hyperlink |
External hyperlink; inspect the URL and its scheme. |
JumpSpecificSlide |
Internal navigation to a particular slide. |
JumpFirstSlide, JumpPreviousSlide, JumpNextSlide, JumpLastSlide, JumpLastViewedSlide |
Built-in slideshow navigation, resolved in slideshow context. |
JumpEndShow, StartCustomSlideShow |
End the current show or start a custom show. |
StartMacro |
Execute a macro. |
StartProgram |
Launch a program. |
OpenFile, OpenPresentation |
Open a file or another presentation; review separately from web URLs. |
StartStopMedia |
Start or stop media playback. |
NoAction, Unknown |
No navigation action, or an unrecognized action requiring review. |
Read external destinations from getExternalUrl and specific internal destinations from getTargetSlide. Internal actions and built-in commands may have no external URL; an empty URL does not mean that the container has no action. Preserve the value returned by getExternalUrlOriginal when it differs from the normalized URL, and include the tooltip returned by getTooltip when available.
Report, Sanitize, and Verify Hyperlinks
The following Python example reads an existing presentation (use the file created above), writes hyperlink-audit.json, applies a policy, saves hyperlink-sanitized.pptx, and reopens it to check both activation types again. It collects containers before changing them and uses reference equality to avoid processing the same container twice. Presentation queries cover ordinary slides; for a package-wide inventory, it also explicitly queries masters, layouts, notes, and the notes and handout masters when present.
The report records a one-based slide index and getSlideId where available. getSlide supplies the owning slide for supported containers. Masters, layouts, and notes have no ordinary slide index and are identified by their scope. Shape containers and text-portion formatting containers are labeled separately; other container types retain their runtime type name. Each container gets a report-local ID so its two actions can be correlated. The report stores action types as the integer constants defined by the Java enumeration.
This deliberately restrictive application policy allows only absolute HTTPS URLs and valid internal slide targets. It rejects macros, programs, file actions, other slideshow actions, unknown actions, and other URL schemes. These rejections are policy decisions, not an Aspose.Slides safety verdict. HTTPS alone does not establish trust: add host allowlists and other checks for your application. Both original and normalized external URLs are checked. The example audits metadata without following links or running actions.
For remediation, the container’s getHyperlinkManager supports setExternalHyperlinkClick, removeHyperlinkClick, and removeHyperlinkMouseOver. Here, prohibited external click links are replaced with a fixed HTTPS landing page; other prohibited clicks and prohibited mouse-over actions are removed independently. Set replace_external_clicks to False to remove all policy violations instead. Choose an application-owned replacement page before deployment.
The report’s export flag uses a conservative PDF review policy: flag mouse-over actions and anything other than an external link or specific slide jump as potentially unsupported. It is a review hint, not a capability test or a guarantee that unflagged links will survive export. Supported PDF and HTML exports may preserve hyperlinks, depending on the action, export options, and viewer. Raster images and video cannot preserve interactive hyperlinks; flag every action when auditing for those outputs.
import json
from pathlib import Path
from urllib.parse import urlsplit
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import HyperlinkActionType, PortionFormat, Presentation, SaveFormat, Shape
IdentityHashMap = jpype.JClass("java.util.IdentityHashMap")
def slide_index(presentation, slide):
for index, candidate in enumerate(presentation.getSlides(), start=1):
if candidate == slide:
return index
return None
def is_https(value):
if not value:
return False
value = str(value)
if any(character.isspace() or ord(character) < 32 for character in value):
return False
try:
uri = urlsplit(value)
return uri.scheme.lower() == "https" and bool(uri.hostname)
except ValueError:
return False
def policy_violation(link):
if link is None:
return None
if link.getActionType() == HyperlinkActionType.JumpSpecificSlide:
return "Missing target slide" if link.getTargetSlide() is None else None
if link.getActionType() != HyperlinkActionType.Hyperlink:
return "Action is not allowed"
if not is_https(link.getExternalUrl()):
return "Normalized URL is not absolute HTTPS"
original = link.getExternalUrlOriginal()
if original and not is_https(original):
return "Original URL is not absolute HTTPS"
return None
def collect_containers(presentation):
found = list(presentation.getHyperlinkQueries().getAnyHyperlinks())
scopes = list(presentation.getMasters()) + list(presentation.getLayoutSlides())
for slide in presentation.getSlides():
scopes.append(slide.getNotesSlideManager().getNotesSlide())
scopes.append(presentation.getMasterNotesSlideManager().getMasterNotesSlide())
scopes.append(presentation.getMasterHandoutSlideManager().getMasterHandoutSlide())
for scope in scopes:
if scope is not None:
found.extend(scope.getHyperlinkQueries().getAnyHyperlinks())
seen = IdentityHashMap()
unique = []
for container in found:
if not seen.containsKey(container):
seen.put(container, True)
unique.append(container)
return unique
def text_or_none(value):
return str(value) if value is not None else None
def add_row(rows, presentation, link, activation, container, container_id):
if link is None:
return
owner_slide = container.getSlide() if hasattr(container, "getSlide") else None
target_slide = link.getTargetSlide()
violation = policy_violation(link)
if isinstance(container, Shape):
owner_type = "Shape"
elif isinstance(container, PortionFormat):
owner_type = "Text portion"
else:
owner_type = str(container.getClass().getSimpleName())
ordinary_action = link.getActionType() in (HyperlinkActionType.Hyperlink, HyperlinkActionType.JumpSpecificSlide)
original = link.getExternalUrlOriginal()
rows.append({
"ContainerId": container_id,
"SlideIndex": slide_index(presentation, owner_slide),
"SlideId": int(owner_slide.getSlideId()) if owner_slide is not None else None,
"Scope": str(owner_slide.getClass().getSimpleName()) if owner_slide is not None else None,
"OwnerType": owner_type,
"Activation": activation,
"ActionType": int(link.getActionType()),
"ExternalUrl": text_or_none(link.getExternalUrl()),
"TargetSlideIndex": slide_index(presentation, target_slide),
"TargetSlideId": int(target_slide.getSlideId()) if target_slide is not None else None,
"Tooltip": text_or_none(link.getTooltip()),
"OriginalExternalUrl": text_or_none(original) if original != link.getExternalUrl() else None,
"PotentiallyUnsafe": violation is not None,
"PolicyViolation": violation,
"TargetExport": "PDF",
"PotentiallyUnsupportedByExport": activation == "mouse-over" or not ordinary_action,
})
replace_external_clicks = True
replacement_url = "https://example.com/blocked-link"
presentation = Presentation("hyperlink-audit-input.pptx")
try:
containers = collect_containers(presentation)
rows = []
for container_id, container in enumerate(containers, start=1):
add_row(rows, presentation, container.getHyperlinkClick(), "click", container, container_id)
add_row(rows, presentation, container.getHyperlinkMouseOver(), "mouse-over", container, container_id)
report = json.dumps(rows, indent=2)
Path("hyperlink-audit.json").write_text(report, encoding="utf-8")
for container in containers:
click = container.getHyperlinkClick()
if policy_violation(click) is not None:
if replace_external_clicks and click.getActionType() == HyperlinkActionType.Hyperlink:
container.getHyperlinkManager().setExternalHyperlinkClick(replacement_url)
else:
container.getHyperlinkManager().removeHyperlinkClick()
if policy_violation(container.getHyperlinkMouseOver()) is not None:
container.getHyperlinkManager().removeHyperlinkMouseOver()
presentation.save("hyperlink-sanitized.pptx", SaveFormat.Pptx)
reopened = Presentation("hyperlink-sanitized.pptx")
try:
remaining_containers = collect_containers(reopened)
violations = 0
for container in remaining_containers:
if policy_violation(container.getHyperlinkClick()) is not None:
violations += 1
if policy_violation(container.getHyperlinkMouseOver()) is not None:
violations += 1
print(f"Audit rows: {len(rows)}; prohibited actions after reopening: {violations}")
if violations != 0:
print("Verification failed: do not distribute the saved presentation.")
finally:
reopened.dispose()
except OSError as exception:
print(f"Unable to write the audit report: {exception}")
finally:
presentation.dispose()
With the input created above, the report contains five action rows. The file mouse-over link and macro click are removed, while the HTTPS links and internal slide navigation remain. The verification prints zero prohibited actions. An input containing a prohibited external click URL also exercises the replacement branch. A container with an allowed click and a prohibited mouse-over keeps its click action.
This selective cleanup differs from removeAllHyperlinks, which removes both activation types throughout the selected scope regardless of policy. Verification here checks hyperlink actions only; it does not remove embedded VBA projects, OLE objects, or other active content, and it does not validate an exported PDF or HTML file.
FAQ
How can I link to a section or its first slide?
Sections in PowerPoint group slides, but an internal hyperlink targets an individual slide. To create navigation to a section, link to the first slide in that section.
Can I attach a hyperlink to master slide elements so it works on all slides?
Yes. Master slide and layout elements support hyperlinks. Links on these elements are available during the slide show on slides that use the corresponding master or layout.
Will hyperlinks be preserved when exporting to PDF, HTML, images, or video?
Supported PDF and HTML exports may preserve hyperlinks; raster images and video cannot. See the export considerations in Report, Sanitize, and Verify Hyperlinks.