Plugin API Reference Documentation

Note as of March 2023: If you're interested in building plugins (even if only for your own use) and would be interested in a chance to influence the plugin system, shoot a quick email to support@amplenote.com from the email address associated with your account. Feedback can be provided in the #plugin-atelier channel on our discord.



Amplenote provides support for client-side plugins that execute in the application on all platforms, allowing for enhancement of the default client behavior. A plugin is defined by a single note in a user's account - as the note is changed and updated, the plugin will be updated as well.


This document provides an overview of what plugins are capable of, and describes the available options for plugins.


If you would like to read a more general guide to getting started writing plugins, check out this related page on writing your first plugin.




linkPlugin creation

To create a plugin, you'll need a note that contains two things: a table of plugin information, and a code block containing the Javascript code of the plugin.



linkMetadata table

A plugin metadata table contains (at least) two columns: the name of the setting, and a value for the setting.

setting name

setting value

The setting name is not case sensitive. All columns are interpreted as strings.


linkname

The only required setting is the name of the plugin, which can be defined as:

name

Plugin name here

This is the name that the user will see when invoking the plugin, and will be used as a prefix in cases where the plugin defines multiple options presented to the user.


linkicon

The name of a Material Design Icon that will be used to identify the plugin. If not provided, a generic "extension" icon will be used.

icon

search


linkdescription

A short description shown when installing or configuring a plugin.

description

Count the number of words in a note.


linkinstructions

More detailed information about using the plugin that will be shown if the plugin is published to the Plugin Directory.

instructions

Here are some helpful words of advice on using this plugin:
1. Instruction one
2. Instruction two
3. Instruction three


linksetting

Defines settings that the user can provide to configure the plugin. The user will be able to supply a string for each setting when configuring the note as a plugin. When plugin code is invoked, it has access to the settings values that the user has provided. All setting values are provided as strings.

setting

API Key


This setting name can be repeated multiple times to define multiple settings.

setting

API Key

setting

Name


linkCode

The first code block in the note will be used as the plugin's code. Any subsequent code blocks will be ignored. The plugin code should define a Javascript object. Any functions defined on this object that match the name of an action will register the plugin to handle that action.

{
insertText(app) {
return "Hello World!"
}
}


To define multiple actions for a single action type, make the action-named field an object. The keys should be the name of the action, with the corresponding function as the value.

{
insertText: {
"one word": function(app) {
return "hello";
},
"two words": function(app) {
return "hello world";
}
}
}


Plugin actions can either be a function - as shown above - or an object with run and check keys with functions as the values (see actions section for further explanation):

{
insertText: {
check(app) {
return true;
},
run(app) {
return "Hello World!";
}
}
}

and:

{
insertText: {
"one word": {
check(app) {
return true;
},
run(app) {
return "hello";
}
},
"two words": function() {
check(app) {
return true;
},
run(app) {
return "hello world";
}
}
}
}


When plugin code is invoked, the plugin object will be this, for example:

{
insertText(app) {
return this._text();
},
 
_text() {
return "hello world";
}
}


The plugin object will be instantiated when the plugin is installed in the client, retaining any state until it is reloaded (e.g. due to the plugin code being changed in the source note).

{
insertText(app) {
this._counter++;
return "hello " + this._counter;
},
 
_counter: 0,
}


Plugin action functions (both run and check) can return promises, which will be awaited.

{
insertText: {
check(app) {
return new Promise(function(resolve) {
setTimeout(resolve, 2000);
}).then(function() {
return true;
});
},
run(app) {
return new Promise(function(resolve) {
setTimeout(resolve, 2000);
}).then(function() {
return "hello world, eventually";
});
}
}
}

Or, they can use async/await syntax:

{
insertText: {
async check(app) {
await new Promise(function(resolve) { setTimeout(resolve, 2000); });
return true;
},
async run(app) {
await new Promise(function(resolve) { setTimeout(resolve, 2000); });
return "hello world, eventually";
}
}
}


The first argument passed to a plugin action function is an application interface object that can be used to access settings and call into the host application itself.


linkAccessing settings

Given a plugin with the following metadata table entry:

setting

API Key

A plugin can access the setting through app.settings:

{
insertText(app) {
return app.settings["API Key"];
}
}


linkAction function arguments

For action functions that receive arguments, the app argument will still be the first argument, before any other arguments:

{
replaceText(app, text) {
return text + " more";
}
}


When using check and run functions, they will receive the same arguments as the action would. So for the linkOption action, for example:

{
linkOption: {
check(app, link) {
return true;
},
 
run(app, link) {
console.log("Hello!");
}
}
}



linkActions

Plugin actions are the interaction points that define how a plugin's code gets called. Actions functions that return values can either return the value directly, or return a Promise that resolves to the value when ready. All action functions are passed an App Interface object as the first argument.


Each action can optionally define a check function that will be called before displaying the plugin to the user in the context of the specific action. The check function receives the same arguments as the plugin action (run) function, and returns a boolean indicating whether the plugin should be displayed or not. Returning a promise that resolves to the boolean is supported, and will show a loading indicator to the user in most cases. Note, however, that the user may no longer be viewing the plugin action context when an asynchronous check completed (e.g. if the user dismisses the menu the plugin action would be listed in).


linkappOption

Adds app-wide options that can be invoked from the "jump to note" (web) or quick search (mobile app) dialogs.

Arguments

Returns

Nothing

{
appOption(app) {
app.alert("hello");
}
}




linkdailyJotOption

Adds an option to the suggestions shown below today's daily jot note in jots mode, with a button to run the plugin action. Implementing a check function is advisable to conditionally show the option, otherwise it will always be shown to the user.

Arguments

noteHandle noteHandle of the daily jot note that the option is being shown in

Returns

Nothing

{
dailyJotOption(app, noteHandle) {
app.alert("Hello");
}
}



To customize the text shown on the "Run" button, return a non-empty string from the plugin action's check function:

{
dailyJotOption: {
check(app, noteHandle) {
return "Do something"; // Return `null`, `false`, or `""` to not show plugin
},
run(app, noteHandle) {
app.alert("Hello");
}
}
}




linkimageOption

Adds an option to the drop-down menu on each image in a note.

Arguments

image image object describing the selected image

Returns

Nothing

See also

app.context.updateImage to update the properties of the image

{
async imageOption(app, image) {
await app.alert("image: " + image.src);
}
}





linkinsertText

Called to insert text at a specific location in a note, when using an {expression}.

Arguments

Returns

String new text to insert in the note in place of the {expression}

{
insertText(app) {
return "hello world";
}
}

The auto-complete menu for expression will include any installed plugins:


The plugin's insertText action will be called to replace the {Test Plugin} expression, where "Test Plugin" is the configured plugin name.


To use a different keyword than the plugin's name, return a string from the check function:

{
insertText: {
check(app) {
return "keyword";
},
run(app) {
return "hello world";
}
}
}



linklinkOption

Adds a button to the Rich Footnote popup, shown when the cursor is in a specific link.

Arguments

link link object describing the link the action was triggered from

Returns

Nothing

{
linkOption(app, link) {
const newDescription = (link.description || "") + "\n\nmore";
app.context.updateLink({ description: newDescription });
}
}



linknoteOption

Adds options to the per-note menu, shown when editing a specific note.

Arguments

noteUUID the UUID of the note that the menu option was invoked in

Returns

Nothing

{
noteOption(app, noteUUID) {
app.alert(noteUUID);
}
}



linkreplaceText

Called to replace some highlighted text, invoked via the selection menu.

Arguments

text the String of selected text

Returns

String new text to replace the selected text with

null to cancel the replacement. Note that the replacement will also be cancelled if the user changes the selection before this action returns a value.

{
replaceText(app, text) {
return "new text";
}
}




To use a different keyword than the plugin's name, return a string from the check function:

{
replaceText: {
check(app, text) {
return "some text";
},
run(app, text) {
return "new text";
}
}
}

This code will result in the returned check function text being displayed to the user:




linkApp Interface

The app interface provides the means of interacting with the application. It is passed to all plugin action functions as the first argument. All app interface functions should be considered asynchronous, returning a Promise that will either resolve to the result, or reject if there is an error.


linkapp.addNoteTag

Add a tag to a note.

Arguments

noteHandle identifying the note to add the tag to

String text of the tag to add - note that this will be normalized to conform to allowed tag names (lowercase, dashes), if it is not a valid tag name

Returns

boolean indicating whether the tag was added. In some cases, shared tags cannot be added to notes - and this will return false.

Throws

If the given tag argument is not a string

{
async noteOption(app, noteUUID) {
const added = await app.addNoteTag({ uuid: noteUUID }, "some-tag");
app.alert(added ? "Tag added" : "Failed to add tag");
}
}


linkapp.alert

Show the user a message. The name of the plugin is shown in the title of the dialog. Similar to app.prompt, except that this doesn't offer inputs, and instead offers "actions", which are buttons the user can pick at the bottom of the notification.

Arguments

message the String to show the user. Use "\n" to output new lines. Unicode characters are supported. Markdown is not (yet).

(optional) object describing additional options, containing any of the following properties:

actions optional Array of action objects that will be added as buttons on the dialog. Each action object can have the following properties:

icon optional String name of a Material Icon to show on the button

label the String text to show on the button

value optional value (of any basic JS type) that will be returned when the action is triggered, instead of the index in the actions array

preface a String to show before the main message

primaryAction an object describing the presentation of the rightmost button on the dialog (the "DONE" button), with the following properties:

icon optional String name of a Material Icon to show on the button

label the String text to show on the button

scrollToEnd a boolean indicating whether the message shown to the user should be scrolled down so the end is visible, if it is long enough that the alert dialog has a scrollbar

Returns

null if the user dismisses the dialog

-1 if the user presses the "DONE" button (or the primaryAction button, if supplied)

If options.actions is provided:

The integer index corresponding to the action the user selected, or - if the selected action includes a value key, the value associated with the value key.


{
noteOption(app, noteUUID) {
app.alert("This is an alert");
}
}



{
async noteOption(app, noteUUID) {
const actionIndex = await app.alert("This is an alert", {
actions: [
{ icon: "post_add", label: "Insert in note" }
]
});
 
if (actionIndex === 0) {
// "Insert in note"
}
}
}



linkapp.attachNoteMedia

Upload a media file, associating it with a the specified note. This function uploads the file directly, so the user must be online for it to work, and it may take a long time, depending on the size of the media and connectivity.

Arguments

noteHandle describing the note to attach the media to

dataURL a data URL describing the media file data

Returns

String URL of uploaded media

Throws

If the media file is too large, or otherwise not allowed

If there are network errors that prevent upload

{
async insertText(app) {
const noteHandle = { uuid: app.context.noteUUID };
 
const response = await fetch("https://source.unsplash.com/featured/300x200");
const blob = await response.blob();
const dataURL = await this._dataURLFromBlob(blob);
 
const fileURL = await app.attachNoteMedia(noteHandle, dataURL);
 
app.context.replaceSelection(`![](${ fileURL })`);
return null;
},
 
_dataURLFromBlob(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
 
reader.onload = event => {
resolve(event.target.result);
};
 
reader.onerror = function(event) {
reader.abort();
reject(event.target.error);
};
 
reader.readAsDataURL(blob);
});
}
}


linkapp.context

Provides details about where the plugin action was invoked, and allows for interaction at that location.


linkapp.context.link

link object describing properties of the link the plugin action was invoked from, e.g. for insertText or replaceText actions. Will be undefined if the plugin action was not invoked from a context where the selection is in a link.


linkapp.context.noteUUID

The String UUID of the note the plugin action was invoked from. This will include the note UUID that a task is in when invoking an action (e.g. insertText or replaceText) in the tasks view, or other editable task lists.


linkapp.context.replaceSelection

Replaces the selection with markdown content. This function will not be present for plugin actions that are not invoked with a user selection/cursor placed in a note - i.e. it is only defined for insertText and replaceText plugin actions. Note that the user can navigate away from a note while a plugin action is executing, in which case, calling this function is not guaranteed to do anything.

Arguments

String of markdown content to replace the selection with

Returns

boolean indicating if the given markdown content replaced the selection. Returns false if the selection has been completely removed from the note, or if the markdown content can't be inserted at the current location (e.g. a table in a task).

Throws

If the context in which the selection existed is no longer available, e.g. the note is completely closed

{
async insertText(app) {
const replacedSelection = await app.context.replaceSelection("**new content**");
if (replacedSelection) {
return null;
} else {
return "plain text"; // Fall back in cases where the markdown content wouldn't be valid at the selection position
}
}
}


linkapp.context.selectionContent

When invoked from an editor context, the markdown representation of the content that is currently selected.


linkapp.context.taskUUID

If the plugin action was invoked from a position in a task, this will be the String UUID of the task in question.

{
insertText(app) {
return app.context.taskUUID || "not in a task";
}
}


linkapp.context.updateImage

If the plugin action was invoked on an image (i.e. imageOption actions), can be called to update image properties.

{
async imageOption(app, image) {
await app.context.updateImage({ caption: "and " + image.caption.toString() });
}
}


linkapp.context.updateLink

If the plugin action was invoked in a link, can be called to update link properties.

{
async replaceText(app) {
if (app.context.updateLink) {
const newDescription = (app.context.link.description || "") + "\nmore";
app.context.updateLink({ description: newDescription });
}
 
return null;
}
}


linkapp.createNote

Create a new note, optionally specifying a name and/or tags to apply.

Arguments

name - optional String name to give the new note

tags - optional Array of String tag names to apply to the new note

Returns

uuid of the newly created note. This is typically a local-prefixed UUID that may change once persisted to the remote servers, but can continue to be used on the same client to identify the note. Calling findNote with this uuid will return a noteHandle with a non-local-prefixed UUID if the note has since completed persistence.

{
async noteOption(app, noteUUID) {
const uuid = await app.createNote("some new note", [ "some-tag" ]);
app.alert(uuid);
}
}


linkapp.deleteNote

Delete a note. Users can restore deleted notes for 30 days after they are first deleted.

Arguments

noteHandle describing the note to delete

Returns

Boolean indicating whether the note described by the noteHandle exists such that it can be deleted.

{
async noteOption(app, noteUUID) {
await app.deleteNote({ uuid: noteUUID });
}
}


linkapp.filterNotes

Find noteHandles for all notes matching a set of filter criteria.

Arguments

(optional) object describing filter parameters, containing any of the following properties:

group - filter group to apply. This corresponds to the group= query string parameter when viewing https://www.amplenote.com/notes and filtering on a specific group or set of groups. Multiple groups can be specified with a , separator.

query - String fuzzy search term to filter matching notes on. Note that this is not full-text search, and matches the behavior of not suggestion UI that matches on note names.

tag - tag filter to apply. This corresponds to the tag= query string parameter when viewing https://www.amplenote.com/notes and filtering on a specific tag or set of tags. Multiple tags can be specified with a , separator - matching notes must have all specified tags. A tag prefixed with ^ will only match notes that do not have the tag.

Examples

{ tag: "daily-jots" }

{ tag: "daily-jots,todo" } - matches notes that have the daily-jots tag and the todo tag.

{ tag: "daily-jots,^todo/next" } - matches notes that have the daily-jots tag and do not have the todo/next tag.

Returns

An array of noteHandles for all notes that match the filter parameters.

{
async insertText(app) {
const noteHandles = await app.filterNotes({ tag: "daily-jots" });
return `note count: ${ noteHandles.length }`;
}
}


linkapp.findNote

Finds the noteHandle of a note, if the note is extant and not marked as deleted. In addition to verifying whether a note exists, this can be used to fill in some additional details for a note, e.g. if the plugin only has a noteUUID it can call this to get the name and tags applied to the note.

Arguments

object identifying the note to find, with the following properties:

uuid the UUID identifying a specific note, if provided, will be used regardless of other properties

name String name of the note to find. If uuid is not provided, this must be supplied.

tags optional Array of tag filter Strings that the note must match, in addition to the name. Each array entry can be the name of a tag e.g. [ "some-tag" ] or can include a negation operator to only match notes that don't have that tag, e.g. [ "^not-this-tag" ]

Returns

noteHandle of the note, or null if the note does not exist or has been marked as deleted

{
async noteOption(app, noteUUID) {
const noteHandle = await app.findNote({ uuid: noteUUID });
app.alert(noteHandle.name);
}
}


linkapp.getNoteBacklinks

Returns the list of notes that link to the specified note.

Arguments

noteHandle identifying the note

Returns

Array of noteHandles identifying the notes that have links to the specified note

{
async noteOption(app, noteUUID) {
let count = 0;
for await (const referencingNoteHandle of app.getNoteBacklinks({ uuid: noteUUID })) {
await app.alert("referencingNoteHandle: " + JSON.stringify(referencingNoteHandle));
count++;
}
await app.alert("count: " + count);
}
}


linkapp.getNoteContent

Get the content of a note, as markdown.

Arguments

noteHandle identifying the note

Returns

The content of the note, as markdown.

{
async noteOption(app, noteUUID) {
const markdown = await app.getNoteContent({ uuid: noteUUID });
app.alert(markdown);
}
}


linkapp.getNoteImages

Get all the inline images in a note (does not include images in Rich Footnotes).

Arguments

noteHandle identifying the note

Returns

Array of image objects

{
async noteOption(app, noteUUID) {
const images = await app.getNoteImages({ uuid: noteUUID });
await app.alert("image count: " + images.length);
}
}


linkapp.getNotePublicURL

Get a public URL for the note, if it has been published. Note that this call requires internet connectivity.

Arguments

noteHandle identifying the note

Returns

String URL for the published note, or null if the note is not published

Throws

If a request to the server fails, e.g. if the client is offline

{
async noteOption(app, noteUUID) {
const publicURL = await app.getNotePublicURL({ uuid: noteUUID });
await app.alert("public URL: " + (publicURL || "none"));
}
}


linkapp.getNoteSections

Gets a list of the sections in a note. Sections are areas of the note delimited by either a heading or a horizontal rule. Sections are identified by the heading (if any) that opens the section, and when relevant, an index to disambiguate between multiple sections with matching headings.

Arguments

noteHandle identifying the note

Returns

An Array of the sections the note is comprised of

{
async noteOption(app, noteUUID) {
const sections = await app.getNoteSections({ uuid: noteUUID });
app.alert("Section count: " + sections);
}
}


linkapp.getNoteTasks

Returns the tasks that are present in the specified note.

Arguments

noteHandle identifying the note to get tasks from. If the note handle identifies a note that does not yet exist, the note will not be created.

options object, with the following optional properties:

includeDone - boolean indicating whether completed and dismissed tasks in the note should be returned in addition to the un-done tasks. Defaults to false.

Returns

Array of task objects

{
async noteOption(app, noteUUID) {
const tasks = await app.getNoteTasks({ uuid: noteUUID });
app.alert(`Note has ${ tasks.length } tasks`);
}
}


linkapp.getNoteURL

Returns a full URL for the specified note. This URL can be used to link to the note (and will be detected as a note link in Amplenote editors/views), and can be used to open the note via app.navigate.

Arguments

noteHandleidentifying the note to get the URL of. If the note handle is for a note that does not yet exist, the note will be created.

Returns

String URL of the note.

{
async noteOption(app, noteUUID) {
const noteURL = await app.getNoteURL({ uuid: noteUUID });
app.alert("note URL: " + noteURL);
}
}


linkapp.getTask

Get the details of a single task.

Arguments

UUID String identifying the task

Returns

task object, or null if no task with the given UUID exists

{
async noteOption(app, noteUUID) {
const taskUUID = "e6adcbcb-04dc-4fbd-857e-dbf63a253e7e";
const task = await app.getTask(taskUUID);
app.alert(JSON.stringify(task));
}
}


linkapp.getTaskDomains

Get the list of configured Task Domains for the user.

Arguments

None

Returns

Array of task domains, each entry an object with the following properties:

name the String display name of the Task Domain

notes an Array of noteHandles, for each note in the Task Domain. This includes notes that are part of the Task Domain due to the tags applied to the note, notes that have been individually specified to be part of the Task Domain, and - for legacy Task Domains - potentially all notes.

uuid the String identifier that uniquely identifies the Task Domain


{
appOption: async function(app) {
const taskDomains = await app.getTaskDomains();
 
const descriptions = taskDomains.map(({ name, notes }) => {
return `${ name } - ${ notes.length } notes`);
});
app.alert(descriptions.join("\n"));
},
}


linkapp.getTaskDomainTasks

Gets the list of tasks that belong to the given task domain. Note that this includes tasks that are not scheduled (on the calendar).

Arguments

String task domain UUID

Returns

Array of task objects describing the tasks in the task domain


Note that this function can return a large amount of data, so it's highly recommended to use an async iterator to reduce the potential for performance impact.

{
async appOption(app) {
const taskDomains = await app.getTaskDomains();
const taskDomainUUID = taskDomains[0].uuid;
 
for await (const task of app.getTaskDomainTasks(taskDomainUUID)) {
console.log("task: " + task.uuid);
}
}
}


linkapp.insertNoteContent

Inserts content into a note.

Arguments

noteHandle identifying the note to insert the text into

content: String of markdown-formatted content to insert

(optional) object of additional options, with the following properties:

atEnd boolean indicating that content should be inserted at the end of the note. Defaults to false.

Returns

Nothing

Throws

If markdown content is over 100k characters

If the target note is readonly

{
noteOption(app, noteUUID) {
app.insertNoteContent({ uuid: noteUUID }, "this is some **bold** text");
}
}


linkapp.insertTask

Inserts a new task at the beginning of a note. See also: note.insertTask.

Arguments

noteHandle identifying the note to insert the task into

task object, with the following attributes (all are optional):

content: String of markdown-formatted content to use in the task

hideUntil: Number a unix timestamp (seconds) to use for the "Hide until" time

startAt: Number a unix timestamp (seconds) to use for the "Start at" time

Returns

The UUID of the newly created task

Throws

If the provided content is not valid in a task (e.g. - a bullet list item)

If note is readonly/locked

{
async noteOption(app, noteUUID) {
const taskUUID = await app.insertTask({ uuid: noteUUID }, { text: "this is a task" });
app.alert(taskUUID);
}
}

link

linkapp.navigate

Opens the app to the location corresponding tot he given Amplenote app URL. Amplenote app URLs start with https://www.amplenote.com/notes. Examples:

Jots area: "https://www.amplenote.com/notes/jots"

Notes area: "https://www.amplenote.com/notes"

Notes list filtered to a tag: "https://www.amplenote.com/notes?tag=some-tag"

A specific note: "https://www.amplenote.com/notes/NOTE_UUID" (replacing NOTE_UUID with a specific note's UUID).


Arguments

url an Amplenote URL string

Returns

true if the given url was a valid Amplenote URL and was navigated to, false otherwise

{
noteOption(app) {
app.navigate("https://www.amplenote.com/notes/jots");
}
}


linkapp.notes

The notes object provides an alternative - and simpler - way to interact with specific notes. Depending on the purpose, it may be preferable than the noteHandle-based functions available on the main app interface. Functions on the notes object return Note interface objects. As with noteHandles, a note interface object may represent a note that does not (yet) exist. Calling any note interface function that requires an extant note will create the note first, if it doesn't already exist.


linkapp.notes.create

Create a new note. This is an alternative interface to app.createNote.

Arguments

name the String to use as the new note's name

tags an Array of String tag names to apply to the new note

Returns

Note interface object for the newly created note

{
async noteOption(app, noteUUID) {
const note = await app.notes.create("some new note", [ "some-tag" ]);
app.alert(note.uuid);
}
}


linkapp.notes.dailyJot

Gets a note interface for the daily jot note on the day corresponding to the given timestamp.

Arguments

timestamp unix timestamp Number (seconds) indicating any time on the day the daily jot note should be for

Returns

Note interface object for the daily jot note

{
async noteOption(app, noteUUID) {
const tomorrowTimestamp = Math.floor(Date.now() / 1000) + 60 * 60 * 24;
const note = await app.notes.dailyJot(tomorrowTimestamp);
app.alert(note.name);
}
}


linkapp.notes.filter

Find noteHandles for all notes matching a set of filter criteria. This is an alternative interface to app.filterNotes.

Arguments

(optional) object describing filter parameters, containing any of the following properties:

group - filter group to apply. This corresponds to the group= query string parameter when viewing https://www.amplenote.com/notes and filtering on a specific group or set of groups. Multiple groups can be specified with a , separator.

Examples

{ group: "archived" }

{ group: "taskList,archived" }

tag - tag filter to apply. This corresponds to the tag= query string parameter when viewing https://www.amplenote.com/notes and filtering on a specific tag or set of tags. Multiple tags can be specified with a , separator - matching notes must have all specified tags. A tag prefixed with ^ will only match notes that do not have the tag.

Examples

{ tag: "daily-jots" }

{ tag: "daily-jots,todo" } - matches notes that have the daily-jots tag and the todo tag.

{ tag: "daily-jots,^todo/next" } - matches notes that have the daily-jots tag and do not have the todo/next tag.

Returns

An array of noteHandles for all notes that match the filter parameters.

{
async insertText(app) {
const noteHandles = await app.notes.filter({ tag: "daily-jots" });
return `note count: ${ noteHandles.length }`;
}
}


linkapp.notes.find

Find a specific note. This is an alternative interface to app.findNote.

Arguments

Either one of

uuid the String UUID identifying the note

noteHandle identifying the note

Returns

Note interface object for the note, or null if the note does not exist or has been marked as deleted

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
app.alert(note.name);
}
}


{
async noteOption(app) {
const note = await app.notes.find({ name: "some note" });
app.alert(note ? "Note exists" : "Note doesn't exist");
}
}


linkapp.prompt

Show the user a message and input fields - defaulting to a single text input - receiving the user-selected/user-entered value(s). Similar to app.alert, except that this allows options to be presented in a dialog, and does not allow multiple buttons at the bottom of the window, like app.alert does.

Arguments

message String to show the user. New lines can be inserted with \n. Unicode characters are supported in output. Markdown isn't supported...yet.

(optional) object describing additional options, containing any of the following properties:

inputs an optional Array of input objects describing the input fields to show. If not provided, a single text input field will be shown (equivalent to inputs: [ { type: "text" } ]).

input object, with the following properties:

label a String to use as a label for the input. Examples: checkbox, select, and text

limit a Number to use as the maximum number of tags that can be selected in a type: "tags" input. If omitted, defaults to 1

options Array of options to use in a type: "select" drop-down input or type: "radio" input each option an object with the following properties:

label the String to show as a label on the input field

value the value corresponding to the option. Will be returned as the result (verbatim) when the user selects the option.

image a String URL of an image to show with the option. Only used in type: "radio" inputs.

placeholder a String to use as a placeholder value for a type: "text" input

type String one of the following values:

"checkbox" a check box field

"note" a field to select a single note from the user's notes list

"radio" a set of radio buttons to select one option from. When specified, the options property should also be provided.

"secureText" a short text field that masks the displayed characters like a password field.

"select" a drop-down select field. When specified, the options property should also be provided.

"string" a single line text input field

"tags" an auto-complete field for the user's tags, in which a single tag can be selected by default, or more can be selected by providing a limit value.

"text" a multi-line text area field

value the initial value for the input. If the user does not change the input, submitting the prompt will use this value. Note that the value must match the value field in one of the options for type: "radio" and type: "select" inputs, or it will be ignored.

actions optional Array of action objects that will be added as buttons on the dialog. Each action object can have the following properties:

icon optional String name of a Material Icon to show on the button

label the String text to show on the button

value optional value (of any basic JS type) that will be returned when the action is triggered, instead of the index in the actions array

Returns

null if the user selected "Cancel", or otherwise closed the dialog without pressing "Submit"

If no inputs or actions options are provided

The String text the user entered

If a single inputs option and no actions are provided, one of:

The String text the user entered for a type: "text" or type: "string" input

The String tag text the user selected for a type: "tags" input. If multiple tags are selected, they will be joined with a , to produce a single string.

The value corresponding to the option.value the user selected in a type: "radio" input

The Bool value corresponding to the user's selection for a type: "checkbox" input

The noteHandle of the selected note for a type: "note" input

The value corresponding to the value field of the selected type: "select" option

If multiple inputs are provided, an Array of values corresponding to value selected for each input (in the same order they were specified), followed by a single entry corresponding to the actions entry that the user selected, or -1 if the user pressed the default "Submit" button.

{
insertText(app) {
return app.prompt("Enter text").then(function(text) {
return text;
});
}
}




{
async noteOption(app, noteUUID) {
const result = await app.prompt("This is the message", {
inputs: [
{ label: "This is the label", placeholder: "This is the placeholder", type: "text" },
{ label: "This is the label", type: "checkbox" },
{ label: "This is the label", type: "select", options: [ { label: "something", value: 1 }, { label: "other", value: 2 } ] },
{ label: "This is the label", type: "radio", options: [ { label: "something", value: 1 }, { label: "other", value: 2 } ] },
]
});
 
if (result) {
const [ textResult, checkboxResult, selectResult ] = result;
 
} else {
// User canceled
}
}
}




{
async noteOption(app, noteUUID) {
const result = await app.prompt("This is the message", {
actions: [
{ label: "Alternative action", value: "alternative" },
{ label: "Another action" },
],
inputs: [
{ label: "This is the label", type: "text" },
{ label: "This is the label", type: "checkbox" },
]
});
 
if (result) {
const [ textResult, checkboxResult, actionResult ] = result; // `result` will be `null` if user presses "Cancel" button or dismisses popup
 
// actionResult === "alternative" // if "Alternative action" was pressed
// actionResult === 1 // if "Another action" was pressed (the index of this action in the `actions` array)
// actionResult === -1 // If the default "Submit" button was pressed
 
} else {
// User canceled
}
}
}




linkapp.removeNoteTag

Removes a tag from a note.

Arguments

noteHandle identifying the note to add the tag to

String text of the tag to remove

Returns

boolean indicating whether the tag was removed. In some cases, shared tags cannot be removed from notes - and this will return false. Note that this will return true even if the note did not have the tag - only a failure to remove a tag will result in false.

Throws

If the given tag argument is not a string

{
async noteOption(app, noteUUID) {
const removed = await app.removeNoteTag({ uuid: noteUUID }, "some-tag");
app.alert(added ? "Tag removed" : "Failed to remove tag");
}
}


linkapp.replaceNoteContent

Replace the entire content of a note with new content, or replace the content of a single section of the note (see app.getNoteSectionsfor discussion of sections).

Arguments

noteHandle describing the note to replace content in

Markdown content String of new content

(optional) object of additional options, supporting the following properties

section object describing the section of the note to replace content in. For sections that start with a heading, the heading will not be replaced, only the content of the section.

Returns

bool indicating whether the replacement was performed. Note that the only failure state for a replacement is when a section is specified and that section is not found in the note.

Throws

If markdown content is over 100k characters

If the target note is readonly

{
async noteOption(app, noteUUID) {
const newContent = "**new content**";
await app.replaceNoteContent({ uuid: noteUUID }, content);
}
}


With a section:

{
async noteOption(app, noteUUID) {
const newContent = "**new content**";
const section = { heading: { text: "Heading of Section to Replace" }};
await app.replaceNoteContent({ uuid: noteUUID }, content, { section });
}
}


linkapp.saveFile

Save a file.

Arguments

The Blob/ File object describing the content of the file

String filename to use for the file

Returns

A promise that resolves when the request to save the file has been sent. Note that the user may see a prompt to accept/name/save the file, in which case this will resolve as soon as the request has been displayed to the user - it does not wait for the user to actually save the file.

{
async noteOption(app) {
const file = new Blob([ "some text" ], { type: "text/plain" });
await app.saveFile(file, "test.txt");
}
}


linkapp.setNoteName

Sets a new name for the given note.

Arguments

noteHandle describing the note to set the name of

String new name for the note

Returns

Boolean indicating whether the name could be changed. Generally, the only failure case is if the note handle doesn't identify an extant note.

Throws

If the name argument is not a string

{
async noteOption(app, noteUUID) {
const noteHandle = { uuid: noteUUID };
const note = await app.findNote(noteHandle);
await app.setNoteName(noteHandle, note.name + " more");
}
}


linkapp.setSetting

Update the value of a single setting. The value will be synchronized to all of the user's devices. Note that the updated value is not guaranteed to be updated in app.settings before the next invocation of a plugin function (check or run).

Arguments

String setting name to update, or add

String new value for the setting. Any non-string values (with the exception of null) will be converted to strings.

Returns

Nothing

{
async appOption(app) {
const count = parseInt(app.settings["counter"] || "0", 10);
await app.setSetting("counter", count + 1);
app.alert("new value: " + count);
}
}


linkapp.settings

An object containing the user-configured settings for the plugin. All values will be strings.


{
insertText(app) {
return app.settings["Some setting"];
}
}


This will insert the string the user has entered for "Some setting", assuming the plugin metadata table includes:

setting

Some setting


linkapp.updateNoteImage

Update an image in a specific note.

Arguments

noteHandle identifying the note to update an image in

image identifying the image to update - only the index and src keys are necessary

object describing the updates to the image, which can contain the properties described in by image, with the exception of the index property

Returns

boolean indicating whether the image could be updated

Throws

If the given markdown is not valid in an image caption


{
async noteOption(app, noteUUID) {
const noteHandle = { uuid: noteUUID };
const images = await app.getNoteImages(noteHandle);
 
for (let i = 0; i < images.length; i++) {
const image = images[i];
await app.updateNoteImage(noteHandle, image, { caption: "**new caption**" });
}
}
}


linkapp.updateTask

Update the properties or content of a single task.

Arguments

String UUID identifying the task

object containing (optional) updates to apply to the task. All properties listed for a task are supported, except the uuid

Returns

boolean indicating whether the task could be updated. If the given task UUID doesn't correspond to any existing task, will return false

{
async insertText(app) {
const taskUUID = app.context.taskUUID;
if (!taskUUID) return null; // Cursor is not in a task
 
await app.updateTask(taskUUID, { completedAt: Math.floor(Date.now() / 1000) });
 
return "";
}
}




linkNote interface

The note interface provides a more convenient way to interact with the app interface functions that operate on a specific noteHandle. Note that - like a noteHandle - the note described by the note interface may or may not exist. Calling functions that modify the note content will create the note if it doesn't already exist.


linknote.addTag

Add a tag to the note. See app.addNoteTag for details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
const added = await note.addTag("some-tag");
app.alert(added ? "Tag added" : "Failed to add tag");
}
}


linknote.attachMedia

Attach a media file (image or video) to the note. See app.attachNoteMedia for more details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
// 1x1 gray PNG (from http://png-pixel.com)
const dataURL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mPcsv/MfwAHrgNAOkBd9gAAAABJRU5ErkJggg==";
const imageURL = await note.attachMedia(dataURL);
app.alert(`ImageURL: ` + imageURL);
}
}


linknote.backlinks

Gets the list of notes that link to the note. See app.getNoteBacklinks for details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
 
let count = 0;
for await (const referencingNoteHandle of note.backlinks()) {
count++;
}
await app.alert("backlinks count: " + count);
}
}


linknote.content

Get the content of the note, as markdown. See app.getNoteContent for more details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
app.alert(await note.content());
}
}


linknote.delete

Delete the note. See app.deleteNote for more details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
await note.delete();
}
}


linknote.images

Get all inline images in the note. See app.getNoteImages for more details..

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
const images = await note.images();
await app.alert("image count: " + images.length);
}
}


linknote.insertContent

Inserts content at the beginning of a note. See app.insertNoteContent for more details.

{
noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
note.insertContent("this is some **bold** text");
}
}


linknote.insertTask

Inserts a new task at the beginning of a note. See app.insertTask for more details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
const taskUUID = await note.insertTask({ content: "this is a task" });
app.alert(taskUUID);
}
}


linknote.name (title)

Get the title of the note.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
app.alert(`This note is named ${ note.name }`);
}
}


linknote.publicURL

Get a link to the published version of a note, if the note is published. See app.getNotePublicURL for details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
 
const publicURL = await note.publicURL();
await app.alert("public URL: " + (publicURL || "none"));
}
}


linknote.removeTag

Remove a tag from the note. See app.removeNoteTag for details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
const removed = await note.removeTag("some-tag");
app.alert(removed ? "Tag removed" : "Failed to remove tag");
}
}


linknote.replaceContent

Replaces the content of the entire note, or a section of the note, with new content. See app.replaceNoteContent for more details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
await note.replaceContent("**new content**");
}
}


linknote.sections

Gets the sections in the note. See app.getNoteSections for more details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
const sections = await note.sections();
app.alert("Section count: " + sections);
}
}


linknote.setName

Sets a new name for the note. See app.setNoteName for more details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
await note.setName(note.name + " more");
}
}


linknote.tags

Returns an Array of tags that are applied to the note.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
const tags = note.tags;
app.alert(`Note has ${ tags.length } tags: ${ tags.join(",") }`);
}
}


linknote.tasks

Gets the tasks in the note. See app.getNoteTasks for more details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
const tasks = await note.tasks();
app.alert(`Note has ${ tasks.length } tasks`);
}
}


linknote.updateImage

Update a specific image in the note. See app.context.updateImage for more details.


{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
const images = await note.images();
 
for (let i = 0; i < images.length; i++) {
const image = images[i];
await note.updateImage(image, { caption: "**new caption**" });
}
}
}


linknote.url

Returns the String URL of the note. See app.getNoteURL for more details.

{
async noteOption(app, noteUUID) {
const note = await app.notes.find(noteUUID);
const noteURL = await note.url();
app.alert(`Note URL: ${ noteURL }`);
}
}


linkAppendix I: Types


linkimage

Describes an image displayed inline in a note. image objects can have the following properties:

caption markdown String describing the content of the image caption

index when there are multiple images in a note with the same src value, the index can be used to disambiguate between the images. This property can not be updated.

src the String source URL of the image

text the String of OCR text recognized in the image


linklink

Describes a link, which may include Rich Footnote content. link objects have the following properties:

description markdown String describing the content of the link's Rich Footnote content. Note that the markdown content supported in Rich Footnotes is a subset of the markdown content supported in notes themselves.

href the String that the link is set to link to

Any of these properties may be null, if the link does not have the corresponding value set.


linknoteHandle

Some app interface functions take a noteHandle argument. Note handles are objects that can identify notes even if they do not yet exist (e.g. future daily jot notes). Calling an app interface function with the noteHandle of a note that does not yet exist is likely to create that note, for example when inserting content into the note.


An individual note that exists in identified by a uuid - however there are some cases where a note may not yet have been acknowledged by the server (e.g. in an extended period of offline usage), in which case the note's uuid may be prefixed with the string "local-" and may change once the note is fully persisted to the Amplenote servers. The local-prefixed uuid can continue to be used on the same client that created the note, even after the note has been fully persisted.


linksection

Describes a chunk of a note, where each note can be broken up into multiple sections, encompassing entirety of the note's content. A note is divided into sections by using headings and horizontal rules. The first part of a note before any headings appear is considered a section (that has no heading), while content after a horizontal rule is also considered a separate section (also with no heading).


Section objects have the following properties:

heading - will be null if the section does not start with a heading (e.g. if this is the first part of the note before any headings, or a section after a horizontal rule); otherwise, will be an object describing the heading, with the following properties:

anchor - the String anchor that can be used to navigate to the heading in the note

href - a String URL that is linked to from the heading - note only a link at the start of the heading content will be considered - links elsewhere in the heading are ignored.

level - the integer level of the heading (i.e. for H1-H3)

text - the String text of the heading, without any formatting that may be applied (bold/italic/etc)

index - if there are multiple sections with headings that otherwise match (either because they are all heading: null or the text of the heading is the same), sections after the first section will include this integer index of the heading.


Examples:



[
{
"heading": null
},
{
"heading": {
"anchor": "Heading_1",
"level": 1,
"text": "Heading 1"
}
}
]




[
{
"heading": null
},
{
"heading": null,
"index": 1
},
{
"heading": {
"anchor": "Heading_1",
"level": 1,
"text": "Heading 1"
}
},
{
"heading": {
"anchor": "Heading_2",
"level": 2,
"text": "Heading 2"
}
},
{
"heading": null,
"index": 2
},
{
"heading": {
"anchor": "Heading_3",
"level": 2,
"text": "Heading 3"
}
}
]


linktask

Some app interface functions return - or accept - task objects that can have the following properties:

completedAt integer unix (seconds) timestamp describing the (UTC) time at which the task was completed - only present if the task has been completed.

content markdown String describing the content of the task

dismissedAt integer unix (seconds) timestamp describing the (UTC) time at which the task was dismissed - only present if the task has been dismissed.

endAt integer unix (seconds) timestamp describing the (UTC) time at which the task should end. When updating a task, must be after the startAt time of the task - and can not be set for a task that does not have a startAt time set (or provided in the same update call). The seconds between the startAt and endAt times define the duration of the task.

hideUntil integer unix (seconds) timestamp describing the (UTC) time that the task will be hidden until, or null if the task is not hidden.

noteUUID String uuid identifying the note that the task resides in. Passing this attribute to app interface functions that receive task objects will not have any effect.

score Number corresponding to the total task score attributed to the task at the current moment. Note that this accumulates once daily, depending on interaction with the task and/or note the task is in.

startAt integer unix (seconds) timestamp describing the (UTC) time that the task starts at, or null if the task does not have a start time set.

uuid String UUID uniquely identifying this task.



linkAppendix II: Plugin code execution environment

Plugin code is executed in a sandboxed iFrame, preventing direct access to the outer page/application. In the native mobile applications, plugin iFrames are loaded inside an isolated (hidden) WebView, with each plugin iFrame providing isolation from any other plugins that may be loaded.


Plugin code is executed in the user's browser on web and in the system WebView on mobile, so it's worth keeping browser compatibility in mind. There are no polyfills applied in the plugin code sandbox, nor is any processing performed on plugin code.



linkAppendix III: Markdown content

Markdown content that is passed to plugins - e.g. via app.getNoteContent - matches the feature set of converting a note to markdown (via per-note menu option or export to markdown). Generally, markdown support follows the GitHub Flavored Markdown Spec.


Note that the markdown passed by plugins to the app is limited as of July 2023, but will continue to be expanded to eventually support all Amplenote features.



linkAppendix IV: Loading external libraries

The plugin execution context is isolated so it can loosen restrictions the application imposes on loading external resources. There are a couple patterns that can be used in plugin code to load external dependencies, depending on how the dependency is packaged.


linkLoading browser builds

The simplest dependency to load is a library that has a browser build. This is a build that can normally be used in a <script> tag - in plugin code a script tag can be appended to the document to load the dependency.


For example, to load the RecordRTC library's browser build:

_loadRecordRTC() {
if (this._haveLoadedRecordRTC) return Promise.resolve(true);
 
return new Promise(function(resolve) {
const script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", "https://www.WebRTC-Experiment.com/RecordRTC.js");
script.addEventListener("load", function() {
this._haveLoadedRecordRTC = true;
resolve(true);
});
document.body.appendChild(script);
});
}

A couple things to note in the above code:

The plugin execution context is kept loaded, so the dependency doesn't need to be loaded on every single call to the plugin - only the first call.

The library in question - RecordRTC - will be available as window.RecordRTC because it is packaged up as a browser build.

This can be used in the plugin's actions as:

async insertText(app) {
await this._loadRecordRTC();
 
// ... remaining plugin code can reference `window.RecordRTC`
}


linkLoading UMD builds

Dependencies that define UMD modules can be loaded with a small code shim:

async _loadUMD(url, module = { exports: {} }) {
const response = await fetch(url);
const script = await response.text();
const func = Function("module", "exports", script);
func.call(module, module, module.exports);
return module.exports;
}


Given a dependency that provides a UMD build, the above helper can be used as:

async imageOption(app, image) {
const metaPNG = await this._loadUMD("https://www.unpkg.com/meta-png@1.0.6/dist/meta-png.umd.js");
 
// ... remaining plugin code can use `metaPNG.*`
}



linkAppendix V: Update history

Unreleased (mobile app version 3.92)

March 10th, 2024 (mobile app version 3.90)

February 20th, 2024 (mobile app version 3.90)

February 8th, 2024 (mobile app version 3.89)

Add limit option for app.prompt "tags" input type

January 19th, 2024 (mobile app version 3.88)

January 12th, 2024 (mobile app version 3.88)

check functions for replaceText actions can return a string that will be used as the replacement text, instead of the name of the plugin.

app.alert actions entries can include a value that will be returned if the action is selected, instead of the index of the action in the actions array

Add support for actions in app.prompt

Add "string" input type for app.prompt

2023

Dec 2023 (mobile app version 3.87)

check functions for insertText actions can return a string that will be used as the replacement text, instead of the name of the plugin. Example

check functions for dailyJotOption actions can return a string that will be used as the text on the button instead of "Run".

Add "tags" app.prompt input type

Add options.primaryAction to app.alert

Dec 1st, 2023 (mobile app version 3.86)

Add section.heading.href field

Add "secureText" app.prompt input type

November 17th, 2023 (mobile app version 3.86)

November 8th, 2023 (mobile app version 3.84)

October 23rd, 2023 (mobile app version 3.83)

Add task.endAt

October 13th, 2023 (mobile app version 3.83)

Add task.repeat

September 29th, 2023 (mobile app version 3.83)

Add scrollToEnd option to app.alert

September 22nd, 2023 (mobile app version 3.81)

Add app.getNoteURL and note.url

September 18th, 2023 (mobile app version 3.80)

Add linkOption action type

September 8th, 2023 (mobile app version 3.80)

Add app.getTaskDomains

September 4th, 2023 (mobile app version 3.78)

Add app.context.link and app.context.updateLink

August 28th, 2023 (mobile app version 3.78)

Add app.setSetting

July 31st, 2023 (mobile app version 3.73)

Add noteUUID to task object

June 26th, 2023 (mobile app version 3.71)

Add dailyJotOption action type

Add inputs[i].value option for app.prompt inputs.

Add query option to app.filterNotes

June 15th, 2023 (mobile app version 3.71)

Add support for plugin action check functions, to determine if the plugin should be shown as an option

June 2023 (mobile app version 3.71)

Update app.notes.find to allow either a noteHandle argument or a UUID argument

Add imageOption actions

Add app.context.updateImage

May 22nd, 2023 (mobile app version 3.70)

Add app.saveFile

Add score to task

May 12th, 2023 (mobile app version 3.70)

Add appOption action

Add app.getNoteTasks, app.updateTask, and note.tasks

Include taskUUID in app.context when insertText or replaceText actions are invoked in a task.

April 30th, 2023 (mobile app version 3.67)

Add app.attachNoteMedia

Add type: "radio" to app.prompt

April 18th, 2023 (mobile app version 3.66)

Add app.context.replaceSelection

April 12th, 2023 (mobile app version 3.66)

Rename app.insertContent to app.insertNoteContent - app.insertContent will remain as an alias to support existing plugins using it.

Add options.atEnd to app.insertNoteContent

April 11th, 2023 (mobile app version 3.66)

Add app.getNoteSections/ note.sections

Add app.replaceNoteContent / note.replaceContent

April 7th, 2023 (mobile app version 3.66)

Add { type: "note" } input type to app.prompt

April 6th, 2023 (mobile app version 3.65)

Add app.navigate

March 31st, 2023 (mobile app version 3.65)

Add app.context

Throw exception from insertTask and insertContent when target note is locked/readonly

Handle markdown tables (e.g. "|||\n|-|-|\n|table|content|") in insertContent

March 30th, 2023 (mobile app version 3.65)

Update app.alert to allow for additional action buttons

Update app.prompt to allow for various input types

Removes options.placeholder from app.prompt

March 28th, 2023

Add options.preface to app.alert

Add options.placeholder to app.prompt

March 9th, 2023

insertText changed to insertContent, handling markdown content

insertTask text attribute changed to content, handling markdown content