Action
Append to Logseq
UPDATES
6 days ago
Changelog
[1.2.2] — 2026-05-22
Fixed
- Drafts crash on send. Removed the intermediate “Sending to Logseq…” progress banner. In Drafts 52.0.1 / macOS 26.3.1 it could trip a dispatch_sync-on-owned-queue assertion when UserNotification scheduling raced with ActionKit teardown, killing the app. Progress messages now go to the script console only.
- script.complete() never called — Operation timed out warning on drafts with many child lines. Total runtime is now well under the async-script threshold.
Changed
- Single final banner. When the parent block landed but children failed, earlier versions could fire a warning and a success banner back-to-back — the same rapid-fire UserNotification pattern as above. Now exactly one final banner fires (success XOR warning).
Performance
- Batched child inserts. Children are now inserted in a single logseq.Editor.insertBatchBlock call instead of one insertBlock per line. Total Logseq HTTP round-trips per send: 2, regardless of child count (was 1 + N). A draft with 10 child lines that previously took ~3 seconds now completes near-instantly.
Compatibility
- No breaking changes. Same draft format, same target (today’s journal root), same API token / credential storage.
6 days ago
Changelog
[1.2.2] — 2026-05-22
Fixed
- Drafts crash on send. Removed the intermediate “Sending to Logseq…” progress banner. In Drafts 52.0.1 / macOS 26.3.1 it could trip a dispatch_sync-on-owned-queue assertion when UserNotification scheduling raced with ActionKit teardown, killing the app. Progress messages now go to the script console only.
- script.complete() never called — Operation timed out warning on drafts with many child lines. Total runtime is now well under the async-script threshold.
Changed
- Single final banner. When the parent block landed but children failed, earlier versions could fire a warning and a success banner back-to-back — the same rapid-fire UserNotification pattern as above. Now exactly one final banner fires (success XOR warning).
Performance
- Batched child inserts. Children are now inserted in a single logseq.Editor.insertBatchBlock call instead of one insertBlock per line. Total Logseq HTTP round-trips per send: 2, regardless of child count (was 1 + N). A draft with 10 child lines that previously took ~3 seconds now completes near-instantly.
Compatibility
- No breaking changes. Same draft format, same target (today’s journal root), same API token / credential storage.
3 months ago
This version now checks if the API is running first, posting an error instead of crashing if it is not.
4 months ago
Updated formatting of the Readme
4 months ago
Updated the documentation to make it clear that this is for Logseq DB.
4 months ago
Updated to strip existing some formatting (as described) before import.
Send to Logseq Journal - Drafts Action
Appends draft content to today’s journal page in Logseq. The first line becomes the parent block, and remaining lines become nested children.
Prerequisites
Logseq version 2.x.x (The DB version of Logseq.)
Logseq HTTP API Server must be enabled:
- Open Logseq → Settings → Features
- Enable “HTTP APIs server”
- Note your API token (or set one)
Logseq must be running when you use the action
Setting Up the Action in Drafts
Step 1: Create New Action
- Open Drafts
- Go to Actions list
- Tap + to create a new action
- Configure:
- Name: Send to Logseq Journal
- Icon: Choose an icon (e.g.,
arrow.up.doc) - Color: Choose a color
Step 2: Add Script Step
- Tap Steps
- Tap + → Advanced → Script
- Enable “Allow asynchronous execution” (critical!)
- Paste the contents of
send-to-logseq-journal.js
Step 3: Configure After Success
- Go to action settings
- Under After Success:
- Add Tag:
sent-to-logseq - Archive: Enable
- Add Tag:
Step 4: First Run - API Token
On first run, Drafts will prompt for your Logseq API token:
- Get your token from Logseq → Settings → Features → HTTP APIs server
- Enter the token when prompted
- The token is stored securely in Drafts’ credential system
Usage
- Write your content in a draft
- First line = parent block
- Additional lines = child blocks (nested under parent)
- Run the action
- Content appears in today’s Logseq journal
Example
Draft content:
## Meeting notes from project sync
- Discussed timeline for Q1
- Action items assigned to team
- Follow-up scheduled for Friday
Result in Logseq journal:
- Meeting notes from project sync
- Discussed timeline for Q1
- Action items assigned to team
- Follow-up scheduled for Friday
Content cleanup:
- Header marks (
#,##, etc.) are stripped from the first line - Bullet prefixes (
-,*,+) are stripped from child lines - Numbered lists (
1.,2., etc.) are preserved as-is
Troubleshooting
“HTTP request failed”
- Ensure Logseq is running
- Verify HTTP API server is enabled in Logseq settings
- Check that port 12315 is not blocked
“Failed to append block”
- Verify your API token is correct
- Try resetting credentials: long-press action → Manage Credentials → Forget
Wrong journal date format
The script uses Logseq’s default format: “Jan 17th, 2026”
If your Logseq uses a different journal format, edit the formatLogseqJournalDate() function in the script.
Resetting Credentials
To change your API token:
- Long-press the action
- Select Manage Credentials
- Tap Forget on “Logseq API”
- Run the action again to enter new token
Steps
-
script
// Send to Logseq Journal - Drafts Action Script // Version: 1.2.2 // // This script appends draft content to today's journal in Logseq. // First line becomes the parent block, remaining lines become children. // // Requirements: // - Logseq running with HTTP API server enabled // - API token configured in Logseq settings // - "Allow asynchronous execution" enabled in Drafts action step // // Note on banners: only one final app.displayXxxMessage() is ever fired. // Earlier versions used an intermediate displayInfoMessage() progress // banner; in Drafts 52.0.1 / macOS 26.3.1 rapid-fire UserNotification // scheduling raced with ActionKit teardown and crashed Drafts ("BUG IN // CLIENT OF LIBDISPATCH: dispatch_sync called on queue already owned by // current thread" on com.agiletortoise.Drafts5.scriptEngine). Progress // now goes to console.log only. // // Note on batching: all children are inserted in a single // logseq.Editor.insertBatchBlock call (total = 2 HTTP round-trips, // regardless of child count). Previously did 1 + N sequential round-trips, // which often tripped Drafts' async-script timeout warning on larger // drafts. // ============================================ // Configuration // ============================================ const LOGSEQ_API_URL = "http://127.0.0.1:12315/api"; // ============================================ // Helper Functions // ============================================ /** * Strip markdown header marks from text (e.g., "## Title" -> "Title") */ function stripHeaderMarks(text) { return text.replace(/^#+\s*/, ""); } /** * Strip list marker prefixes from text (e.g., "- item" -> "item") * Handles: -, *, + (but NOT numbered lists, which are preserved) */ function stripListPrefix(text) { // Strip leading whitespace, then bullet prefix (-, *, +) followed by space return text.replace(/^\s*[\-\*\+]\s+/, ""); } /** * Format date for Logseq journal page name * Logseq default format: "Jan 17th, 2026" */ function formatLogseqJournalDate(date) { const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; const day = date.getDate(); const suffix = (day === 1 || day === 21 || day === 31) ? "st" : (day === 2 || day === 22) ? "nd" : (day === 3 || day === 23) ? "rd" : "th"; return months[date.getMonth()] + " " + day + suffix + ", " + date.getFullYear(); } /** * Make a request to Logseq HTTP API */ function logseqRequest(method, args, token) { var http = HTTP.create(); try { var response = http.request({ "url": LOGSEQ_API_URL, "method": "POST", "encoding": "json", "headers": { "Authorization": "Bearer " + token, "Content-Type": "application/json" }, "data": { "method": method, "args": args } }); return response; } catch (e) { return { success: false, statusCode: 0, responseText: "" }; } } /** * Parse response from Logseq API */ function parseResponse(response) { if (!response.success) { return { success: false, error: "HTTP request failed with status " + response.statusCode }; } try { var data = JSON.parse(response.responseText); if (data.error) { return { success: false, error: data.error.message || JSON.stringify(data.error) }; } return { success: true, result: data }; } catch (e) { return { success: false, error: "Failed to parse response: " + e.message }; } } // ============================================ // Main Script // ============================================ function main() { // 1. Get API token from secure credential storage var credential = Credential.create("Logseq API", "Logseq HTTP API authentication token"); credential.addPasswordField("token", "API Token"); if (!credential.authorize()) { app.displayErrorMessage("Authorization cancelled"); context.fail(); return; } var token = credential.getValue("token"); if (!token || token.trim() === "") { app.displayErrorMessage("No API token provided"); context.fail(); return; } // 2. Parse draft content var lines = draft.content.split("\n").filter(function(line) { return line.trim() !== ""; }); if (lines.length === 0) { app.displayErrorMessage("Draft is empty"); context.fail(); return; } var parentContent = stripHeaderMarks(lines[0]); var childLines = lines.slice(1).map(function(line) { return stripListPrefix(line); }); // 3. Get today's journal page name var today = new Date(); var journalPage = formatLogseqJournalDate(today); // 4. Append parent block to journal page. // No app.displayInfoMessage() here — see header note on the ActionKit // dispatch_sync crash; intermediate banners are intentionally suppressed. console.log("appendBlockInPage: " + journalPage); var appendResponse = logseqRequest( "logseq.Editor.appendBlockInPage", [journalPage, parentContent], token ); var appendResult = parseResponse(appendResponse); if (!appendResult.success) { app.displayErrorMessage("Failed to append block: " + appendResult.error); context.fail(); return; } // 5. Insert all children in ONE batch call under the parent block. // insertBatchBlock with sibling:false makes the array items children // of parentUUID. Single round-trip regardless of child count. var parentBlock = appendResult.result; if (childLines.length > 0 && parentBlock && parentBlock.uuid) { var parentUUID = parentBlock.uuid; var childBlocks = childLines.map(function(line) { return { "content": line }; }); console.log("insertBatchBlock under parent UUID: " + parentUUID + " (children: " + childLines.length + ")"); var batchResponse = logseqRequest( "logseq.Editor.insertBatchBlock", [parentUUID, childBlocks, {"sibling": false}], token ); var batchResult = parseResponse(batchResponse); if (!batchResult.success) { // Single final banner (see header note on the ActionKit dispatch_sync // crash; never fire two banners back-to-back). Parent already landed. app.displayWarningMessage("Sent to " + journalPage + " — children failed: " + batchResult.error); return; } } // 6. Success! var childCount = childLines.length; var message = "Sent to " + journalPage; if (childCount > 0) { message += " (" + childCount + " child block" + (childCount > 1 ? "s" : "") + ")"; } app.displaySuccessMessage(message); } main(); script.complete();
Options
-
After Success Archive , Tags: sent-to-logseq Notification Info Log Level Info