Skip to content

Profiles ​

Profiles provide feature restriction for different rendering contexts. They complement SafeMode (XSS prevention) by controlling which markup features are available.

Quick Start ​

php
use Djot\DjotConverter;
use Djot\Profile;

// User comments - basic formatting, nofollow links
$converter = new DjotConverter(profile: Profile::comment());

// Blog posts - all formatting, no raw HTML
$converter = new DjotConverter(profile: Profile::article());

// Chat messages - inline formatting and lists only
$converter = new DjotConverter(profile: Profile::minimal());

// Trusted content - everything allowed
$converter = new DjotConverter(profile: Profile::full());

Built-in Profiles ​

Profile::full() ​

All features enabled. Use only for trusted content like admin interfaces or backend documentation.

php
$converter = new DjotConverter(profile: Profile::full());

Profile::article() ​

For blog posts and articles. All formatting features except raw HTML.

Allowed: Headings, paragraphs, lists, tables, code blocks, images, links, footnotes, all inline formatting

Denied: Raw HTML blocks and inline (XSS prevention)

php
$converter = new DjotConverter(profile: Profile::article());

Profile::comment() ​

For user-generated content like comments. Basic formatting with nofollow links.

Allowed:

  • Inline: text, emphasis, strong, code, link, delete, insert, highlight, superscript, subscript, line breaks
  • Block: paragraphs, lists, blockquotes, code blocks

Denied: Headings, images, tables, footnotes, raw HTML, divs, spans, symbols, math, definition lists

Link policy: Adds rel="nofollow ugc" to all links

php
$converter = new DjotConverter(profile: Profile::comment());

Profile::minimal() ​

For chat messages and micro-posts. Basic inline formatting plus lists.

Allowed:

  • Inline: text, emphasis, strong, code, delete, insert, superscript, subscript, line breaks
  • Block: paragraphs, lists

Denied: Links, images, highlight, headings, tables, code blocks, raw HTML, footnotes

Max nesting: 2 levels

php
$converter = new DjotConverter(profile: Profile::minimal());

Profile Hierarchy ​

Each profile is a superset of the one below it:

full ⊃ article ⊃ comment ⊃ minimal
ProfileInline elementsBlock elements
minimaltext, emphasis, strong, code, delete, insert, superscript, subscript, breaksparagraph, lists
commentall of minimal + link, highlightall of minimal + blockquote, code_block
articleall except raw_inlineall except raw_block
fullallall

This ensures consistent behavior: anything allowed in a more restrictive profile is also allowed in less restrictive ones.

Note: For standard djot, Profile::full() and no profile behave identically. However, with extensions that add custom node types, full() will deny unknown types while no profile allows everything through unfiltered.

Custom Profiles ​

Create custom profiles for specific needs:

php
use Djot\Profile;
use Djot\NodeType;
use Djot\LinkPolicy;

$profile = new Profile();
$profile
    ->allowInline([
        NodeType::TEXT,
        NodeType::EMPHASIS,
        NodeType::STRONG,
        NodeType::LINK,
    ])
    ->allowBlock([
        NodeType::PARAGRAPH,
        NodeType::LIST_BLOCK,
        NodeType::LIST_ITEM,
    ])
    ->setLinkPolicy(
        LinkPolicy::unrestricted()
            ->addRelAttribute('nofollow')
    )
    ->setMaxNesting(3)
    ->setMaxLength(10000);

$converter = new DjotConverter(profile: $profile);

setMaxLength() applies to source-based entry points (convert(), convertFile(), parse(), parseFile()). It does not apply to render(Document) because an arbitrary AST does not retain a canonical source length.

Deny Lists ​

Instead of allowlists, use deny lists to block specific features:

php
$profile = new Profile();
$profile
    ->denyInline([NodeType::RAW_INLINE])
    ->denyBlock([NodeType::RAW_BLOCK, NodeType::TABLE]);

Handling Disallowed Elements ​

Three options for what happens when disallowed markup is encountered:

php
// Convert to plain text (default - safest for UX)
$profile->onDisallowed(Profile::ACTION_TO_TEXT);

// Strip completely from output
$profile->onDisallowed(Profile::ACTION_STRIP);

// Throw exception
$profile->onDisallowed(Profile::ACTION_ERROR);

Example with ACTION_TO_TEXT:

php
$converter = new DjotConverter(profile: Profile::minimal());
$html = $converter->convert('Check [this link](https://example.com)!');
// Output: <p>Check this link!</p>
// Link converted to text, URL removed

Control how links are rendered:

php
use Djot\LinkPolicy;

// Add nofollow to all links
$policy = LinkPolicy::unrestricted()
    ->addRelAttribute('nofollow')
    ->addRelAttribute('ugc');

// Only allow specific domains
$policy = LinkPolicy::create()
    ->allowDomains(['example.com', 'docs.example.com']);

// Block specific domains
$policy = LinkPolicy::create()
    ->denyDomains(['spam.com', 'malware.site']);

// Block dangerous schemes
$policy = LinkPolicy::create()
    ->denySchemes(['javascript', 'data', 'file']);

$profile = new Profile();
$profile->setLinkPolicy($policy);

Feature Reasons ​

Provide user-friendly explanations for why features are disabled:

php
$profile = Profile::comment();

// Get reason for specific feature
$reason = $profile->getReasonDisallowed(NodeType::IMAGE);
// "Images are disabled to prevent spam, inappropriate content, and bandwidth abuse."

// Set custom reason
$profile->setFeatureReason(
    NodeType::TABLE,
    'Tables are not supported in comments. Please use lists instead.'
);

Combining with SafeMode ​

Profile (feature restriction) and SafeMode (XSS prevention) work together:

php
$converter = new DjotConverter(
    profile: Profile::comment(),  // Feature restriction
    safeMode: true,               // XSS prevention
);
  • Profile: "What features are allowed?"
  • SafeMode: "How do we prevent XSS in allowed features?"

Node Types Reference ​

Inline Types ​

TypeDescription
NodeType::TEXTPlain text
NodeType::EMPHASIS_italic_
NodeType::STRONG*bold*
NodeType::CODE`code`
NodeType::LINK[text](url)
NodeType::IMAGE![alt](url)
NodeType::DELETE{-deleted-}
NodeType::INSERT{+inserted+}
NodeType::HIGHLIGHT{=marked=}
NodeType::SUPERSCRIPT{^super^}
NodeType::SUBSCRIPT{~sub~}
NodeType::SPAN[text]{.class}
NodeType::SYMBOL:symbol:
NodeType::MATH$math$
NodeType::RAW_INLINE`<b>`{=html}
NodeType::FOOTNOTE_REF[^note]
NodeType::SOFT_BREAKLine break (soft)
NodeType::HARD_BREAKLine break (hard)

Block Types ​

TypeDescription
NodeType::PARAGRAPHParagraph
NodeType::HEADING# Heading
NodeType::CODE_BLOCKFenced code block
NodeType::BLOCKQUOTE> quote
NodeType::LIST_BLOCKList container
NodeType::LIST_ITEMList item
NodeType::TABLETable
NodeType::DIV::: div
NodeType::SECTIONSection wrapper
NodeType::THEMATIC_BREAK---
NodeType::RAW_BLOCKRaw HTML block
NodeType::FOOTNOTEFootnote definition
NodeType::DEFINITION_LISTDefinition list
NodeType::DEFINITION_TERMDefinition term
NodeType::DEFINITION_DESCRIPTIONDefinition description
NodeType::LINE_BLOCKLine block

Released under the MIT License.