Action

Append to Logseq

Posted by kerim, Last update about 23 hours ago

UPDATES

about 23 hours ago

Updated formatting of the Readme

show all updates...

about 23 hours ago

Updated formatting of the Readme

about 23 hours ago

Updated the documentation to make it clear that this is for Logseq DB.

1 day 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

  1. Logseq version 2.x.x (The DB version of Logseq.)

  2. Logseq HTTP API Server must be enabled:

    • Open Logseq → Settings → Features
    • Enable “HTTP APIs server”
    • Note your API token (or set one)
  3. Logseq must be running when you use the action

Setting Up the Action in Drafts

Step 1: Create New Action

  1. Open Drafts
  2. Go to Actions list
  3. Tap + to create a new action
  4. Configure:
    • Name: Send to Logseq Journal
    • Icon: Choose an icon (e.g., arrow.up.doc)
    • Color: Choose a color

Step 2: Add Script Step

  1. Tap Steps
  2. Tap +AdvancedScript
  3. Enable “Allow asynchronous execution” (critical!)
  4. Paste the contents of send-to-logseq-journal.js

Step 3: Configure After Success

  1. Go to action settings
  2. Under After Success:
    • Add Tag: sent-to-logseq
    • Archive: Enable

Step 4: First Run - API Token

On first run, Drafts will prompt for your Logseq API token:

  1. Get your token from Logseq → Settings → Features → HTTP APIs server
  2. Enter the token when prompted
  3. The token is stored securely in Drafts’ credential system

Usage

  1. Write your content in a draft
  2. First line = parent block
  3. Additional lines = child blocks (nested under parent)
  4. Run the action
  5. 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:

  1. Long-press the action
  2. Select Manage Credentials
  3. Tap Forget on “Logseq API”
  4. Run the action again to enter new token

Steps

  • script

    // Send to Logseq Journal - Drafts Action Script
    // Version: 1.2.0
    //
    // 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
    
    // ============================================
    // 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();
        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;
    }
    
    /**
     * 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
    // ============================================
    
    // 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();
        script.complete();
    }
    
    var token = credential.getValue("token");
    
    if (!token || token.trim() === "") {
        app.displayErrorMessage("No API token provided");
        context.fail();
        script.complete();
    }
    
    // 2. Get graph name from configured value (optional - not used in API calls but documented)
    // var graphName = context.configuredValues["graphName"];
    
    // 3. 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();
        script.complete();
    }
    
    var parentContent = stripHeaderMarks(lines[0]);
    var childLines = lines.slice(1).map(function(line) {
        return stripListPrefix(line);
    });
    
    // 4. Get today's journal page name
    var today = new Date();
    var journalPage = formatLogseqJournalDate(today);
    
    // 5. Append parent block to journal page
    app.displayInfoMessage("Sending to Logseq...");
    
    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();
        script.complete();
    }
    
    // 6. Get parent block UUID and insert children
    var parentBlock = appendResult.result;
    
    if (childLines.length > 0 && parentBlock && parentBlock.uuid) {
        var parentUUID = parentBlock.uuid;
        var failedChildren = [];
    
        for (var i = 0; i < childLines.length; i++) {
            var childContent = childLines[i];
    
            // Insert child block under parent
            var childResponse = logseqRequest(
                "logseq.Editor.insertBlock",
                [parentUUID, childContent, {"sibling": false}],
                token
            );
    
            var childResult = parseResponse(childResponse);
    
            if (!childResult.success) {
                failedChildren.push(i + 2); // Line number (1-indexed, +1 for parent)
            } else if (childResult.result && childResult.result.uuid) {
                // Update parentUUID to the last inserted child for sequential insertion
                // Actually, we want all children under the original parent, so don't update
                // parentUUID = childResult.result.uuid;
            }
        }
    
        if (failedChildren.length > 0) {
            app.displayWarningMessage("Some children failed (lines: " + failedChildren.join(", ") + ")");
        }
    }
    
    // 7. Success!
    var childCount = childLines.length;
    var message = "Sent to " + journalPage;
    if (childCount > 0) {
        message += " (" + childCount + " child block" + (childCount > 1 ? "s" : "") + ")";
    }
    app.displaySuccessMessage(message);
    
    // Signal async completion
    script.complete();
    

Options

  • After Success Archive , Tags: sent-to-logseq
    Notification Info
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.