Template requirements 

The extension has exactly one requirement of your templates: every content element must be identifiable in the rendered HTML.

The content element ID 

Each content element needs a "c-id" — its UID prefixed with c — on the element that wraps it:

Rendered HTML of a content element
<div id="c10" class="frame frame-default frame-type-textpic">
    ...
</div>
Copied!

This is how the injected JavaScript maps a DOM node to a database record. No c-id means no edit button for that element.

Nothing to do. fluid_styled_content renders the c-id out of the box, so the extension works immediately after Installation.

Add the UID to the wrapping element yourself:

EXT:my_sitepackage/Resources/Private/Templates/Content/Default.html
<div id="c{data.uid}">
    ...
</div>
Copied!

container templates do not render the c-id by default:

Container template
<div id="c{data.uid}">
   <f:for each="{children_200}" as="record">
       <f:format.raw>{record.renderedContent}</f:format.raw>
   </f:for>
</div>
Copied!

DCE elements need the c-id added to the DCE template:

DCE template
<div class="dce" id="c{contentObject.uid}">
    Your template goes here...
</div>
Copied!

Alternative: the data-frontend-edit attribute 

For templates that cannot carry the c-id anchor - dynamic content element extensions (DCE), other custom Fluid templates - a second matching channel exists: a data-frontend-edit="tt_content:{uid}" attribute on the content element's own wrapping HTML element.

Example HTML output using the data attribute instead
<div data-frontend-edit="tt_content:10" class="my-custom-wrapper">
    ...
</div>
Copied!

The bundled xfe:editable ViewHelper renders this attribute for you:

Custom Fluid Template
{namespace xfe=Xima\XimaTypo3FrontendEdit\ViewHelpers}

<div class="my-custom-wrapper"{xfe:editable(record: data)}>
    ...
</div>
Copied!

The ViewHelper renders only the attribute, so it goes inside the opening tag in inline notation. The tag notation <div <xfe:editable record="{data}" />> renders the same output, but it is not valid HTML and breaks IDEs, formatters and linters.

Both patterns can be mixed freely on the same page; an element only needs one of them. Unlike the c-id anchor pattern, a data-frontend-edit element is always treated as the content element itself - no sibling resolution is attempted.

Alternative: render markers (experimental) 

Templates you cannot or do not want to change, e.g. Content Blocks without the fluid_styled_content layout, can be detected through the site setting frontendEdit.markerBasedDetection. While it is active, every rendered content element is wrapped in a pair of HTML comments:

Rendered HTML with render markers
<!--xfe:b:tt_content:10-->
<div class="my-custom-wrapper">
    ...
</div>
<!--xfe:e:tt_content:10-->
Copied!

The markers are an addition to the other two patterns. An element that already has a c-id or a data-frontend-edit attribute keeps using it.

The output of the content element needs exactly one root element. With several root elements, or with loose text next to the root element, the markers cannot point at a single element and are ignored. The element's own empty anchor is not counted, so the anchor pattern <a id="c10"></a><div>...</div> works.

Two features still rely on the c-id and do not work for elements that are only detected through markers or the data-frontend-edit attribute:

Render markers lists the operational limits, such as caching, minifiers and non-HTML output.

Editing foreign records (news, addresses, ...) 

The data-frontend-edit attribute also works for records from any other table - not just tt_content - by adding a table prefix: data-frontend-edit="{table}:{uid}". This covers the classic case of editing foreign records displayed on a detail page, e.g. a news detail page rendered by EXT:news:

News detail template (Detail.html)
{namespace xfe=Xima\XimaTypo3FrontendEdit\ViewHelpers}

<div class="news-detail"{xfe:editable(record: newsItem, table: 'tx_news_domain_model_news')}>
    <h1>{newsItem.title}</h1>
    ...
</div>
Copied!

record takes a record array such as {data} or an object with a getUid() method, such as an Extbase model or a core Record object. Alternatively, pass the uid directly.

This is deliberately thin: the menu offers exactly edit, info and history - no hide, delete or move, since those are meaningful only for tables this extension understands specifically (tt_content, pages). Permissions are checked the same way as everywhere else in the extension (the backend user's actual edit rights on that record); a table the current user cannot edit - or that TYPO3 does not know at all - never gets a menu. Translated records resolve to the current frontend language automatically, the same way tt_content does.

Extend the menu the same way as for content elements, via the FrontendEditDropdownModifyEvent - the record row carries a _table key so a listener can tell it apart from a tt_content row.

Optional markers 

Two features need additional markers in your templates. Both are opt-in — omit them and the corresponding feature simply does not appear.

Marker Needed for
ColumnTargetViewHelper "Create new content" buttons per column, and Drag & Drop Reordering (drop targets cannot be resolved without it)
Data ViewHelper Edit links for related records inside a plugin, e.g. single news items in a list

What cannot be edited 

Only content elements belonging to the current page receive a menu. Inherited content — a shared footer pulled in from another page, for example — cannot be edited from the inheriting page. Use the toolbar to jump to the page that owns the record.