Skip to content

[MarkdownEditor] Allow document relative links in the link validator plugin#9554

Merged
acstll merged 9 commits intoelastic:mainfrom
Zacqary:markdown-bare-relative-link-validate
Apr 13, 2026
Merged

[MarkdownEditor] Allow document relative links in the link validator plugin#9554
acstll merged 9 commits intoelastic:mainfrom
Zacqary:markdown-bare-relative-link-validate

Conversation

@Zacqary
Copy link
Copy Markdown
Contributor

@Zacqary Zacqary commented Apr 1, 2026

Summary

In elastic/kibana#257733, the Dashboard Markdown panel experienced a regression when switching from our legacy custom Markdown editor to the EUI Markdown editor: relative links without a leading / started getting thrown out by the markdown link validator plugin. Links like <a href="discover">, which the browser would natively resolve to the /app/discover on the current space, no longer worked.

We were able to build a workaround parsing plugin to resolve these inks before the link validator ran, but this capability should probably be added to EUI directly.

This PR adds a document relative link resolver to the euiMarkdownLinkValidator plugin. It is enabled by the allowDocumentRelative option.

Why not just let the consumer disable the plugin?

We can also get document-relative links to work if we disable the link validator altogether, but restricting protocols to the default allowlist of http, https, and mailto is still big help for security. With this change, we can allow document-relative links to work while still blocking dangerous stuff like ftp:// or javascript:

Also, browsers resolve document-relative hrefs inconsistently if a trailing / gets added to a base URL. Given a link to the [same document root](../another-page), the browser would resolve:

  • http://base.url/a/b/c -> http://base.url/a/b/another-page
  • http://base.url/a/b/c/ -> http://base.url/a/b/c/another-page

The changes in this PR will strip the trailing / from the base URL before resolving the link to ensure consistent behavior.

Kibana doesn't seem to add trailing slashes all that much, but the EUI docs website does.

Alternative: create a new plugin?

If we want to keep euiMarkdownLinkValidator solely as a yes/no validator, we could take the document link resolver code and put it in a new plugin, which would get added to the default plugin chain before euiMarkdownLinkValidator runs. I think there's a simplicity advantage of keeping this code in the validator plugin itself, because it allows us to put the allowDocumentRelative config option in the same object as allowRelative. Open to changing this if the EUI team disagrees.

API Changes

component / parent prop / child change description
getDefaultEuiMarkdownPlugins parsingConfig adds allowDocumentRelative option allowDocumentRelative enables the document relative link resolver in the link validator plugin, defaults to false so as to not change existing implementations

Impact Assessment

Impact level: 🟢 Low

This PR sets allowDocumentRelative to false by default. Consumers will have to manually enable it to change link validation behavior.

Release Readiness

QA instructions for reviewer

Testing the feature

Note that the Markdown editor storybook seems to be broken, as I wasn't able to actually change the editor content in my testing. So here's how I was able to test locally:

  • In packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx change line 50 to read allowDocumentRelative: true
  • In packages/eui/src/components/markdown_editor/markdown_editor.stories.tsx add the following to the end of initialContent: [link](link)
  • Open the Markdown editor in the sandbox
  • Switch to Preview mode and ensure link is a link and hasn't been converted to a text node
  • Click the link and ensure it takes you to localhost:6006/link (You'll get a Not Found page because Storybook only uses ?-based pathing)

Documentation

Review the two docs pages listed under Release Readiness

Checklist before marking Ready for Review

Reviewer checklist

  • Approved Impact Assessment — Acceptable to merge given the consumer impact.
  • Approved Release Readiness — Docs, Figma, and migration info are sufficient to ship.

@Zacqary Zacqary changed the title [MarkdownEditor] Allow bare relative links in the link validator plugin [MarkdownEditor] Allow document relative links in the link validator plugin Apr 1, 2026

export const DEFAULT_OPTIONS = {
allowRelative: true,
allowDocumentRelative: false,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also default this to true if we want to just enable this functionality for free, but I figured I'd start with false to avoid changing existing implementations

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think your choice to make it false by default is good 👍

@Zacqary Zacqary marked this pull request as ready for review April 1, 2026 19:32
@Zacqary Zacqary requested a review from a team as a code owner April 1, 2026 19:32
Copilot AI review requested due to automatic review settings April 1, 2026 19:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates EUI’s Markdown link validator plugin to optionally support document-relative links (e.g. [link](discover)), addressing a regression seen when moving from a legacy editor to EUI’s Markdown editor while preserving protocol allowlisting for security.

Changes:

  • Add allowDocumentRelative (and baseUrl) options to euiMarkdownLinkValidator and implement document-relative URL resolution.
  • Add Jest coverage for document-relative URL detection and resolution behavior.
  • Update EUI documentation and examples to demonstrate enabling document-relative links, plus add a changelog entry.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/website/docs/components/editors-and-syntax/markdown/plugins.mdx Updates plugin docs/example to enable allowDocumentRelative and show document-relative links.
packages/website/docs/components/editors-and-syntax/markdown/format.mdx Adds security/link-validation documentation clarifying document-relative behavior and configuration.
packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx Implements document-relative detection + resolution and adds new options.
packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.test.tsx Adds tests for isDocumentRelativeUrl and allowDocumentRelative resolution.
packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/parsing_plugins.ts Updates parsing plugin config docs to mention the new option.
packages/eui/src/components/markdown_editor/markdown_editor.stories.tsx Adds a document-relative link example to the story content.
packages/eui/changelogs/upcoming/9554.md Adds changelog entry for the new option.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx Outdated
Comment thread packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx Outdated
Comment thread packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx Outdated
Zacqary added a commit to elastic/kibana that referenced this pull request Apr 6, 2026
…e-window target (#260782)

## Summary

Closes #253431 
Closes #257733

This fixes a couple of regressions in the new Markdown panel which
resulted from us switching from our own bespoke Markdown editor to
`EuiMarkdownEditor`

### Fix relative links
I've added a new parsing plugin to our implementation of the markdown
editor which handles space-relative links like `[discover](discover)`
linking to `/app/discover`. The EUI link validator strips these as
invalid, so the new parsing plugin will resolve them to full paths
before the link validator runs. It also handles other document-relative
links like `../../../upper-level-path`

I have opened an [EUI PR](elastic/eui#9554) to
commit these changes upstream to the link validator plugin. Once that
merges, we can revert this custom plugin and just pass
`allowDocumentRelative: true` to the default EUI Markdown plugins'
`parsingConfig`.

### Add open-in-new-tab setting

This restores the previous toggle setting for opening links in a new tab
or not. The user can do this through a new settings popover.

In legacy, it defaulted to `false`. I'm keeping it `true` by default in
the new implementation, but allowing the user to disable it.
<img width="1168" height="509" alt="Screenshot 2026-04-01 at 2 38 15 PM"
src="https://github.com/user-attachments/assets/4570163e-ee76-491b-8ee6-caf0fda74887"
/>

#### Known design bug
To prevent the settings button tooltip from covering the Editor/Preview
toggle, I set the tooltip position to `bottom`

<img width="996" height="196" alt="Screenshot 2026-03-31 at 3 10 26 PM"
src="https://github.com/user-attachments/assets/c07fbe15-d7e1-431f-b89e-c556de68a806"
/>

But because of EUI limitations, I can't do the same for the help button:

<img width="265" height="150" alt="Screenshot 2026-03-31 at 3 42 08 PM"
src="https://github.com/user-attachments/assets/6274b0d0-3456-45a2-b295-14841f818ef1"
/>

I've submitted an upstream [EUI
PR](elastic/eui#9546) to allow us to fix this.

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
@acstll acstll self-requested a review April 8, 2026 07:26
Copy link
Copy Markdown
Contributor

@acstll acstll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @Zacqary, for opening this. It's working as expected, and the code looks good. I left a few comments, mostly about JSDoc 🙂

Comment thread packages/eui/changelogs/upcoming/9554.md Outdated
});
});

describe('euiMarkdownLinkValidator with allowDocumentRelative', () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(non-blocking) would you consider adding a test case for allowDocumentRelative: true with allowRelative: false? to confirm it's intentional that allowRelative is required for allowDocumentRelative to be enabled…

Comment on lines +155 to +162
it('preserves hash fragments during resolution', () => {
const ast = createLinkAst('discover#/view/saved-search');
euiMarkdownLinkValidator({ allowDocumentRelative: true, baseUrl })(ast);

expect(getLinkNode(ast).url).toBe(
'/s/my-space/app/discover#/view/saved-search'
);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(non-blocking) would it be a good idea to add a similar test case for preserving query strings e.g. 'discover?index=logstash-*'


export const DEFAULT_OPTIONS = {
allowRelative: true,
allowDocumentRelative: false,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think your choice to make it false by default is good 👍

Comment on lines +145 to +148
/**
* Resolves a document relative URL against a base URL, replicating
* native browser resolution of e.g. `<a href="discover">`.
*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(non-blocking) is it actually true this replicates native browser resolution if it's stripping the trailing slash? i understand resolution would be different whether there's a trailing slash or not… i would suggest updating the JSDoc if you agree

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up catching the trailing slash bug after writing the initial comment, good call on updating it. Pushed a fix.

* resolution, the same way an `<a href="discover">` would behave in
* plain HTML.
* @default false
*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's intentional, i think it'd be good to mention here that allowRelative=true is required for allowDocumentRelative=true to work

also, similarly to my comment below, would you consider adjusting the "using the browser's native URL resolution" part to mention trailing slashes will be stripped?

Zacqary and others added 2 commits April 9, 2026 12:28
@Zacqary Zacqary requested a review from acstll April 9, 2026 17:32
@elasticmachine
Copy link
Copy Markdown
Collaborator

💚 Build Succeeded

History

@elasticmachine
Copy link
Copy Markdown
Collaborator

💚 Build Succeeded

History

Copy link
Copy Markdown
Contributor

@acstll acstll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Thanks @Zacqary for addressing the feedback and again for the PR!

@Zacqary
Copy link
Copy Markdown
Contributor Author

Zacqary commented Apr 13, 2026

@acstll Thanks for the approval, I don't have merge permissions in this repo, if you're able to hit merge for me?

@acstll acstll merged commit 2b88d4f into elastic:main Apr 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants