Importing Tiddlers from JSON

Hello all,

I have a process where I export a table from Excel into a json file and then import that into Tiddlywiki. Each row represents a tiddler with columns becoming fields. This works really great except for one thing. The imported tiddlers do not have a created or modified field. They only have the fields as they exist in the json file. Is there any way of getting around this?

Thanks in advance!

1 Like

Unless “timestamps is off” a tiddler will get a modified and created date (need to check) as soon as they are edited. You can create a button containing a list widget that iterates all tiddlers and includes an action widget. You could do something as simple as add an empty caption field.

Just an idea, unless the middle JSON step is required somehow, would the native Excel import plugin help you and getting your data into TW?

Thanks @TW_Tones and @Lamnatos for the suggestions. After I sent the OP, I realized that I really need to give some more thought as to what I was going to use the created and modified fields for. I’m looking at different ways to sort the imported tiddlers and the created/modified fields may not be the right approach. Some noodling is definitely in order.

A simple solution would be to create a button allowing you to bulk edit recently imported tiddlers manually:

<$button>
Set imported timestamp
<$list filter="[[$:/Import]links[]]">
<$action-setfield imported=<<now [UTC]YYYY0MM0DD0hh0mm0ssXXX>> $timestamp="no"/>
</$list>
</$button>

You would need to remember to click the button right after importing the tiddlers while the $:/Import tiddler is up to date.

You could automate this by overriding the tiddler $:/core/ui/ViewTemplate/body/import, but modifying core tiddlers is generally discouraged because it prevents that file from receiving updates when you upgrade TiddlyWiki.

A cleaner approach is to create a small startup module to implement a new system tag $:/tags/ImportAction. Tiddlers with this tag will execute automatically immediately after an import.

:warning: Make a backup of your wiki before attempting this!

To achieve this, we need to create a small script to “hook” into the import process.

Create a new tiddler (e.g., $:/plugins/custom/import-hook.js) with the following fields:

  • Type: application/javascript
  • Field: module-type with value startup

Then paste this code into the body:

/*\
title: $:/plugins/custom/import-hook.js
type: application/javascript
module-type: startup

Runs actions tagged with $:/tags/ImportAction after an import is performed.
Only actions present before the import will be performed.
\*/
(function(){
"use strict";
exports.name = "import-actions-hook";
exports.platforms = ["browser"];
exports.after = ["startup"];
exports.synchronous = true;

exports.startup = function() {
    var NavigatorWidget = require("$:/core/modules/widgets/navigator.js").navigator;
    var originalHandlePerformImportEvent = NavigatorWidget.prototype.handlePerformImportEvent;

    NavigatorWidget.prototype.handlePerformImportEvent = function(event) {
        
        var importTiddlerTitle = event.param || "$:/Import";
        var importTiddler = this.wiki.getTiddler(importTiddlerTitle);
        var blockedTitles = [];

        // --- PHASE 1: Identify targets BEFORE import ---
        if(importTiddler) {
            try {
                var importData = JSON.parse(importTiddler.fields.text);
                if(importData && importData.tiddlers) {
                    
                    var incomingTitles = Object.keys(importData.tiddlers);
                    
                    for(var k=0; k<incomingTitles.length; k++) {
                        var title = incomingTitles[k];
                        
                        // Check 1: Is the tiddler actually selected for import?
                        // TiddlyWiki stores selection state in fields like "selection-TiddlerTitle"
                        var selectionState = importTiddler.fields["selection-" + title];
                        
                        // If explicitly set to "no", it is NOT being imported. Skip blocking logic.
                        if(selectionState === "no") continue;

                        // Check 2: Content Comparison
                        // We compare the INCOMING text with the EXISTING text.
                        // If they are identical, it is a safe re-import (allow execution).
                        // If they are different (or new), it is a potential threat (block execution).
                        var incomingText = importData.tiddlers[title].text;
                        var existingTiddler = this.wiki.getTiddler(title);
                        var existingText = existingTiddler ? existingTiddler.fields.text : null;

                        if(incomingText !== existingText) {
                            blockedTitles.push(title);
                        }
                    }
                }
            } catch(e) {
                console.error("Import Hook: Error analyzing import safety", e);
            }
        }

        // --- PHASE 2: Perform the Import ---
        // This writes the new/changed tiddlers to the wiki
        var result = originalHandlePerformImportEvent.call(this, event);
        
        // --- PHASE 3: Run Actions ---
        var actionTiddlers = this.wiki.filterTiddlers("[all[tiddlers+shadows]tag[$:/tags/ImportAction]]");
        
        for(var i=0; i<actionTiddlers.length; i++) {
            var actionTitle = actionTiddlers[i];

            // SECURITY CHECK:
            if(blockedTitles.indexOf(actionTitle) !== -1) {
                console.warn("SECURITY BLOCKED: Prevented execution of newly imported/modified action: " + actionTitle);
                continue;
            }

            this.invokeActionString(this.wiki.getTiddlerText(actionTitle), this, event, {
                importTiddler: importTiddlerTitle
            });
        }
        
        return result;
    };
};
})();

How this works

This script uses a technique called “monkey patching” to extend TiddlyWiki’s core Navigator widget:

  1. Intercept: It listens for the handlePerformImportEvent (which happens when you click “Import”).
  2. Execute Core Logic: It runs the original TiddlyWiki import function first, ensuring the files are actually imported as usual.
  3. Hook: Immediately after the import finishes, it searches for any tiddlers tagged with $:/tags/ImportAction.
  4. Execute Actions: It runs the text of those tiddlers as ActionWidgets, passing them a variable called importTiddler.

Note: this solution uses the “Monkey Patch” approach instead of relying on the th-before-importing hook to let you use standard TiddlyWiki macros and widgets immediately after an import, which are easier to write and maintain.

Save and reload your wiki for the script to take effect.

Now you can create your timestamp action.

  1. Create a new tiddler (e.g., $:/config/AutoSetImportDate).
  2. Add the tag: $:/tags/ImportAction
  3. Paste the following into the text field:
<$list filter="[<importTiddler>links[]]">
<$action-setfield imported=<<now "[UTC]YYYY0MM0DD0hh0mm0ssXXX">> $timestamp="no"/>
</$list>

This uses the variable <importTiddler> passed by our script to target the specific batch being processed, applying the timestamp instantly without requiring a manual button click.

Demo (with examples): https://import-action.tiddlyhost.com/

See also: Feature-request: action on import?

1 Like

Yep!

And in case you’d like a well-developed tool that includes this and other elements, let me just link to Tiddler Commander by @Mohammad:

It’s great at all kinds of batch processing, and offers a little prompt, at the bottom of the import tiddler, for working with the most recent import-batch.

All excellent suggestions! I do have Commander installed and use it often.

As I said, I really need to think about what it really is I want to accomplish. The tiddlers that are being imported represent each log from my Radio Listening hobby, be it the AM band, FM band, SW band or whatever. My original thought was to use the created field to sort by which log was the latest. But that really won’t work because all imported tiddlers would have the same created date (basically identically to having no created field at all). After stopping overthinking things, I think the easiest thing to do is to just sort by the log date which is already a field. Overthinking is so easy to slip into!

However, the above suggestions may come in handy for other things so I will definitely keep them handy.

1 Like

Noting: This is scoped to only imported tiddlers? by iterating the links[] in <importTiddler>

Add a column for created and fill it from converting your log date to TW5 DateFormat before export to json.

image

This will run the action widgets in all import action tiddlers. You can scope this to the current import batch or any other set of tiddlers using filters.

However, importing action tiddlers poses a security risk because they would execute immediately and could contain destructive actions. Therefore, I updated the demo code to only run import actions that are already present in the wiki, ignoring those contained within the new import.

1 Like

Yes, I have suggested recently that the Import routine should at least prompt the user BEFORE “running” anything. I do think there is value having a method to bring this to the attention of the interactive user, whether they proceed or not.

The value this could add;

  • Bring attention to a readme
  • Post installation script including
    • to delete unwanted tiddlers (eg remove tiddler, overwirtten shadows etc…)
    • run a monkey patch
    • trigger a user interactive configuration or setup
  • Importable set of actions designed to perform a particular task
    • Support inc resetting broken wikis
    • Guided Module/solution design eg creating custom buttons
  • Solution removal scripts (custom) when it goes beyond just a plugin
  • Support for merging tiddlers such as wiki review suggestions