Wiki custom macros
Requirements
This capability is available in ONES 7.45.0. Install and enable the app, using the following tools:
| Component | Version |
|---|---|
| ONES | 7.45.0 |
@ones-open/web-sdk | 0.0.20 |
create-ones-app | 0.0.20 |
@ones-open/node-sdk (for backend static updates) | 0.0.20 |
Overview
Wiki custom macros embed custom document blocks directly in the Wiki page body to display and interact with custom content. The macro becomes part of the page content.
The app defines the macro name, edit dialog content, display styles and export content. Wiki manages insertion, viewing and editing. Wiki supplies the edit dialog container; the app defines the configuration UI and its styles inside it.
| Capability | Provided by Wiki | Implemented by the app |
|---|---|---|
| Display | Loads the render entry into the page body and supplies context | Macro name, display styles and business interactions |
| Editing | Toolbar, edit dialog container, permissions and collaboration controls | Edit dialog content, validation and save logic |
| Static output | Displays app-defined static content for sharing, mobile, search and export | Definition and updates of static content |
Architecture
Macros integrate with Wiki through the open platform. Wiki manages the page and macro workflows, the platform connects apps to the product, and the app implements custom content and business logic. Declare macro configuration and entries in opkx.json; after installation and activation, the platform makes them available to Wiki.
Capability relationships
customData and resource references are saved with the Wiki document; exportSpec is stored in the Wiki macro card. The app manages its own business data. After business data changes, the app backend must explicitly update static content; see Update static content from the app backend.
Viewing, editing and static display
The configuration flow shows saving and finishing. Cancelling or closing the dialog restores pre-session macro content. App frontend business actions and backend data updates are managed by the app and are separate from saving or cancelling macro configuration.
What users see
Insert
In page editing mode, type /, followed by the macro shortcut declared by the app. Each menu row shows the macro icon and name on the left and the shortcut, such as /custom, on the right.
Edit
Select the macro to insert app content into the page body. In page editing mode, Wiki provides a toolbar with the macro icon, name and declared action buttons. An action opens the configuration form in a dialog. Wiki supplies the dialog container and collaboration controls; the app supplies the configuration UI, validation and save logic.
Finishing editing keeps saved changes. Cancelling or closing the dialog restores macro content from before this edit. See Collaborative editing for multi-user restrictions.
View
App content appears inside the container provided by Wiki. Your app should provide the display and interactions required by its business logic. Viewing mode does not show the Wiki configuration toolbar.
Static output
Public sharing, mobile and exports display saved static content without running the macro. Static content supports text, links and images; search extracts searchable text from it.
Capability configuration
Macros integrate through an app declaration and page entries. The following snippets illustrate configuration and SDK usage without prescribing a business implementation.
Extension declaration
Merge the following configuration into app.extensions in your app's opkx.json:
{
"app": {
"extensions": {
"wikiMacro": [
{
"key": "customMacro",
"slots": {
"ones:wiki:macro:render": "/web/pages/macro.html",
"ones:wiki:macro:edit": "/web/pages/macro-editor.html"
},
"config": {
"name": "Custom macro",
"shortcut": "custom",
"actions": [{ "id": "configure", "title": "Configure" }]
}
}
]
}
}
}
| Configuration | Required | Purpose |
|---|---|---|
key | Yes | Unique macro identifier within the app; keep it stable across upgrades |
slots | Yes | Macro render entries |
slots.ones:wiki:macro:render | Yes | Content render area inserted into the Wiki page body |
slots.ones:wiki:macro:edit | Conditional | Configuration render entry; required when actions are declared. Wiki renders it in an edit dialog during editing |
config | Yes | Macro name, icon, shortcut and actions |
config.name | Yes | Macro name in the insertion menu and toolbar |
config.shortcut | No | Shortcut used to find the macro, such as /custom |
config.icon | No | App image path; omission uses the transparent default icon |
config.actions | No | Action buttons in the Wiki toolbar |
config.actions[].id | For each action | Unique action ID within the macro, passed as toolbarState.actionId |
config.actions[].title | For each action | Action button label |
config.actions[].icon | No | App icon path for the action button |
Entry and icon paths must start with / and refer to resources included in your app build. Use transparent SVG or PNG files for custom icons. Macro keys and action IDs accept letters and digits, up to 64 characters.
Full declaration constraints
Wiki Macro Declaration
Properties
^[a-zA-Z0-9]+$^/[^\s?#]*([?][^\s#]*)?([#][^\s]*)?$^/[^\s?#]*([?][^\s#]*)?([#][^\s]*)?$^/[^\s?#]*([?][^\s#]*)?([#][^\s]*)?$^[a-zA-Z0-9]+$^/[^\s?#]*([?][^\s#]*)?([#][^\s]*)?$All of:
Macro context
Both the render and edit entries can use ONES.getExtensionContext to read the current macro context or subscribe to changes:
import { ONES } from '@ones-open/web-sdk'
import type { WikiMacroContext } from '@ones-open/web-sdk'
const context = await ONES.getExtensionContext<WikiMacroContext>()
console.log(context.customData, context.scene, context.writable)
import { ONES } from '@ones-open/web-sdk'
import type { WikiMacroContext } from '@ones-open/web-sdk'
const unsubscribe = ONES.getExtensionContext<WikiMacroContext>((context) => {
// Update the UI using the latest configuration, scene and editability.
console.log(context.customData, context.scene, context.writable)
})
// Keep unsubscribe and call unsubscribe() when the entry unmounts.
// In React, return it from useEffect; see the render component example below.
| Information | Fields | Description |
|---|---|---|
| App and macro identity | appId, installationId, macroKey | Current app, installation and macro definition |
| Document location | teamId, pageId, documentId, blockId, cardUuid | Team, page, collaborative document, macro block and static content card |
| Document content | customData, resources | Macro data and resource IDs saved for the displayed version |
| Document version | documentVersion | Current collaborative revision or displayed page version |
| Page scene | scene | Active runtime scenes are edit, view and history |
| Document editability | writable | Whether configuration editing is allowed, subject to permissions, scene and locks |
| Insertion state | isInsertedByCurrentUser | Whether the current user inserted this runtime instance; useful for first-time setup guidance |
| Macro editing state | toolbarState | { state: 'init' } or { state: 'editing', actionId, customParams? } |
getExtensionContext reads context; it neither saves data nor retrieves the published exportSpec. Public sharing, mobile and search do not run macros and send no context callbacks to the app.
writable controls document configuration editing, not app business permissions. Viewing mode may allow business actions under app permissions; historical versions must disable all mutations. Subscribe to changes, and authenticate and authorize business actions on your backend.
Implementation
After declaring the extension, implement the render and edit entries and use macro context to connect configuration with content. The examples below support the insertion, editing and viewing flow described above.
Render entry
The render entry displays content from customData. Mount this component using your app template at the entry declared by ones:wiki:macro:render:
import { ONES } from '@ones-open/web-sdk'
import type { WikiMacroContext } from '@ones-open/web-sdk'
import { useEffect, useState } from 'react'
export default function MacroContent() {
const [context, setContext] = useState<WikiMacroContext>()
useEffect(() => ONES.getExtensionContext<WikiMacroContext>(setContext), [])
if (!context) return <p>Loading…</p>
const data = context.customData
const text =
data && typeof data === 'object' && !Array.isArray(data) && typeof data.text === 'string'
? data.text
: 'No content yet'
return (
<section>
<p>{text}</p>
{context.scene === 'history' && <p>Historical version · Read only</p>}
</section>
)
}
If your app needs an additional configuration entry inside macro content, show it only when scene === 'edit', writable === true and toolbarState.state === 'init', then call Wiki.macro.openEditor({ actionId: 'configure' }).
Edit entry and content saving
The edit entry is the configuration UI declared by ones:wiki:macro:edit. Wiki page and macro editing have distinct lifecycles:
| Stage | Wiki behavior | App responsibility |
|---|---|---|
| App enabled or page opened | Offers insertion or loads an existing macro's render entry | Reads saved customData; loading alone does not change the document |
| Macro inserted and first configured | Creates the instance; a declared action opens configuration | Initializes and saves customData, exportSpec and resources through saveData |
| Configuration reopened | Acquires an edit session and opens the entry with actionId | Initializes a draft from current customData and identifies the selected action |
| Macro content saved | Confirms data and static content while keeping editing open | Submits complete content through saveData; retains the draft on failure |
| Editing finished | finishEdit ends the session and keeps acknowledged saves | Waits for saveData before finishing; finishing does not save the form itself |
| Editing cancelled or dialog closed | Restores data, static content and resource references from before this edit | Calls cancelEdit; separate app database writes are not rolled back |
| Wiki page published | Saves a page version through the page publication flow | Saving or finishing a macro does not publish the entire Wiki page |
Initialize the form from context and identify the action through toolbarState.actionId. Each saved field has a distinct purpose:
customDatainitializes and changes macro configuration, such as display settings, form content or a business record ID.exportSpecdefines static content for sharing, mobile, search and export; update it when data changes affect static output.resourcescontains referenced document resource IDs, not original filenames or binary file content.
On confirmation, save complete content before ending editing:
import { ONES, Wiki } from '@ones-open/web-sdk'
import type { WikiMacroContext } from '@ones-open/web-sdk'
async function saveMacroContent(text: string) {
const context = await ONES.getExtensionContext<WikiMacroContext>()
if (!context.writable || context.toolbarState.state !== 'editing') return
await Wiki.macro.saveData({
customData: { text },
exportSpec: [{ type: 'text', data: text }],
resources: [],
})
await Wiki.macro.finishEdit()
}
async function cancelMacroEdit() {
await Wiki.macro.cancelEdit()
}
saveData submits complete content, not a partial update:
| Field | Content |
|---|---|
customData | App JSON data such as display configuration or a business record ID; at most 32 KiB of serialized UTF-8 content |
exportSpec | Static content for sharing, mobile, search and export, displayed in array order |
resources | Document resource IDs referenced by the macro; pass [] when none are needed |
Document configuration is stored in customData and participates in Wiki page versioning. Business data can be stored separately in Hosted Storage or an app database, with a business record ID referenced from customData.
On a save failure, retain the draft and show an error. Do not call finishEdit or report success early. Disable duplicate submissions while saving and update form editability when context changes.
Call saveData only from the macro edit entry, never from ordinary viewing mode.
Build and install
After the app is built, installed and enabled, its macros appear in the Wiki insertion menu. Follow the insertion and editing flow above to configure a macro, then publish the page.
Wiki pages displayed in Project directory components also support macros without an extra entry. Work item descriptions do not support macros; pasted macro blocks are filtered out.
Static content and data updates
Text, links and images
exportSpec supports these formats. Text is plain text and does not interpret HTML:
const imageId = await Wiki.macro.uploadResource(file)
await Wiki.macro.saveData({
customData: {
text: 'Content summary',
image: { resourceId: imageId, originalName: file.name },
},
exportSpec: [
{ type: 'text', data: 'Content summary' },
{ type: 'link', data: { title: 'Instructions', url: 'https://example.com/details' } },
{ type: 'image', data: imageId },
],
resources: [imageId],
})
Use this excerpt in the macro edit entry; file comes from your file selection form. uploadResource returns a Wiki document resource ID. Files must not exceed 20 MiB and are subject to Wiki file type and quota restrictions. Uploading does not save macro content; reference the resource in a later save. This replaces complete content, so also submit any other configuration and resources you need to retain.
Uploads and original filenames
After obtaining a File from a file selection form, the app can read file.name, the original selected filename. The SDK submits this name together with the uploaded content.
Wiki.macro.uploadResource(file) returns only Promise<string>, containing the document resource ID. It does not return an additional original filename. The image example above saves file.name alongside the resource ID in customData.image, so reopening the macro can restore both from context. resources still contains IDs only.
Do not infer filenames from resource IDs. The macro SDK currently has no method to look up original filenames by resource ID. Backend uploads take an app-supplied filename through uploadResource({ name, data }); retain it in the app when needed.
Update static content from the app backend
When app business data changes, the backend can call saveExportSpec to update the associated macro’s static content. Business persistence and static updates are separate operations and do not synchronize automatically.
import { wiki } from '@ones-open/node-sdk'
// installationInfo comes from installation callback data stored by the backend.
// context is supplied by the render entry; validate and retain the association.
const macro = wiki.macro(installationInfo).instance(context)
await macro.saveExportSpec([{ type: 'text', data: 'Content summary' }], [])
Keep installation credentials on the backend. saveExportSpec submits complete static content and resource references. It changes no macro configuration and creates no page version. Refresh the share or mobile page, or create a new export, to see successfully published updates. Existing exported files are unchanged.
If business persistence succeeds but publication fails, retry only static synchronization to avoid repeating the business operation. When multiple macros reference one business record, retain each macro's context and synchronize each separately.
Collaborative editing
Only one session can edit a macro's configuration at a time. Wiki manages locks, release and state synchronization; your app does not need its own lock or unlock button.
| Scenario | What users see | Actions and restrictions |
|---|---|---|
| No session is editing | Page editing mode shows the macro icon, name and action buttons | Users with edit permission can open the configuration dialog |
| This window is editing | The dialog displays the app's form; configuration buttons in the page body are disabled | Configure, save or cancel in the dialog |
| Another window is editing | A lock-status icon and inline editing hint replace configuration buttons; macro content remains visible | Configuration editing is unavailable; the app receives writable: false |
| Editing finishes or is cancelled | The dialog closes; other windows restore configuration buttons after state synchronization | The collaboration lock is released |
| Viewing, historical versions or no edit permission | No Wiki editing toolbar or lock hint | No configuration editing; apps must also disable business mutations in historical versions |
Two tabs signed in as the same account are separate sessions. A locked macro cannot be moved, deleted or replaced, including through its parent container. Other page content remains editable. Configuration entries inside your app must also react to writable and toolbarState. On failure, retain the draft and follow the error guidance to retry or reopen editing.
Display in other scenarios
| Scenario | Display | App responsibility |
|---|---|---|
| Desktop viewing and editing | Runs the render entry; editing can open the configuration form | Adapt configuration entries to context; authorize business actions in the app |
| Desktop history | Runs the render entry with historical configuration | Disable mutations; business storage does not automatically return to a historical state |
| Public sharing, mobile and exports | Uses the last successfully saved exportSpec | Provide readable static content that does not depend on app execution |
| App disabled, uninstalled or macro load failure | Uses saved static content, or a placeholder when none exists | Do not rely on the render entry to handle these scenarios |
Wiki shows a skeleton while the macro loads. Your app handles loading its own business data. Existing macros can run again after the app is re-enabled. Keep macro identifiers stable across upgrades and support previously saved configuration.
FAQ
Why does the share page show old content after the app content changes?
Publish results through backend saveExportSpec. Sharing and exports do not query application storage live. Refresh the page or export again after synchronization succeeds.
Do macro content updates create page versions?
Business data writes and saveExportSpec do not create page versions. Configuration changes through saveData participate in normal page editing and versioning.
Do historical versions restore app business data?
Wiki provides historical macro configuration, but application business storage is not rolled back. Store and read the relevant historical business data in your app when required.
Web SDK quick reference
See Wiki macro types for type definitions and every field, and ONES.getExtensionContext for callback parameters, return values and cleanup.
All methods return a Promise and act on the current macro.
| Method | Purpose |
|---|---|
openEditor({ actionId, customParams? }) | Opens a declared edit action |
saveData({ customData, exportSpec, resources }) | Saves complete macro content and keeps editing open |
finishEdit() | Waits for saves and ends editing |
cancelEdit() | Restores content from before editing and exits |
uploadResource(file) | Uploads a document resource and returns its ID; requires sign-in and card read permission, but no edit session |
setStatus('valid' | 'dataInvalid' | 'resourceInvalid') | Reports render status; invalid states cause Wiki to show static content |
Node SDK quick reference
See Wiki macro SDK for backend integration and Types and fields for installation data, macro context and file fields.
| Interface | Purpose |
|---|---|
wiki.macro(installationInfo) | Create a client with installation credentials |
client.instance(context) | Bind an existing macro instance |
macro.uploadResource({ name, data }) | Upload document resources and return resource IDs |
macro.saveExportSpec(exportSpec, resources) | Update complete static content and resource references |