TLDR; Here is a plugin for Obsidian that I created : Umbracidian
When taking notes and writing down ideas, keeping track of tasks etc, I used to use Microsoft OneNote. I've used it for years and I always felt it was OK but something about it just didn't click. I felt I was using it because it was all I could find and also I had so much in it now that moving was going to be difficult. I then started to do some reading, finding out how others take notes and found two options, Notion and Obsidian. So I gave them both a go, didn't like either and went back to OneNote.
Jump forward 6 months and I thought, lets give them a go again. So I installed Notion again and instantly uninstalled it again. It felt too complex just to get up and running. Maybe I just didn't give it enough time but anyway, I then installed Obsidian. I read the getting started docs and I was up and running. I'd imported all my OneNote files - which I've since realised had nothing of any use in them.
I installed some plugins and I had everything just the way I wanted it. What I loved the most was all my notes were Markdown format and they are saved individually on disk, so, moving them to other systems in the future, if I need to, will be super easy. Anyway, it also got me thinking - if my notes are in Markdown, I can just copy/paste the markdown in to Umbraco to make blogs!
When setting up Obsidian, I installed a few other Obsidian community plugs and I noticed a few that worked with other Content Management Systems like WordPress. I searched to see if anyone had written one for Umbraco and found there wasn't.
A quick read of the Obsidian Developer Docs and I had installed the Sample Starter Plugin and I was up and running. The first thing I noticed was the sample was created using TypeScript, this was good to know as I had been working with a bit of TypeScript when building a Package for Umbraco 15 so it wasn't totally alien to me.
After 30mins of looking around the Sample plugin, having a read of the Obsidian dev docs, which are super detailed, I felt comfortable enough to give it a go and build an Umbraco plugin.
I had a think about how I was going to build this. It made sense to make it work with Umbraco 15 - I initially thought I would use the Content Delivery API but, after some reading up about it and chatting with some friends, the Content Delivery API wasn't what I needed, it was the Management API that I needed to use. The Content Delivery API is for getting Content via JSON so you can then use Umbraco in a headless fashion, the Management API allows me to communicate directly with the backoffice, so I can create, published, delete content. I can basically do anything I am able to do as an admin logged in, but via API.
I setup up a fresh copy of Umbraco 15 with Paul Seals Clean Starter kit installed - so that I had some basic content in my site and then investigated the Management API, reading the Umbraco docs and then accessing Swagger and hoping to find everything I needed to know. I kind of did find everything but I found the Swagger information horrendous. There isn't really any useful info unless you know exactly what endpoint you're looking for. Which I didnt.
I went back to the Umbraco documentation and although it's really useful, the examples felt a bit simplistic and only showed how to GET cultures. Nothing about how to create nodes in the backoffice. More reading was going to be needed and also the examples were in c# which again, wasn't helpful for my requirements. This was going to be a real learning experience!
I found a few tutorials kicking about the internet where other people had used the API endpoints but these were all via Postman, which were a bit more helpful or another step forward but again, they were just pulling data back from Umbraco, probably because they have just followed the Umbraco documentation.
I started following the docs to at least get an API connection to my Umbraco instance. The documentation is easy to follow and within a few minutes I had a specific API user setup in the backoffice and I was making a connection - pulling back cultures from the API.
As I mentioned early, the Swagger docs don't tell you what endpoint you need to use to do different things so it was time for a bit of, ok, a lot of trial and error. I thought the Umbraco Heartcore docs might help since I was basically using Umbraco CMS in a headless manner, the HeartCore docs didn't help. I felt like I was slowly losing the will to make this work. I wasn't making much progress and it was frustrating me since it had started so well.
I decided to take a step back, make some notes and think of the project as a lot of smaller parts. Lets tackle the small parts, a bit like I had already done e.g. get a sample plugin setup. Get an API users setup. I needed to stop thinking about the final outcome and think of all the parts needed to get there and then tick them off one at a time.
I was pulling back the bearer token from the API via the endpoint /umbraco/management/api/v1/security/back-office/token
I then manually created the new document type in the back office that would store the blog info and for now, I allowed that document type to be created on the root of the site.
Now that I have created the document type, I needed to be able to save that via the API. I found a couple of API endpoints and playing around with Postman, I could see I could save a document type by using the UUID but that meant I needed to find the UUID of the document type.
I managed to do a search using the endpoint
/umbraco/management/api/v1/item/document-type/search?query=${docType}&skip=0&take=1
Using this endpoint I was then able to search for the Document Type by name and retrieve the first instance of that.
The search was a GET
request and was pretty straightforward and once successful, I then received the document type as a JSON response and within the JSON, I could see I had the UUID for the document type. Progress.
Now I had the document type UUID, I went searching the docs to see how to post a new file. I found an endpoint that I thought might work but I couldn't work out what data was required so that the post would be successful.
I had no idea so I posted on the Umbraco forum. and it was suggested I take a look at the payload Umbraco sends to the API via the browser dev tools. This was a massive help and to test this, I took things right back to basics. I created the code in a plain TypeScript file, I removed the need for Obsidian at this point and just focussed on the logic of getting the code to create a note in the backoffice with some dummy content.
Once I had something getting created in Umbraco, I could then think about moving the code in to the Obsidian Plugin and tackle any issues that way, instead of hitting Obsidian issues, Umbraco issues and general coding issues all at the same time.
The end point I used to create the content node was /umbraco/management/api/v1/document
Now that I had some code that connected to the API and created a new content blog, I started to move the code over to the Obsidian Sample package again and I instantly ran in to more issues.
[watch] build finished, watching for changes...
✘ [ERROR] Could not resolve "node:http"
node_modules/node-fetch/src/index.js:9:17:
9 │ import http from 'node:http';
╵ ~~~~~~~~~~~
The package "node:http" wasn't found on the file system but is built into node. Are you trying to
bundle for node? You can use "platform: 'node'" to do that, which will remove this error.
✘ [ERROR] Could not resolve "node:https"
node_modules/node-fetch/src/index.js:10:18:
10 │ import https from 'node:https';
╵ ~~~~~~~~~~~~
Fix : Run npm i node-fetch@2.7.0
which was a down grade from what was installed.
The next error was due to how Obsidian actually works - what I hadn't realised was Obsidian is an electron app, so Chrome under the hood. Which this meant was my previous working code, which was running as a script, now was running in to new issues because I was basically communicating with the API via a browser.
Access to fetch at from origin 'app://obsidian.md' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Access has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Fix : Adding CORS Policy on Umbraco Setup so that Obsidian can connect to the API endpoint.
Everything was working but then I made more code changes and all of a sudden, things were broken again. I jumped in to debug and found that a promise was pending. This was due to using async functions and I had missed an awaited when getting something!
I then decided I didn't like that fact that someone was going to need to modify Umbraco startup to add a CORS policy for this plugin. It felt like a barrier to people using it. To update the startup, you would need to do a deployment and I would need to add more info on how to setup the plugin, it wasn't something I wanted. So I did a bit of reading and found that CORS could be removed if not using node-fetch
, node-fetch
also had the limitation that it would only work on desktop. So I did a bit more reading and swapped out node-fetch
and used requestUrl
I was now making successful posts to the backoffice but my console was still giving me errors
To fix this, when I was getting the document type back from Umbraco, I had to use .json
on the raw data I was getting back from Umbraco so I could get the actual data.
const obsidianDocTypeRaw = await CallUmbracoApi(endpoint, token, 'GET'); const jsonDocType = obsidianDocTypeRaw.json;
When creating a unique GUID for my new document types, originally I was importing the randomBytes module from crypto which meant I was creating the new GUID server side.
import { randomBytes } from 'crypto';
But then I found that this wouldn't work on mobile devices but there was an alternative which is available via the web browser (client side) and since Obsidian is a browser, I could just use that instead. There is crypto
available via the MDN Crypto API.
Now I had everything working, tidied up and not reliant on server-side code, it meant the plugin would be available for mobile and desktop versions. The final step was to create a PR to have the plugin officially listed within Obsidian. This has turned out to be a slow process, at the time of publishing this, I've waited 2 weeks so far and still no sign of it being approved. From reading forums and reddit posts, it sounds like there are only 2 people reviewing hundreds of submissions and so it's just a waiting game.
In the meantime, I've published my plugin on github with instructions on how to install it manually e.g. not via Obsidian.
It was great experience and I really enjoyed learning more about Typescript and creating something that wasn't in .net or c# but still something that will work with Umbraco.
I've a load more features I want to add to the plugin but for version 1, it's now done.