Generate Pretty Code / App Screenshots ๐Ÿ“ธ

Spice up your docs, blog posts and Tweets with some pretty screenshots! The following free tools will generate nicely formatted screenshots of code, device mockups and embedded info.

Code Screenshots

1. developed by the team behind Raycast, generates clean and minimal code screenshots.
You can also specify options via GET URL params, so you could programatically generate an image (e.g. using this shell script, or this VS Code extension).


2. Carbon

Carbon has more advanced styling options. It's open source (view on GitHub) and developed by @mfix22.
You can also directly generate an image from a GitHub Gist, by passing the Gist ID in the URL, for example:


3. is another clean and simple option, it too is open source (view on GitHub) developed by @Idered.


VS Code Extensions

While we're on the topic of code screenshots, I have to mention a few really neat IDE extensions which help you take great screenshots, directly from VS Code.

Browser / Device Screenshots

5. Browser Frame

Browser Frame, developed by @pqvst creates clean and minimal web browser mock-ups, with a range of browser frames in both light and dark mode supported.


6. Screely

Screely, developed by @JurnW creates very simple browser mockups, with an optional background fill.


7. Mock up Phone

Mock up Phone by Authgear has a range of different mobile, tablet, laptop and large screen device frames, which are easy to upload images into. But keep in mind the screenshots ratio must be correct to avoid it being stretched.


8. Screenshot.Rocks

Screenshot.Rocks by @daveearley generates very simple browser and mobile mockups, with an optional background, shadow and controls.


9. The App Launchpad (requires sign up)

Unlike other options on this list, The App Launchpad does require sign up, but it's free to use. It's a tool that makes it easy to build beautiful screenshots for your app, optimised for publishing to Google Play and the Apple Store.


10. Magic Mockups

Magic Mockups, developed by Kaspars Sprogis is an older tool, but still very useful if you want to generate mockups displayed on a physical device in real-life situations. Note that your screenshot needs to be exactly the right ratio, to avoid it being stretched or cropped.


11. Device Shots

Device Shots by @diogocapela let's you build colourful, annotated app screenshots.


Social Media Screenshots

12., built by Callum Mckeefery generated beutiful embeded social media posts. It supports content fetched from Twitter, LinkedIn and Shopify, and has several themes and customization options.


13. Tweetlet

Tweetlet by Basharath is very similar to Poet, but also supports embedding code, images, text and Tweets.



14. Fabpic

Fabpic by Shadab Alam adds beautiful gradient backgrounds, drop shadow and borders to any image


15. SuperShots

Finally, SuperShots by Superblog is a simple tool to add gradient backgrounds to a picture



API Flash


There's plenty of screenshot API services out there, but API Flash is one that I've personally used across countless projects. You can generate a URL which resolves to a screenshot of your website and pass in parameters to configure it (like custom CSS, element selector, device + resolution, timeout delay, no ads / cookie banner, etc).
Although you only get 100 scr/ month on the free plan, there's an option to set the cache time, which makes it easy to stay within that limit.

Screenshot Apps

Flameshot is a free and open source (here on GitHub), cross-platform (C++ for Win, Mac and Linux), highly customisable desktop screenshot app. It's simple to use, includes some nice features like an in-app screenshot editor, and most importantly has an integrated command line interface.

If you use Firefox, you can capture screenshots right from your browser. Just right-click on any part of a website and select "Take Screenshot" (or Ctrl + Shift + S). From here, you can either capture the entire page, a selection, or specific HTML nodes.

Gimp can also take screenshote (although it needs to e launched first), just navigate to File --> Create --> Screenshot, select your options, and hit Snap.

Finally, the ImageMagick package includes a screenshot util with plenty of customisation options, and can be invoked from the command line (with import), or integrated into a script.

And if you need to create visual step-by-step guides, there's several extensions that will help with this, including:, ScribeHow, Minervaknows, etc.

50+ Awesome Dev Tool Tips ๐Ÿ”ฅ

The browser developer tools are incredibly powerful, with new debugging and optimisation features being added every few months.

The following post outlines 50+ awesome tips, that you can use to supercharge your web development experience.

Design Mode

Enabling design mode will make the entire web page editable, just by clicking and typing.

To use it, open up the console and run:

document.designMode = 'on'


Pretty Print

Raw files are usually minified, and therefore hard to read. Use the Pretty Print option to format it

In the background, Chrome is creating a new file (names filename.js:formatted), closing that file will undo the formatting.

If doing this each time is getting boring, then there's an experimental setting to auto-enable pretty print for all sources.
Under โ‹ฎ โ†’ Settings โ†’ Experiments Select Automatically pretty print in the Sources Panel.


Command Pallet and Super Search

The command pellet gives you full access to every command available within the dev tools, and is super valuable for quick navigation.

Type Ctrl + Shift + P to open up the Command Menu, then start typing to filter commands and press enter to execute.

In the same way, if you're only looking to find a function name, you can use Ctrl + Shift + O to filter methods across files.


From the same menu, you can also search through all loaded resources by filename or code (HTML, CSS, JS, etc), all network requests, visible components, memory profiles, snippets and much more. Advanced features like RegEx are supported.

For an app built with a framework, you'll probably see a lot of irrelevant files (like nodemodules, webpack output, etc). You can hide this under โ‹ฎ โ†’ Hide ignore-list sources. By default, this uses the smart `xgoogle_ignorelist` to detect what's likely not relevant, but you can also add your own custom sources, specified by regex under Settings.


So you've spent a while crafting a function in the console, and you plan on reusing it across various sites later. That's where snippets come in, they're invoked from the command pallet, and let you save code for later and execute it using the bang ! operator.


Live Expressions

Instead of repeatedly executing a command to monitor for changes, you can watch values in real-time using Live Expressions. Just execute a command, then pin it using the eye icon to see changes reflected automatically.

There's many use cases for this, but one I use often is when testing keyboard navigation of an app, pinning the document.activeElement command will print a log of evertime the focused element changes, even once it's been removed from the GUI.


Tracking Changes

We've all been there, you've been editing your app's HTML, CSS and JS through the dev tools and got things working perfectly, but you can't remember exactly what you changed. That's where the Changes Tab comes in. Access it through the command pallet (with Control+Shift+P, then type "Show Changes"), or through the lower draw.

You'll then be able to see a diff output of all your changes. From here you can Copy changes to clipboard, or revert certain changes.


Console Shorthand

  • $() - Short-hand for Document.querySelector() (to select DOM elements, jQuery-style!)
  • $$() - Same as above, but for selectAll for when returning multiple elements in an array
  • $_ - Returns value of last evaluated expression
  • $0 - Returns the most recently selected DOM element (in the inspector)
  • $1...$4 can also be used to grab previously selected UI elements
  • $x() - Lets you select DOM elements using an Xpath query
  • keys() and values() - Shorthand for Object.getKeys(), will return an array of either obj keys or values
  • copy() - Copies stuff to the clipboard
  • monitorEvents() - Run command each time a given event is fireed
  • For certain common console commands (like console.table()), you don't need to type the preceding console., just run table()

You can clear the console at anytime using Ctrl + L, using the clear button, or by running clear()

There's many more console shorthand commands, here's a full list.

Warning These tricks only work within the dev tools console, and will not work in your code!

Find Unused Code

Easily identify which bundles are the largest, and how much of their code is actually used, and what the load impact of each file is, using the Coverage tool. This illustrates exactly which code is being loaded but never used, and what the cost of it is.

Click the three dots, select coverage and reload the page. Any red bars indicate unused code, and will likely be making your app slower. Clicking an individual file will let you see specifically what code isn't being used.


Rendering Panel

This tool is super useful for identifying elements that are being edited more often than possibly necessary, and which are likely negatively affecting performance and user experience.

The frame rendering stats is specifically useful, for monitoring CPU and GPU usage, helping you identify things before they become a problem.


Network Paint Times

Your probably familiar with the waterfall chart rendered by the Network tab, and how useful that is for detecting slower requests. But from here, you can also enable screenshots to see exactly what parts of your site will load visually for end users on slower connections.


Network Timings

Clicking on an item shows headers and response, but head to the Timing tab, and you'll be able to see what stage the request was held up at, and specific server timings. Using the Server-Timing API, you can pass even more detailed info from your API to your client app, and view data in the Timings tab of the browser dev tools. To use this, just add the Server-Timing header to your response data, and detailed charts will be visible in the dev tools.

To find the total size of your web page, under the Network panel, check Disable Cache, reload the page, and the info bar at the bottom will display total size, total time, total requests and other key stats.

Inspect Network Requests

You likely already know this, but you can also view the request and response for any HTTP request your site makes, as well as view loading times and see where in the code it was triggered.



It's really worth exploring the performance panel and all that it has to offer. Just hit the record button, then interact with your site like an end-user might. When you're finished, you'll have a really detailed breakdown of CPU usage, FPS and memory allocated to the heap. Where ever there's a spike in the timeline, that usually indicates an area of code that needs to be optimised. You can then investigate this further by drilling down on the flame chart, to see the full stack trace of everything that happened on the main thread.


Identifying Memory Leaks

Modern browser automatically garbage-collect any data that is no longer being referenced to. But poorly written code can mean you have obsolete code references that build up over time causing a memory leak. This can be detrimental to performance and greatly hider user experience.

Thankfully, they're not as hard to find or debug as you might have thought. Under the Memory tab, select "Allocation instrumentation on timeline", then hit record.
The blue bars indicate memory that's still in use, and the grey bars show what was garbage collected. So a rapidly growing blue bar would be where your mem leak is happening, and you can then click that bar to see exactly what data objects they contain, and easily identify the cause.


Worth also noting that the web page isn't the only source of memory leaks. They can also be caused by add-ons, the browser engine itself or even data caching. Use the Statistics view to see a breakdown of what data is using memory.


Raw Memory Inspection

If you're building a web assembly app, this will be particularly important to you. From the memory inspector, you can drill down the scope, and inspect ArrayBuffer, TypedArray, DataView, and Wasm Memory. Here's a WASM demo:


Test bfcache

bfcache is a browser feature that enables instant backward and forward navigation, it works differently from the HTTP cache because it stores a snapshot of the entire page in memory, which is what the user will see while the navigation is happening.

In order for the bfcache feature to work effectively on your site, you need to optimise for it. And that's where the Back/Forward Cache Tester comes in. Under Application โ†’ Back/forward cache tab, click "Test back/forward cache", and you'll be presented with the results which will list issues for each frame. Clicking each result will also give you instructions on how you can fix it.


Full Refresh

Some errors are caused by cached content, and for those a normal refresh isn't enough. With the dev tools open, you can hold down the refresh button (for 2 seconds), and you'll see some additional refresh options, including "Empty Cache and Full Reload".
This is also useful for measuring first-time load metrics for new users, when nothing is previously cached.

To refresh all tabs at once, just reun chrome://restart in the address bar.


Lighthouse is an extremely useful (and easy!) tool for measuring Core Web Vitals - accessibility, SEO, responsivness, performances, security, PWA compatibility, best practices and overall user experience.

Just open the Lighthouse tab, and click "Start Scan".


Lighthouse results can be exported in a range of formats, and there are various external viewers you can use to gain additional insight (like this one).

Lighthouse scans can also be incorporated into your CI/CD system, so that you have constant visibility into your apps core vitals.

Page Size Breakdown

Understanding what data is being loaded into your site will help you reduce overall bundle size. This can be done from the Memory and Network tabs, but sometimes a more visual view helps put things into context.
The Chrome Memory Treemap is really useful for this - to use, just run a Lighthouse scan, export the results in JSON, then import it into
You can click any element, to zoom in and inspect additional info.


Record User Flows

Note (This feature is still in beta, and currently requires Chrome Dev Eddition)

Record, reply and audit user flows under the audit panel. Just click Start new Recording, enter a name and hit go. Everything the user does, including mouse moves, keypresses, pauses and more will be logged. You can then use Replay to view back the users journey.

In the replay settings each step can be expended to view details, you can also edit, add and remove steps in real-time. There are additional options for simulating things like environment of slow network connection. This is super useful for user testing.

You can also import and export user flows as a Pupateer scripts, to share with others.

Advanced User Flow Operations

The recorder tool has many other valuable features which often go under-used. Being aware of what you can do, will help you supercharge your user testing.

Examples of when this can be useful include: sending the exact steps to recreate a bug to another developer, demonstrating to an analyst exactly how users behaved during a testing session, or slowing things down to debug complex issues.

Once you've recorded a user flow, you can:

  • Replay it (obviously!)
  • View detailed performance metrics over time
  • Export it (as JSON, Puppeteer or Puppeteer reply script)
  • Edit the flow (then re-import it)
  • Share user flows with others (for testing or demonstration)
  • Configure replay settings, such as apply throttling or device limitations
  • Replay in slow mo, with detailed debugging
  • Apply breakpoints, to pause and inspect at certain steps
  • Import user flows generated by other tools
  • Add additional steps, or remove steps

There are several third-party tools that let you do even more, as well as import / export in additional formats, like the Cypress add-on, Nightwatch add-on, Jest add-on, WebDriver add-on and more.

Pausing Execution with Breakpoints

Breakpoints are an absolute essential for debugging. They enable you to pause everything at a certain point to inspect state and discover issues. You're probably already aware that you can trigger a breakpoint at a certain point, either with the debugger statement statement, or by clicking the margin (in the sources panel, or with a compatible IDE). But there's several other types of breakpoints, including:

  • Conditional line-of-code - On an exact region of code, but only when some other condition is true.
  • DOM - On the code that changes or removes a specific DOM node, or its children.
  • XHR - When an XHR URL contains a string pattern.
  • Event listener - On the code that runs after an event, such as click, is fired.
  • Exception - On the line of code that is throwing a caught or uncaught exception.
  • Function - Whenever a specific function is called.

You can also make conditional breakpoints (represented as orange tabs), which will only pause the execution when certain conditions are met. To do so, just right-click, select Edit Breakpoint, then use JavaScript to resolve a boolean using current state.

If there's a specific method you want to pause on, just run debug(methodName) to start, and undebug(methodName) to end.

Once a breakpoint has been hit, you can interact with any current state through the console.


Remote Debugging

As any app developer will tell you, nothing beats testing on a real device. But when it comes to the web, the browser debugging tools are essential. That's where remote debugging comes in - it enables you to test on a physical device while continuing to have the debugging power of the browser tools.

The same can work in the opposite direction, where you run your dev environment locally or host it remotely, but access it on an external device.

This does require either port forwarding or custom domain mapping (but neither are as scary as they sound!). The docs provide setup instructions and proxy configurations.

And if you're developing a native Android app, which has embedded web views, you can use the browser's dev tools to debug these too (docs).

Mock Location and Sensors

In a similar way to the iOS and Android emulators, you can simulate various sensors and hardware settings. This is useful if the app your developing relies on any of this data. Under the Sensors tab, you'll be able to change your location, time zone, locale, screen lock, orientation, motion / acceleration etc.

If you frequently find yourself switching between locations or locales, you can add these under Settings --> Locations.


Death by Errors, no more!

If you're wading through a large code base or dealing with a poorly written library, and drowning in exceptions which are distracting you from what you're actually trying to debug, then under the settings you can opt to ignore any exceptions thrown by certain scripts or from a given framework. The ignore list can be specified by regex patterns for specific targeting.

You can auto-hide source files for most major frameworks by heading to โ‹ฎ โ†’ Ignore List, and selecting "Automatically add known third-party sources to Ignore list". This will make the stack trace in the console show less irrelevant info. You'll still be able to view the full stack for any given error, just by clicking "Show more".

By default the console will show output from all contexts. This means if you've got an extension installed that's throwing an error it will be cluttering up your console. Just right-click on the file name, and under Filter select Hide messages from [ext name]. You can unhide these at anytime from the same menu.

View and Edit Storage

While we're in the Application tab, it's worth mentioning how essential these tools are for viewing, deleting and editing data stored in local storage, session storage, cookies, IndexedDB, etc.

From the storage tab, you can also see a breakdown of how much your site is storing, and simulate restraints like custom storage quotas or disabling cookies.

Note that stored data is (usually) only accessible from the domain which set that data. So if you're debugging stored data in the console for any context other than the default one, you'll need to use the frame dropdown at the top to switch domains.

Debug Background Services

If you app includes notifications, sync, background fetch or anything else that should continue running even when the app / current tab is not in the foreground, then these tools are invaluable. Under the Application tab's Background Services section, you can click a API category, start recording, then put your app into the background. When you come back, you'll be able to see specifically which events fired, and inspect each one.

Side note, you can view all registered service workers, manage, debug and de-register them from: chrome://serviceworker-internals


HTTPS Security Checks

The Security tab provides a good starting point, for when verifying common HTTPS issues on your site. It checks for, and highlights common SSL issues, including non-secure main origins and mixed content. You can also check web certificate chains in more detail.


Web Auth

This ones a bit more niche, but absolutely essential if you're building anything with soft-tokens or 2FA. The WebAuthn tool will let you generate and emulate virtual authenticator devices using a variety of protocols (2FA, FIDO/CTAP) and interface types (USB, NFC, Bluetooth, internal) with additional options for user verification, resident keys, etc.

Here's a quick demo:


For an overview of web auth, see, or view the W3 spec

On a side-note, there's an interesting article explaining how they built the webauthn tab.

Accessibility Tools

Accessibility is not just important for inclusion, it's also a legal requirement for most public-facing apps and services. If you're not yet sure the core concepts of web accessibility standards, I recommend the Web.Dev Accessibility Tutorial, which provides a great summary.

Lighthouse provides a good starting point for auditing accessibility, and is easy to use, and built directly into the developer tools.

The CSS tools also have a built-in contrast tool, which will help you apply readable colors to your site. The inspect pop-up will show a warning, and you can analyze this further in the Styles pain.

Beyond that, the Accessibility tab let's you view an element's position in the DOM tree, ARIA attributes, and computed accessibility properties, all of which are used by accessibility tools like screen readers.

There are additional add-ons which can give you much more powerful insights. Mainly, the axe DevTools. This will show you detailed results and instructions of how to fix.


You can capture screenshots directly from the dev tools, including: full-page, specific area or single-node screenshots.
Either open up the command pallet and type screenshot, or for an element screenshot, just right-click on the DOM element in the inspector and select Capture Screenshot.



From the elements tab, right-click on an element and under Copy, there are several different options. Copy selector will give you a CSS selector to target that element, similarly copy JS path will give you a query string to select the element in JavaScript, and copy outer HTML will copy the actual HTML. Copying the styles of an element, will give you all the computed CSS for a given element.

Animations Timeline

The animations panel (opened by clicking the 3-dots) lets you record any keyframe animations, and then scrub through them to inspect the actual CSS properties that are affected.


Forcing Elements State

If you need to preview the styles of a given element in a particular state (e.g. :hover, :visited, :active, :focus, etc), then either right click it and select Change pseudo class, of from the the styles editor click the :hov icon.


CSS Sizes and Units

Do you ever inspect an element, then hold the arrow keys up/ down for literally ever until the size looks right? Well you can also drag the units horizontally to easily preview different sizes. Similarly, for angles you can use the clock rotater to preview / apply any value. Got the wrong units? Just hover over the size, and click the drop down to quickly switch units.


Color Pallets

Most apps include only a handful of colors, and usually when your changing a color, it will be to one of those values. That's why the palette tool is so useful. By default, there are several pre-made palettes: from your pages current colors, your pages CSS variabels, the Material pelette and an empty custom palette. Switch between them with the arrows.

While we're here, it's worth also touching on just how powerful the color tool is. From here you can:

  • Change color shades, hue and transparency - with live preview
  • Convert between units (hex, RGB(A), HSL(A), HWB(A))
  • Use the eye dropper to pick any color from your screen
  • Copy color value to the clipboard

If you're not already doing so, try to make use of CSS variables (not SASS vars) throughout your app. That way you can update the color in one place, and have it previewed/ applied everywhere. In the dev tools, click a variables name to go to original definition, from there you can modify it.


Easy Box Shadows

Box shadows are one of those things that are best previewed live. That's why the shadow-editor is so useful. Just click the icon next to any shadow to preview different X/Y offsets, blur, spread and directions of both inset and normal shadows.


Easy Animations

By clicking the animation option, you can easily preview various transitions and effects.


Responsive Design Mode

Easily check that your site displays nicely on a range of devices, using the Responsive Design Mode.

By default only a few devices are shown, but head to Settings --> Devices and you'll be able to enable a whole bunch more from the list, or even create your own custom device with dimensions, user agent, platform, architecture and more.



You may have notices a little chip/ badge next to certain elements in the Elements tab. These are Badges, and can be used to apply overlays or add extra features to certain element types including Grids, Flex layouts, Ads, Scroll-Snaps, Containers, Slots and more. To enable badges, right-click an element in the DOM tree and select Badge settings, then check / uncheck what you'd like to be visible.

Many of these badges open the door to additional features, like the Flexbox Debugger and Grid layout debugger



There's always that one front-end dev, so keen to please the designers that he's using an actual ruler to measure the elements on his screen. Well no need for that, or any dodgy ruler browser extensions, as this feature is built directly into the dev tools. Enable it under โ‹ฎ โ†’ Settings โ†’ Preferences โ†’ Elements โ†’ "Show rulers on hover".


In Firefox, there is a built-in ruler feature, available through the Toolbox Buttons in the top-right.


Style Overview

The CSS Overview tab helps you quickly get an overview of CSS improvments you can make. The aim is consistency (colors, fonts, breakpoints, styles, etc).

  • Color Pallet - Shows all colors used on your site. Useful for identifying elements which don't conform to your desired theme / designs
  • Fonts - Displays all typefaces, sizes and variations used in your page. A good webpage will be consistent with only a few fonts and text styles.
  • Media queries - Outputs all breakpoints used in your site, sorted by highest occurrence. You should aim to keep them consistent to make responsive testing easier
  • Unused Declarations - Lists key information about any unused declarations as well as styles that have no effect. Removing these will speed up load times, and make CSS easier to read.



The Layers panel (more tools โ†’ Layers) will show what's happening both off screen and on additional layers (with 3d mode).

It's particularly useful for visualising how specific animations are working, from a functional perspective, without having to wade through a bunch of keyframes and obfuscated code.



Saving Changes to Disk

There are two methods to save or persist changes you've made in the dev tools. Workspaces and Local Overrides.

Workspaces enable you to save changes you make in the devtools directly to your working copy on disk
Workspaces can be setup under Sources โ†’ File System โ†’ Add Folder. They support HTML, CSS and JavaScript and content can be edited, previewed and saved directly through the sources panels. Certain frameworks require some extra setup to get properly working.

Local Overrides enable you to persist changes across sessions (but without effecting original source files)
Overrides can be setup under Sources โ†’ Overrides. You can use the Changes menu to preview / export a diff of what you've changed.


For more advanced tasks, everything in the developer tools can be automated, via the Automation APIs using WebDriver Protocol (which is what tools like Selenium use). As an example, see the webdriver devtools package.

Familiar Shortcuts

So almost everything within the browser developer tools has keyboard shortcuts, here's the full list. But if you're struggling to memorise them all, then you can actually switch to familiar VS Code bindings. Under Settings --> Shortcuts, under the Match shortcuts from preset menu, select Visual Studio Code.

Dark Mode

Finally, but by far the most important tip of all: dev tools dark mode!

Under Settings --> Preferences --> Appearances --> Theme, use the dropdown to switch from Light to Dark, and immediately 10x your developer experience. Because like they say... bugs are attracted to the light ๐Ÿ›๐Ÿ”ฆ

And if you're too cool for the default dark mode, you can write your own stylesheet, then enable the custom loading of CSS! There's a few pre-made stylesheets and an extension available here.

Useful Add-Ons

We're not quite done... so far we've only covered the built-in dev tools, but there are a bunch of super useful add-ons/ extensions

If you're working with a specific framework (like React, Svelte, Vue, etc), then adding their dev tools with give you additional debugging power over components, state and more.

Beyond that, almost everything else can be done nativity / without additional extensions, there's still a few QoL add-ons that can be helpful, but keep in mind, that if you use any of these, you should create a separate Dev profile within your browser, as otherwise they may negatively effect you privacy (installed extensions make you more identifiable).

  • Visbug - Interact with and modify any websites, without needing any HTML or CSS knowledge
  • Lighthouse - Automated performance, quality and correctness checker
  • Designer Tools - Grids and rulers for in-browser measurement and alignment
  • Motion Tools - Inspect and design complex CSS animations
  • Pixel Perfect - Overlay designs over webpages for building pixel perfect pages
  • CPU + Mem Performance Monitor - Add system resources overlay to sites
  • SEO Inspector - Easy inspection of Meta tags for SEO
  • Save all Resources - Easily download everything associated with a site, preserving directory structure
  • Multi-Proxy - Connect to multiple proxies (simultaneously) with IP matching and blocking
  • Accessibility Insights - Get an overview of accessibile navigation issues
  • Check my Links - Quickly find and highlight all dead links within a page
  • Weppalizer - Similar to BuiltWith, quickly check what tech a site is built using
  • Octotree - Adds sidebar on GitHub for much easier navigation

Are we finished yet?

Alright, this time I swear it's the last section, but I couldn't resist mentioning this too!

There is SO MUCH more to the browser developer tools than covered here. Even if you've been a web developer for several decades, I'm pretty sure there's still a whole bunch of handy features that even you've not yet come across. So don't be afraid to go exploring!

The best features are still experimental. You can try them out by enabling them under Settings --> Experiments. There's a link next to each item where you can view a usage tutorial as well as the API docs.

Other Browsers:

  • Firefox dev tools has a very similar feature set and layout to Chrome, but includes a few advanced features (around audio, shaders)
  • Safari's developer tools are lagging behind in terms of features, but are sometimes still required for iOS testing.
  • Other Chromium-based browsers (like Edge, Brave, Vivaldi, etc) inherit from the same source as Chrome, and as such have virtually identical dev tools.

The following sources are great for staying up-to-date with the latest in debug tools:

Fun with console.log() ๐Ÿ’ฟ

If you've ever developed a web app, you'll be familiar with console.log(...), the method which prints data to the developer console; useful for debugging, logging and testing.

Run console.log(console), and you'll see that there's much more to the console object.
This post briefly outlines the top 10 neat tricks you can use to level up your logging experience.



The console.table() method prints objects/ arrays as a neatly formatted tables.

  'Time Stamp': new Date().getTime(),
  'OS': navigator['platform'],
  'Browser': navigator['appCodeName'],
  'Language': navigator['language'],

Screenshot showing an example output of console.table


Group related console statements together with collapsible sections, using

You can optionally give a section a title, by passing a string as the parameter. Sections can be collapsed and expanded in the console, but you can also have a section collapsed by default, by using groupCollapsed instead of group. You can also nest sub-sections within sections but be sure to remember to close out each group with groupEnd.

The following example will output an open section, containing some info'URL Info');
  console.log('Protocol', window.location.protocol);
  console.log('Host', window.origin);
  console.log('Path', window.location.pathname);
  console.groupCollapsed('Meta Info');
    console.log('Date Fetched', new Date().getTime());
    console.log('OS', navigator['platform']);
    console.log('Browser', navigator['appCodeName']);
    console.log('Language', navigator['language']);

Screenshot showing an example output of

Styled Logs

It's possible to style your log outputs with some basic CSS, such as colors, fonts, text styles and sizes. Note that browser support for this is quite variable.

For example, try running the following:

  '%cHello World!',
  'color: #f709bb; font-family: sans-serif; text-decoration: underline;'

You should get the following output:

Screenshot showing an example using CSS in the console

Pretty cool, huh? Well there's a lot more you can do too!
Maybe change the font, style, background color, add some shadows and some curves...

Screenshot showing an example using CSS in the console

Here's something similar I'm using in a developer dashboard, the code is here

Screenshot showing an example using CSS in the console


Another common debugging technique is measuring execution time, to track how long an operation takes. This can be achieved by starting a timer using console.time() and passing in a label, then ending the timer using console.timeEnd(), using the same label. You can also add markers within a long running operation using console.timeLog()

let output = "";
for (var i = 1; i <= 1e6; i++) {
  output += i;
concatenation: 1206ms - timer ended

Screenshot showing an example output of console.time

There's also a non-standard method, console.timeStamp() which adds markers within the performance tab, so you can correlate points in your code with the other events recorded in the timeline like paint and layout events.


You may only want to log to the console if an error occurs, or a certain condition is true or false. This can be done using console.assert(), which won't log anything to the console unless the first parameter is false.

The first parameter is a boolean condition to check, followed by 0 or more data points you'd like to print, and the last parameter is a message to output. So console.assert(false, 'Value was false') will output the message, since the first parameter is false.

const errorMsg = 'the # is not even';
for (let num = 2; num <= 5; num++) {
  console.log(`the # is ${num}`);
  console.assert(num % 2 === 0, { num }, errorMsg);

Screenshot showing an example output of console.assert


Ever find yourself manually incrementing a number for logging? console.count() is helpful for keeping track how many times something was executed, or how often a block of code was entered.

You can optionally give your counter a label, which will let you manage multiple counters and make the output clearer.
Counters will always start from 1. You can reset a counter at anytime with console.countReset(), which also takes an optional label parameter.

The following code will increment the counter for each item in the array, the final value will be 8.

const numbers = [1, 2, 3, 30, 69, 120, 240, 420];
numbers.forEach((name) => {

The following is an example output of labelled counters.

Screenshot showing an example output of console.count

Instead of passing in a label, if you use a value, then you'll have a separate counter for each conditions value. For example:

console.count(NaN);         // NaN: 1
console.count(NaN+3);       // NaN: 2
console.count(1/0);         // Infinity: 1
console.count(String(1/0)); // Infinity: 2


In JavaScript, we're often working with deeply nested methods and objects. You can use console.trace() to traverse through the stack trace, and output which methods were called to get to a certain point.

Screenshot showing an example output of console.trace

You can optionally pass data to also be outputted along with the stacktrace.


If your logging a large object to the console, it may become hard to read. The console.dir() method will format it in an expandable tree structure.

The following is an example of a directory-style console output:

Screenshot showing an example output of console.dir

You can also print XML or HTML based trees in a similar way, by using console.dirxml().


You may have some logging set up within your app, that you rely on during development, but don't wish the user to see. Replacing log statements with console.debug() will do just this, it functions in exactly the same way as console.log but will be stripped out by most build systems, or disabled when running in production mode.

Log Levels

You may have noticed that there's several filters in the browser console (info, warnings and error), they allow you to change the verbosity of logged data. To make use of these filters, just switch out log statements with one of the following:

  • - Informational messages for logging purposes, commonly includes a small "i" and / or a blue background
  • console.warn() - Warnings / non-critical errors, commonly includes a triangular exclamation mark and / or yellow background
  • console.error() - Errors which may affect the functionality, commonly includes a circular exclamation mark and / or red background

In Node.js different log levels get written to different streams when running in production, for example error() will write to stderr, whereas log outputs to stdout, but during development they will all appear in the console as normal.

Multi-Value Logs

Most functions on the console object will accept multiple parameters, so you can add labels to your output, or print multiple data points at a time, for example: console.log('User: ',;

But an easier approach for printing multiple, labelled values, is to make use of object deconstructing. For example, if you had three variables (e.g. x, y and z), you could log them as an object by surrounding them in curly braces, so that each variables name and value is outputted - like console.log( { x, y, z } );

Screenshot showing an example output of console deconstructing

Log String Formats

If you need to build formatted strings to output, you can do this with C-style printf using format specifiers.

The following specifiers are supported:

  • %s - String or any other type converted to string
  • %d / %i - Integer
  • %f - Float
  • %o - Use optimal formatting
  • %O - Use default formatting
  • %c - Use custom formatting (more info)

For example

console.log("Hello %s, welcome to the year %d!", "Alicia", new Date().getFullYear());
// Hello Alicia, welcome to the year 2022!

Of course, you could also use template literals to achieve the same thing, which might be easier to read for shorter strings.


Finally, when you're looking for an output from an event, you might want to get rid of everything logged to the console when the page first loaded. This can be done with console.clear(), which will clear all content, but nor reset any data.

It's usually also possible to clear the console by clicking the Bin icon, as well as to search through it using the Filter text input.

And some more...

There's so much more that you can do with logging to the console! For more info, check out the MDN console Documentation or the Chrome Dev Console Docs.

Just a quick note about best practices...

  • Define a lint rule, to prevent any console.log statements from being merged into your main branch
  • Write a wrapper function to handle logging, that way you can enable / disable debug logs based on environment, as well as use appropriate log levels, and apply any formatting. This can also be used to integrate with a third-party logging service with code updates only needed in a single place
  • Never log any sensitive info, the browser logs can be captured by any installed extensions, so should not be considered secure
  • Always use the correct log levels (like info, warn, error) to make filtering and disabling easier
  • Follow a consistent format, so logs can be parsed by a machine if needed
  • Write short, meaningful log messages always in English
  • Include the context or category within logs
  • Don't overdo it, only log useful info

GitHub Markdown Tricks ๐Ÿ™

A collection of 15 things you can do with GitHub-flavoured markdown to spice-up your projects readme.

Centre an image or other content

Make the title, sub-title and / or logo image centred (see example)

<p align="center">
  <img width="500" src="---" alt="---">

Include Notes and Warnings

> **Note**
> This is a note

> **Warning**
> This is a warning

Will render:

Note that this feature is still in beta.

Tiny Text

Using the <sub></sub> (Subscript) and <sup></sup> (superscript) will generate tiny text. But to keep it vertically-centred, you can combine the two with: <sub><sup>Small Text!</sup></sub>

Insert Keyboard Keys

Great for visually showing which keys to press, or drawing attention to something (see example)


Collapsible Sections

Useful for including long blocks of text (like logs or API response), without clogging up your page (see example)

    <summary>Collapsable Title</summary>
    <p>Put Content Here</p>

Note that to include markdown within the <p></p> content block, you must pad it with blank lines, both top and bottom

Embedding a Video

You can embed an MP4, simply by including the URL to the raw video in your markdown file (see example)

Adding Title to Links

You can add a title to a link, which will be displayed when the user hovers over it. This is done by surrounding it in quotes immediately after the URL

[DuckDuckGo]( "A great search engine for privacy").

GitHub-Specific Emojies

In GitHub flavoured markdown, emojis can be specified by their shortcode (e.g. :nerd_face:), here's a cheatsheet

But there are also some GitHub-specific emojis, including:

  • Octocat :octocat: โ†’ octocat
  • Bowtie :bowtie: โ†’ bowtie
  • Neck Beard :neckbeard: โ†’ neckbeard
  • Troll Face :trollface: โ†’ trollface
  • Ship It :shipit: โ†’ shipit
  • Suspect :suspect: โ†’ suspect

And a few more...

  • :rage1: โ†’ rage1 :rage2: โ†’ rage2 :rage3: โ†’ rage3 :rage4: โ†’ rage4
  • :hurtrealbad: โ†’ hurtrealbad :goberserk: โ†’ goberserk :finnadie: โ†’ finnadie :feelsgood: โ†’ feelsgood
  • :atom: โ†’ atom :basecamp: โ†’ basecamp :basecampy: โ†’ basecampy :electron: โ†’ electron

Moving Readme

If it bothers you having your file cluttering up your projects root directory, then you can place it in .github/ (along with any other GH stuff or assets). It will still be rendered as normal in your repositories home, but now you've got one less file in your project's base directory. (see example)

Including the code quote symbol (`) in a code block

Sometimes you may need to use the ` symbol within a code block.

Just wrap the code with double quotes ``, and ensure there is a space around the quote symbol

(here, I'm including quotes inside quotes inside quotes!)

Single-Cell Tables

This is a great way to draw attention to an important message

Hello World!

Creating Home Page .md

You can display a short markdown document at the top of your GitHub profile. To do so, just create a public repository with the same name as your GitHub username, and populate it with a non-empty For more info, see the Profile Readme Docs, or (see example)

Syntax Highlighting in Code Blocks

Code blocks become much easier to read with syntax highlighting. To use this, you must specify the language immediatley after the first ```.

For example:

const name = 'Alicia';

You can view a full list of supported languages, here

Showing Code Additions (or Deletions)

Set the language type to diff, and then precede each line which indicates an addition with +, or a - if it's a deletion. Note that the sign must be the first character of the line (watch out for white space), and should be followed by at least one blank space/ tab.

Create Buttons

In GitHub-flavoured markdown, the <kbd> tags also make great buttons. for your readme header.

For example:


**[<kbd>โ€ƒ<br>โ€ƒQuick Startโ€ƒ<br>โ€ƒ</kbd>][Quick Start]**โ€ƒ


Small Text

Both superscript (top of line, like this) and subscript (bottom of line, and this) are supported.
Just use the <sup></sup> or <sub></sub> tags.

You can wrap these elements with non-HTML markers (like bullet lists, tables or titles) too. (see example)

Side-Aligned Images

In a similar way to centering images, you can also right align them, where the text will flow down the left-side. An example of this can be seen in the tuxi repo. (See example)

<img src="" alt="Video Preview Gif" align="right" width="500px"/>

Merging Cells in Tables

It is possible to merge cells in tables (so that a given cell spans multiple rows or columns), but only if you define your table in HTML (see example).

This is done by adding the colspan or rowspan attribute, with the number of cells horizontally or vertically that it should span.

For example:

 <td colspan="2">I take up two columns!</td>

You can also set the text align property here too, using align="center" to centre text.

Further Links

Comparison of Private / Secure Emai Providers ๐Ÿ“ฌ

The following table is a quick, undetailed comparison of security-focused email providers.

Update: Since the markdown table isn't very clear, here's is a web version:

Name Jurisdiction Encryption Open Source Onion Site Pricing Domain Support Additional Aliases or Catch-All POP, IMAP, STMP External Security Audit Accepts Crypto Personal Info Requiements
ProtonMail ๐ŸŸข Switzerland ๐ŸŸข PGP ๐ŸŸข Yes ๐ŸŸข Yes ๐ŸŸข Free Plan - 500MB, 1 address, no custom domain. Paid plans start from โ‚ฌ5 and allow for additional features, custom domain, and increased message volume ๐ŸŸ  Yes (Pluss Plan, โ‚ฌ5.00/m) ๐ŸŸ  Yes (Professional Plan, โ‚ฌ8.00) ๐ŸŸ  Yes, but through Bridge ๐ŸŸข Yes (2021, by Securitum) ๐ŸŸข Yes (BTC only) ๐ŸŸข Recovery Email Only
Tutanota ๐ŸŸข Germany (14 Eyes) ๐ŸŸ  Hybrid AES + RSA ๐ŸŸ  Client Apps Only ๐Ÿ”ด No ๐ŸŸข Free Plan - 1GB, 1 address, no custom domain. Paid plans start from โ‚ฌ1 / month, and allow for a custom domain, 5 alias addresses and improved search and filters ๐ŸŸ  Yes (Premium Plan, โ‚ฌ1.00/m) ๐ŸŸ  โ‚ฌ1 / month per 20 aliases. No catch-all ๐Ÿ”ด No ๐ŸŸ  Apparently, but not published ๐ŸŸ  No (But ProxyStore gift cards accepted ๐ŸŸข None
CriptText โšช (Decentralized) ๐ŸŸ  Signal Protocol ๐ŸŸข Yes โšช N/A (no webmail) ๐ŸŸข Free ๐ŸŸข Yes ๐Ÿ”ด No ๐Ÿ”ด No ๐Ÿ”ด No โšช N/A (no payment required) ๐ŸŸข None
Mailfence ๐ŸŸข Belgium (14 Eyes) ๐ŸŸข PGP ๐Ÿ”ด No ๐Ÿ”ด No ๐ŸŸข Free Plan - 500MB, 1 address, no custom domain. Paid plans start from โ‚ฌ2.50 and allow for custom domain, 10 aliases, mail client access and increased message volume ๐ŸŸ  Yes (Entry Plan, โ‚ฌ2.50/m) ๐ŸŸ  Yes (Entry Plan, โ‚ฌ2.50) ๐ŸŸข Yes (IMAP, POP3, SMTP) ๐Ÿ”ด No ๐Ÿ”ด No (Card, PayPal) ๐ŸŸข Recovery Email Only ๐ŸŸข Germany (14 Eyes) ๐ŸŸข PGP ๐Ÿ”ด No ๐Ÿ”ด No ๐ŸŸ  No free plan. Starts at โ‚ฌ1 / month. Increases to โ‚ฌ9 for all features, and increased storage ๐ŸŸ  Yes (Standard Plan, โ‚ฌ3.00/m) ๐ŸŸ  25 aliases (Standard Plan). No catch-all ๐ŸŸข Yes (IMAP, POP3) ๐Ÿ”ด No ๐Ÿ”ด No ๐Ÿ”ด Requires full name, location, mobile number and a recovery email
Soverin ๐ŸŸ  Netherlands (9 Eyes) ๐ŸŸข PGP ๐Ÿ”ด No ๐Ÿ”ด No ๐ŸŸ  No free plan, but flat fee of โ‚ฌ3.25 / month for everything ๐ŸŸ  Yes (Paid Plan, โ‚ฌ3.25) ๐ŸŸ  Yes (Paid Plan, โ‚ฌ3.25) ๐ŸŸข Yes (IMAP) ๐Ÿ”ด No ๐Ÿ”ด No ๐Ÿ”ด Requires phone number
Posteo ๐ŸŸข Germany (14 Eyes) ๐ŸŸข PGP ๐ŸŸข Yes ๐Ÿ”ด No ๐ŸŸ  No free plan. Pricing is โ‚ฌ1 / month ๐Ÿ”ด No ๐ŸŸ  Yes (โ‚ฌ0.10/m per alias). No catch-all ๐ŸŸข Yes (IMAP, POP3) ๐Ÿ”ด No ๐Ÿ”ด No (Card, PayPal, Bank Transfer, Cash) ๐ŸŸข Payment Info Only
Runbox ๐ŸŸข Norway (14 Eyes) ๐ŸŸข PGP ๐ŸŸ  Client Apps Only ๐Ÿ”ด No ๐ŸŸ  No free plan. Starts at โ‚ฌ14.95 / year for a single domain, going up to โ‚ฌ69.95 / year for more storage and domains ๐ŸŸ  Yes (Micro Plan, โ‚ฌ1.25/m) ๐ŸŸ  โ‚ฌ3.95 / year per 5 aliases. No catch-all ๐ŸŸข Yes (IMAP, POP, SMTP) ๐Ÿ”ด No ๐ŸŸข Yes (BTC only) ๐ŸŸ  Full name, recovery email
Kolab Now ๐ŸŸข Switzerland ๐ŸŸข PGP ๐ŸŸข Yes ๐Ÿ”ด No ๐ŸŸ  No free plan. Starts at CHF 5.00 / mo, basic features only. Groupware account is CHF 9.90 ๐ŸŸ  Yes (Group Plan, CHF 9.90/m) ๐ŸŸ  Yes (Group Plan Only (CHF 9.90/ m) ๐ŸŸข Yes (IMAP, POP, SMTP) ๐Ÿ”ด No ๐Ÿ”ด No (Card, PayPal, Bank Transfer) ๐ŸŸ  Full name, recovery email
CounterMail ๐ŸŸข Sweden (14 Eyes) ๐ŸŸข PGP ๐Ÿ”ด No ๐Ÿ”ด No ๐ŸŸ  No free plan. Pricing is $4.83 / month ๐ŸŸ  Yes (Pain Plan + $15 setup fee) ๐ŸŸ  Yes, max 10 text aliases. No catch-all ๐ŸŸข Yes (IMAP) ๐Ÿ”ด No ๐ŸŸข Yes (BTC only) ๐ŸŸข None
StartMail ๐ŸŸ  Netherlands (9 Eyes) ๐ŸŸข PGP ๐Ÿ”ด No ๐Ÿ”ด No ๐ŸŸ  No free plan. Starts at $5 / month, but without custom domain support ๐ŸŸ  Yes (Custom Domain Plan, $5.85) ๐ŸŸ  Unlimited aliases. No catch-all ๐ŸŸข Yes (IMAP, SMTP) ๐ŸŸ  Apparently, but not published ๐ŸŸ  Yes (BTC only, but only personal accounts) ๐ŸŸ  Full name, recovery email
Disroot ๐ŸŸ  Netherlands (9 Eyes) ๐ŸŸ  PGP (Off-by-default) ๐Ÿ”ด No ๐Ÿ”ด No ๐ŸŸข Free, with payment required for each additional feature ๐ŸŸ  Yes (Donation required) ๐Ÿ”ด No ๐ŸŸข Yes (IMAP, POP, SMTP) ๐Ÿ”ด No ๐ŸŸข Yes (BTC only) ๐ŸŸข None
Hushmail ๐Ÿ”ด Canada (5 Eyes) ๐ŸŸข PGP ๐Ÿ”ด No ๐Ÿ”ด No ๐ŸŸ  No free plan. Starts at $5.99 / month, rising to $7.99 for email archiving support ๐ŸŸ  Yes (Small Business Plan, $5.99/m) ๐ŸŸ  Yes (Small Business Plan, $5.99/m) ๐ŸŸข Yes (POP, SMTP) ๐Ÿ”ด No ๐Ÿ”ด No ๐ŸŸ  Full name, recovery email
LavaBit ๐Ÿ”ด United States (5 Eyes) ๐ŸŸ  DIME ๐Ÿ”ด No ๐Ÿ”ด No ๐ŸŸ  Free plan, but invite only. Pricing ranges from $30 - $60 / year, depending on storage requirements ๐Ÿ”ด No ๐Ÿ”ด No ๐Ÿ”ด No ๐Ÿ”ด No ๐Ÿ”ด No ๐ŸŸ  Full name, recovery email

To make changes to the above list, submit a pull request to

See Also

One-Line Web Server ๐Ÿ–ฅ๏ธ

The following commands will each start a simple web server, serving up the files in the current directory.
Just open up the browser, and navigate to the system's IP + port (e.g. http://localhost:8080).


python -m http.server 8000


npx http-server ./ --port 8080


php -S


ruby -run -e httpd ./ -p 8080


Rscript -e 'servr::httd()' -p8080


Caddy is a feature-rich production-ready Go-based web server, with easy configuration. Just download and use something like the following command.

caddy file-server

Rust (with miniserve)

cargo install miniserve
miniserve -p 8080 .


busybox httpd -f -p 8080

You can also share the server with someone remotely,
see: Using Ngrok to expose server to the internet

NPM Dependency Security Best Practices ๐Ÿ“ฆ

The Definitive Guide to NPM Package Security

Check for Outdated Dependencies

It's important to keep dependencies up-to-datem since these packages usually undergo continuous improvements, bugs fixes and security patches.

Yarn has this functionality built in, with yarn outdated, which shows version info for all a projects installed dependencies.

A better tool for the job is npm-check (MIT) by @dylang, which will check for outdated, incorrect, and unused dependencies. This is used by just running npx npm-check from your projects root, you can also interactively update outdated packages with the --update flag, check the docs for all options.

There's also David-DM, a website that shows list of outdated dependencies for a given public git repo, with embeddable badges.

Remove Unused Packages

Large projects have a tendency to accumulate unused dependencies, causing there to be more to maintain, larger package size, and increased attack surface.

An easy way to check for unused dependencies, is with depcheck, a CLI tool for determining which of your installed packages are (likely) not being used anywhere in your code base. To use, just run npx depcheck. This tool is included within the npm-check mentioned above. It's worth noting, that this is not 100% accurate, so be careful when removing dependencies.
You can also remove any dependencies that have an overlapping range, with yarn dedupe

Audit Package Security

If a project has a large number of dependencies, then it will be more exposed to security risks. Many packages open new ports, thus increasing the attack surface, and some include vulnerabilities, a few of which are extremely severe; and open source projects regularly grow stale, neglecting to fix security flaws.

yarn audit or Snyk Test can be used to find, fix and monitor known vulnerabilities in open-source dependencies.

Sometimes these can be fixed automatically, with yarn audit fix, which will update certain semver-compatible packages to the patched version, if available.

Another option is to use a bot to scan for vulnerable packages, and submit a PR when a fix becomes available. The branch can then be pulled, tested and either merged or rejected. A popular choice for this is Dependabot. This functionality, plus more is also included with Snyk, a hosted solution for E2E application security monitoring.

If your project contains a dependency that has been identified as vulnerable, it is important to address it as quickly as possible. Either:

  • Update the package to a patched version, if available
  • Remove or replace the vulnerable package
  • Mark the issue as low risk, if you've looked into it and identified that there is no issue in your use case, such as in a dev dependency that is not used in production

It's worth noting that not all issues raised by the audit will actually have any impact on your application. Each issue still needs to be looked into to determine weather it is relevant to your use case though.

Only use Well Maintained Packages

Packages that are used by tens of thousands of projects are usually more closely monitored by other developers and security researchers, as opposed to smaller projects with very few regular users. This is important, as security (and other issues) will likely be spotted sooner, and addressed quicker in better maintained projects.

One way to assess package usage is its download rate, indicated in "Stats" section of the npmโ€™s package page, or using for historic download data. You should also check the projects GitHub page, see what to look for below.

Analysing Package Sizes

Bundlephobia is a super useful tool for checking any packages size and bundle impact prior to installing. There's also cost-of-modules, a CLI tool for locally analysing the size of the significant packages within your project. Just run npx cost-of-modules within your projects root.

Respecting Package Licenses

Open-Source components are published either under a permissive or a copyleft license. Before adding any package, it's important to check the license. Some common open source software licenses, such as GPL will prevent you from using their packages unless you also open source your code, and many packages (such as MIT) require you to correctly give attribution to the original author. To quickly understand what any given license allows you to do, TDLR; Legal is a useful resource.

There are also tools to help you automate the process of license checking within your project. The most notable hosted solution is FOSSA, which will automatically scan your project for dependency licenses and notify you of any issues and breach of licenses. There's also license-checker, a CLI tool which runs locally, and will output a list of licenses used by dependencies. This can be used by running npx license-checker from the root of your project

Ignore Run Scripts

NPM script hooks allow code to run at various points of the package life cycle. This is often used for general housekeeping tasks, but it can also be abused by bad actors to execute malicious code (such as the eslint-scope incident and the crossenv incident). Such attacks can usually be avoided by properly vetting new packages as well as upgrades. Adding the ignore-scripts option to your .npmrc file will prevent packages from running arbitrary commands.

Package Health Checks

The npm doctor command is useful for ensuring that everything is in order, and should be integrated into your automated checks. It will do the following:

  • Check the official npm registry is reachable, and display the currently configured registry.
  • Check that Git is available.
  • Review installed npm and Node.js versions.
  • Run permission checks on the various folders such as the local and global node_modules, and on the folder used for package cache.
  • Check the local npm module cache for checksum correctness.

Considerations when adding new Packages

A lot of the above issues can be mitigated, just by being thoughtful about what your installing into your project. Think about weather you really need another new dependency, ensure there's nothing similar already included in your app, and then thouroughly check the project out, being sure to validate the following criteria:

  • When was it last updated?
  • How often are releases published?
  • How many developers have contributed to it? (More is better, as there is a lower risk of the project being abandoned, and a higher change that issues are spotted).
  • How many open issues are there?
  • Does it have a security policy?
  • Any open or recent security advisories?
  • Is it correctly using semantic versioning?
  • Is it thoroughly tested? Does it use many additional dependencies? If so, are they to be deemed trustworthy?
  • How many other projects rely on it?
  • Is it frequently downloaded? (Check the "Stats" section of the NPM page, or use
  • Is the code clear and readable? If it is, then issues will be much easier to spot
  • Is the license compatible with your project, and have you acknowledged the terms (e.g. attribution)
  • How large is the package? And how will this affect your project's bundle size

For quick info about any package, you can also run npm view [package-name]

Strict Installs

When setting up your project in the CI environment, it is reccomended to use the CI install command. This is more strict than a regular install, which can help catch errors or inconsistencies caused by the incrementally-installed local environments of most npm users. It's also significantly faster, since it skips certain user-oriented features. Just replace npm i, with npm ci, or for yarn use: yarn install --frozen-lockfile.


In order to ensure reproducible builds, yarn needs to know exactley which version of each package should be installed. This is specified in the yarn.lock file (or npm-lock.json for NPM), and is auto-generated on install. This file should be commited to git, as it will prevent updates in a packages minor version causing any unexpected errors in on other machines.


Using your own registry, such as Verdaccio which can be privatley self-hosted, gives you far greater control over your NPM downloads. It allows for easy usage of private packages, and also gives you the ability to proxy other registries, and cache the downloaded of modules along the way, for improved speed and control, you can link multiple registries and override public packages. Alternative options include using NPM's enterprise offering, or installing dependencies directly from private git repos.

Dependency Inversion Principle

When writing your own modules, follow the SOLID principles, specifically in terms of the coupling between the different classes or modules. Writing small, maintainable, tested, single-purpose packages, which will then be easier to manage in terms of security and quality.

Secure your NPM Account

If you're publishing any of your modules to the NPM registry, then secure access is very important. Ensure that you've enabled 2FA, and keep your account credentials and tokens safe. If your not making any updates, then login using read-only mode. Make use of NPM author tokens for publishing and updating packages.

Watch out for Typosquatting Attacks

Typosquatting is an attack that involves packages with slight naming changes, that are often installed by mistake due to a typo. Since packages can have access to environmental variables, which often include credentials, tokens and API keys, a malicious actor can harvest this sensitive info.

To protect yourself:

  • Take care when copying and pasting commands into the terminal and always verify the source of the repo that you are installing via npm info.
  • Default to having an npm logged-out user in your daily work routines so your credentials wonโ€™t be the weak spot that would lead to easily compromising your account.
  • Consider appending the --ignore-script flag when installing packages, to prevent them from executing arbitrary code.

Tips and Tricks

  • Use any NPM CLI package, without having to first install it, using npx
  • While developing multiple packages simultaneously, yarn link is useful for creating symbolic links between them
  • Due to how NPM dependencies are structured, if you've been working on a lot of projects, you can find your disk space frequently running low. NPKill is a handy CLI util, to find all node_modules directories for easy deletion of the ones you no longer need. Just run npx npkill to get started. You can also do this nativley
  • To open the GitHub page for any package, just run npm docs [package-name]
  • If your facing any issues with NPM, then running npm doctor with troubleshoot common issues, and output the likely fix
  • List all installed packages in a tree structure with npm ls
  • Many of the NPM short commands can be grouped together, for example npm cit is the same as fresh install and run tests
  • When working with mono-repos or multiple packages, you can use the --prefix [./path/to/dir] flag to run NPM commands in other directories, without having to cd
  • To view all valid release versions of a given package, run npm view [package-name] versions, or yarn info [package-name] versions
  • To view tree of dependencies for any package, check out
  • More info can be found for any CVE via or using GitHub Advisories.

Dashy - A Self-Hosted Home Lab Dashboard ๐Ÿš€

Here's a quick project that I built in order to keep track of locally running services on my home lab. It serves as a landing page, to make it easier to navigate to various apps, without having to remember and type IP addresses or URLs.



  • Instant search by name, domain and tags - just start typing
  • Full keyboard shortcuts for navigation, searching and launching
  • Multiple color themes, with easy method for adding more
  • Customizable layout options, and item sizes
  • Quickly preview a website, by holding down the Alt key while clicking, to open it in a resizable pop-up modal
  • Many options for icons, including full Font-Awesome support and the ability to auto-fetch icon from URLs favicon
  • Additional info for each item visible on hover (including opening method icon and description as a tooltip)
  • Option for full-screen background image, custom nav-bar links, and custom footer text
  • User settings stored in local storage and applied on load
  • Encrypted cloud backup and restore feature available
  • Easy single-file YAML-based configuration
  • Small bundle size, fully responsive UI and PWA makes the app easy to use on any device
  • Plus lots more...

Source Code

Source, on GitHub:

Live Demo

Demo 1 โ”† Demo 2 โ”† Demo 3


Usage Guide

For full setup instructions, see this post, or follow the GitHub readme.


Get the code: git clone and cd dashy
Then install the dependencies: yarn


All settings are specified in ./public/conf.yml. You can see a full list of options in the docs, or modify one of these example configs


First build the project, with yarn build, you can then run yarn start to run it. Alternatively use Docker, with docker run -it -p 8080:80 --rm --name my-dashboard lissy93/dashy


Running yarn dev will build, test, lint then start the development server and watch for changes

Similar Apps / Alternatives

There are a few self-hosted web apps, that serve a similar purpose to Dashy. Including, but not limited to: Dashboard, Dash Machine, Heimdall, HomeDash2, Homepage, Homer, Organizr and Simple-Dash


The app makes use of the following components, kudos to their respective authors

And the app itself is built with Vue.js


Licensed under MIT X11, ยฉ Alicia Sykes 2021:

Using Espanso to boost Efficiency ๐Ÿšค


Espanso is an open source, privacy-first, cross-platform text expander developed by @federico-terzi and written in Rust. In short, it detects when you type a certain keyword, and replaces it on the fly with a pre-defined string or dynamic output.

Espanso not only supports simple text replacement/ expansion, but also images, custom scripts and shell commands, app-specific configurations and more. There is also a basic form feature, enabling arguments to be passed to a block. It's under active development, so hopefully there will be even more functionality implemented in the future.

It uses a file-based configuration, written entirely in YAML (but I think there is a GUI in development), and for the most part is quick and easy to it it configured exactly to your liking. But you can also use pre-built packages, installed via Espanso Hub (or any external source).

There are many possibilities where Espanso can be really useful, but the main areas that I am using it for are:

  • Quickly typing characters that do not appear on my keyboard (such as math symbols, foreign language characters and emojis)
  • Easily inserting longer strings that would otherwise have required many keystrokes
  • Inserting dynamic content, such as the output of a script, response from an API call, or time/ date info
  • Making typing easier, with a custom spelling and grammar auto-correct system

Espanso Links: Docs, Reddit, Package Hub, Source Code, Quick Start, Author's Site

I'm still working on my config, but for reference here it is:

Use Cases

Easy Emoji Inputs

The first thing I used Espanso for was being able to type emojis, without having to use wait for a popup to load or use the internet.

There is a plugin that does exactly this perfectly, called espanso-all-emojis, by @FoxxMD, using gemoji. It can be installed with:
espanso install all-emojis

Then just type the name of the emoji, surrounded by colons. For example:
:smile: --> ๐Ÿ˜„, :rocket: --> ๐Ÿš€, :milky way: --> ๐ŸŒŒ

For reference, here is the full list emojis, along with their shorthand code

The next thing I wanted to do, was be able to easily insert old-school ASCII emoticons or Lenny faces. This could be done with a similar method, but I didn't want to have to remember all the key combinations. A perfect opportunity to give Espanso's form feature a go!

With the above code, typing :lenny will open up a form with a dropdown, using the arrow keys I can now select an option, hit enter and it will be inserted

Espanso Lenny Demo

# Easily inputs ASCII emoticons from a dropdown
- trigger: :lenny
  form: "{{smileys}}"
      type: choice
      - 'ยฏ\\_(ใƒ„)_/ยฏ'
      - '(โ•ฏยฐโ–กยฐ๏ผ‰โ•ฏ๏ธต โ”ปโ”โ”ป'
      - '( อกเฒ  ส–ฬฏ อกเฒ )'
      - 'โ˜‰ โ€ฟ โš†'
      - 'ส•โ€ขแดฅโ€ขส”'
      - 'โ‹†๏ฝกหš โ˜๏ธŽ หš๏ฝกโ‹†๏ฝกหšโ˜ฝหš๏ฝกโ‹†'
      - '(ใฅแต”โ—กแต”)ใฅ'
      - '|แต”โ€ฟแต”|'
      - 'โคœ(*๏น*)โค'
      - 'ใƒ„'

Inserting Dynamic Content

Espanso has a series of built in extensions, that are able to insert dynamic data, either from a command, script, web address or API

An example of how this can be useful, is for fetching your current public IP address, using

# Outputs public IP address
- trigger: ":ip"
  replace: "{{output}}"
    - name: output
      type: shell
        cmd: "curl ''"

Or the current weather in your location, using

# Outputs the current weather for your location
- trigger: ":weather"
  replace: "{{output}}"
    - name: output
      type: shell
        cmd: "curl ''"

Easily insert the MIT license:

# Outputs full MIT license text, from GitHub
- trigger: :mit-long
  replace: "{{output}}"
  - name: output
    type: shell
      cmd: "curl ''"

Generating Deterministic Passwords on the Fly

LessPass is a stateless password manager, given a set of arguments (usually site, username and master pass) the output will always be the same, omitting the need to store passwords anywhere. I use it for less important accounts, and this sounded like another great use case for Espanso.

- trigger: :pass
  replace: "{{lesspass}}"
  - name: "params"
    type: form
      layout: |
        Less Pass Generator
        Website: {{site}}
        Login: {{login}}
        Master Password: {{pass}}
  - name: lesspass
    type: shell

With the above block, you can type :pass, and a form will popup, prompting you for the three arguments, and on submit a password will be returned and auto-filled. This does of course require the LessPass CLI tool to be installed.

Quickly Closing Brackets

This is a small one, saving only a single key press, but over time it all adds up. In Espanso, you can specify where the cursor should be placed using $|$

So typing a colon : followed by any type of bracket, tag or formatting symbol, will result in the corresponding closing bracket will be filled, and the cursor will be moved conveniently middle of the parenthesis.
This works for (), [], {}, <>, ` `, '', "", __, -- and **

# Auto close brackets, quotes and modifiers, putting cursor in the center
- trigger: ':('
  replace: '($|$)'
- trigger: ':['
  replace: '[$|$]'
- trigger: ':{'
  replace: '{$|$}'
- trigger: ':<'
  replace: '<$|$>'
- trigger: ':`'
  replace: '`$|$`'
- trigger: ":\'"
  replace: "\'$|$\'"
- trigger: ':"'
  replace: '"$|$"'
- trigger: ':_'
  replace: '_$|$_'
- trigger: ':*'
  replace: '*$|$*'
- trigger: ':-'
  replace: '-$|$-'

Date / Time Info

Another handy feature, is the built-in date extension. For the format of the date, see the chrono::format::strftime Docs.

# Outputs todays date (dd/mm/yy)
- trigger: :date
  replace: "{{date}}"
  - name: date
    type: date
      format: "%d/%m/%y"

# Outputs the current time (24hr)
- trigger: :time
  replace: "{{time}}"
  - name: time
    type: date
      format: "%H:%M"

# Outputs the month and year (e.g. January 2020)
- trigger: :month
  replace: "{{date}}"
  - name: date
    type: date
      format: "%B %Y"

Inserting Links

This is handy if you find yourself often sharing links in forums, or pasting them in documents. It makes use of Espanso's handy built-in Clipboard Extension, to get the URL that has been copied.

This works for Markdown with :md-link, HTML with :html-link and BB Code with :bb-link.

# Outputs markdown link, with clipboard contents as the URL
- trigger: ":md-link"
  replace: "[$|$]({{clipboard}})"
  - name: "clipboard"
    type: "clipboard"

# Creates a HTML anchor element, with clipboard contents as href
- trigger: ":html-link"
  replace: "<a href=\"{{clipboard}}\" />$|$</a>"
  - name: "clipboard"
    type: "clipboard"

# Outputs BB Code link, with clipboard contents as the URL
- trigger: ":bb-link"
  replace: "[url={{clipboard}}]$|$[/url]"
  - name: "clipboard"
    type: "clipboard"

For example, say you's copied have and ran :html-link is would return <a href="" /></a>, with the cursor in the middle, ready for the title.

Auto-Correct Typos

This is certainly the task that I use Espanso for most! And I have previously written a post outlining this.

If you're interested in doing this, I prepared a list of 4,200 of the most commonly misspelled words from Wikipedia, presented in AHK format, and wrote a quick script to convert it to Espanso YAML.

I personally just use the 250 words that I most often mistype / spell. The format looks like this (below), and my full script is here

- trigger: acsent
  replace: accent
  propagate_case: true
  word: true
- trigger: advesary
  replace: adversary
  propagate_case: true
  word: true

The word will not update until a terminator character (such as space or enter) is pressed (defined by word: true). The case will be propogated, (because propagate_case: true is set), so the output will match the case of the original word (either lower-case, upper-case or capitalized)

Inserting Common HTML and Markdown Elements

A simple one, if you find yourself often typing the symbols required for DOM elements, then this can save a bit of time.

Common tags, like :hr, :br, :div, :span, :para, :h1, :h2 etc are autofilled, with the cursor placed inside the tag ready for the value. For custom web components and XML tags, use :tag, and a form will open, where you can type the name of the element

Right now, for markdown, all I have is :md-code to insert a code block, and :md-collapse will in the very annoying <details><summary>, and again place the cursor inside.

# Inserts common HTML elements
- trigger: :hr
  replace: '<hr />'
- trigger: :br
  replace: '<br />'
- trigger: :div
  replace: '<div>$|$</div>'
- trigger: :span
  replace: '<span>$|$</span>'
- trigger: :h1
  replace: '<h1>$|$</h1>'
- trigger: :h2
  replace: '<h2>$|$</h2>'
- trigger: :h3
  replace: '<h3>$|$</h3>'
- trigger: :para
  replace: '<p>$|$</p>'

# Inserts any custom HTML, XML or web component tag 
- trigger: ":tag"
  replace: "<{{html.element}}>$|$</{{html.element}}>"
  - name: "html"
    type: form
      layout: "XML / HTML Element Inserter\nTag Name: {{element}}"
      fields: { element: { type: text }}

# Inserts a markdown code block
- trigger: :md-code
  replace: "```\n$|$\n```"

# Inserts markdown collapsable section
- trigger: :md-collapse
  replace: "\n<details>\n\t<summary>$|$</summary>\n\t<p></p>\n</details>"

Inserting Personal Info

There are several things that I find I need to type quite often, for various reasons. For example, email addresses, phone numbers, social media links, address and other important details. For some of this, I just use shortcuts (e.g. :addr outputs my address), whereas for other tasks I use dropdowns.

For example, to insert a social media profile link, without having to remember different shortcuts for different services, I just type :social. I do the same thing with email addresses and project websites

# Inserts the URL to a selected website or social media platform
- trigger: ":social"
  replace: "{{social.links}}"
  - name: "social"
    type: form
      layout: "Social Media Profiles \n{{links}}"
          type: choice
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''

Formulating Search Queries

There are browser extensions and web services that do this already (like DuckDuckGo bangs), but it's often useful to search a specific website, without having to first navigate to it. This function will formulate the URL, with the correct parameters ready for searching. You can also use Ctrl + L to focus the address bar.

For example, :srch-wiki will output You can also search with the contents of your clipboard (swc), where the query will be automatically filled.

# Quick search, formulates the URL params for searching a given website
- triggers: [:srch-ddg, :search-duckduckgo]
  replace: ''
- triggers: [:srch-wiki, :search-wikipedia]
  replace: ''
- triggers: [:srch-gh, :search-github]
  replace: ''
- triggers: [:srch-so, :search-stackoverflow]
  replace: ''
- triggers: [:srch-dh, :search-dockerhub]
  replace: ''
- triggers: [:srch-wa, :search-wolframalpha]
  replace: ''
- triggers: [:srch-red, :search-reddit]
  replace: ''
- triggers: [:srch-bbc, :search-bbc]
  replace: ''
- triggers: [:srch-vt, :search-virustotal]
  replace: ''
- triggers: [:srch-amz, :search-amazon]
  replace: ''
- triggers: [:srch-yt, :search-youtube]
  replace: ''
- triggers: [:srch-maps, :search-maps]
  replace: ''
- triggers: [:srch-goo, :search-google]
  replace: ''

# Similar to above, but it uses the clipboard for the search query
- trigger: ":swc-ddg"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-wiki"
  replace: "'{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-gh"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-so"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-dh"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-wa"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-red"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-bbc"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-vt"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-amz"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-yt"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-maps"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-goo"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]

Additional Notes

This is just a tiny tiny selection of things you could use Espanso for, the possibilities are almost endless, and I keep finding new ways to use it to speed up my typing. I'm excited about the future of the project, as new features and improvements are being added all the time.

Huge kudos to the author, Federico Terzi, who has done the bulk of the work himself.


This is worth mentioning, as I am sure others will be wondering about it. Initially I was fearful of a system that could apparently intercept all of my keystrokes, but the author has highlighted that it has been built with security in mind, there is absolutely no logging, and Espanso has a memory of just 3 characters (in order for the backspace functionality to work). There's also no network requests, and I verified this myself, both in the source, any by running Wireshark.
The code is also extremely efficient, written in Rust, it uses virtually negligible system resources, even on a low-spec PC.

My Life in Months ๐Ÿ—“๏ธ

Do you ever wonder how you're spending you're life? I do, and so I went through the main activities that I do on a daily, weekly or monthly basis and calculated the approximate total time I've spent on each of them. The following chart is a breakdown of time as a proportion of my total life (so far), where each square represents 1 month.

My Life in Months

I am now having an mini existential crisis after seeing how much of my life I have spent on relatively meaningless activities!

Quick How-To Guides ๐Ÿ’ซ

A mix of simple things that (despite doing regularly) I still forget, as well as more niche stuff that took me a little while to figure out. The purpose of documenting this, is both to help others and for future reference for myself




Code Management

See Also

SSH Tarpit with EndleSsh ๐Ÿชค๐Ÿ•ณ๏ธ

Endlessh is an SSH tarpit to keep the automated bots hitting port 22 locked up, and waste script kiddies time.

You can either get it from your package manager with sudo apt install endless, or build from source. To build, ensure you have libc6-dev installed, then git clone, cd endlessh, make, and move it to your path- sudo mv endlessh /usr/local/bin/.

Move the service to systemd, sudo cp util/endlessh.service /etc/systemd/system, and enable it sudo systemctl enable endlessh. Next, specify the configuration mkdir /etc/endlessh and sudo vim /etc/endlessh/config
Add your desired config, e.g.

Port 22
Delay 10000
MaxLineLength 32
MaxClients 4096
LogLevel 0
BindFamily 0

If you're using a low port number, ensure you update the endlessh.service with AmbientCapabilities=CAP_NET_BIND_SERVICE, and run sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/endlessh.

Finally, run sudo systemctl start endlessh to start the service, you should now see the service running on your specified port when you run netstat -tulpn | grep endlessh. If you need to check the logs, run sudo journalctl -u endlessh

Pimping up Your DuckDuckGo Search Results ๐Ÿ’„

Yet another awesome feature of DuckDuckGo, is that they make it really easy to modify your theme, just go to: From here you can customize your colors, fonts and layout of your search results and home page.

I am no designer by any stretch of the imagination (as you can probably see!), but here are a couple of themes I made, along with their code if you want to use them. You can preview themes live without making any changes by clicking the link below each screenshot, or to apply a theme, see the JS snippet at the end of this post.

Settings in DDG can either be applied temporarily with DuckDuckGo's URL parameters, locally as cookies, or globally using DDG's Cloud Save feature.


Screenshot - Navy Turquoise

Try it Out!

Color Palette: #0b1021, #080813, , #00af87, #0a7355, #d3d5e5, #a8d3ff


{"kae":"d", "k5":"1", "kay":"b", "kbc":"1", "kax":"v261-7", "kx":"a8d3ff", "kaa":"0a7355", "kj":"080813", "k9":"00af87", "k18":"1", "ka":"Hack", "k8":"d3d5e5", "k21":"080813", "k7":"0b1021", "kt":"v"}

Cookie Data

5=1; ay=b; bc=1; ae=d; ax=v261-7; 18=1; aa=0a7355; x=a8d3ff; 8=d3d5e5; 9=00af87; j=080813; 7=0b1021; 21=080813; a=Hack; t=v


Screenshot - Titanium

Try it Out!

Color Palette: #dedede, #9b83db, #000000


{"kae":"d", "k5":"1", "kay":"b", "kbc":"1", "kax":"v261-7", "kx":"000000", "kaa":"9b83db", "kj":"9b83db", "k9":"9b83db", "k18":"1", "k8":"000000", "k21":"9b83db", "k7":"dedede", "kt":"b", "ku":"1", "ka":"Arial Rounded MT Bold"}

Cookie Data

5=1; ay=b; bc=1; ae=d; ax=v261-7; u=1; 18=1; j=9b83db; x=000000; 7=dedede; 8=000000; aa=9b83db; 9=9b83db; 21=9b83db; t=b; a=Arial%20Rounded%20MT%20Bold


Screenshot - Cyberpunk

Try it Out!

Color Palette: #101116, #ff0055, #9254b5, #785eef, #fffc58


{"kae":"d", "k5":"1", "kay":"b", "kbc":"1", "kax":"v261-7", "kx":"FFFC58", "kaa":"9254b5", "kj":"FF0055", "k9":"FF0055", "k18":"1", "ka":"Cyberpunk", "k8":"785eef ", "k21":"FFFC58", "k7":"101116", "kt":"e"}

Cookie Data

5=1; ay=b; bc=1; ae=d; ax=v261-7; 8=785eef%20; aa=9254b5; x=FFFC58; 18=1; j=FF0055; 21=FFFC58; 7=101116; 9=FF0055; a=Cyberpunk; t=e


Screenshot - Dracula

Try it Out!

Credit: This theme was inspired by Dracula


{"kae":"t", "ks":"m", "kw":"n", "ko":"s", "ku":"-1", "ky":"44475a", "k7":"282a36", "k8":"f8f8f2", "k9":"50fa7b", "kt":"p", "km":"l", "kj":"282a36", "ka":"p", "kaa":"bd93f9", "kx":"f1fa8c", "kaf":"1", "kai":"1", "kf":"1"}

Cookie Data

ae=t; s=m; w=n; o=s; u=-1; y=44475a; 7=282a36; 8=f8f8f2; 9=50fa7b; t=p; m=l; j=282a36; a=p; aa=bd93f9; x=f1fa8c; af=1; ai=1; f=1


Screenshot - Hack

Try it Out!

Color Palette: #101116, #070709, #00ff2b, #d1d1d1, #fffc58, #118b25, Font: Courier


{"kae":"d", "k5":"1", "kay":"b", "kbc":"1", "kax":"v261-7", "kx":"FFFC58", "kaa":"0cbd2b", "kj":"070709", "k9":"00ff2b", "k18":"1", "ka":"Courier New", "k8":"d1d1d1", "k21":"118b25", "k7":"101116", "kt":"Courier"}

Cookie Data

5=1; ay=b; bc=1; ae=d; ax=v261-7; j=070709; x=FFFC58; 18=1; 7=101116; 9=00ff2b; aa=0cbd2b; 21=118b25; 8=d1d1d1; t=Courier; a=Courier%20New


Screenshot - Neon

Try it Out!

Color Palette: #261d49, #2a1f48, #df95ff, #9254b5, #1bccfd, #21f6bc, Font: Hack


{"kae":"d", "k5":"1", "kay":"b", "kbc":"1", "ka":"Hack", "k7":"261d49", "k8":"1bccfd", "k21":"2a1f48", "k18":"1", "kx":"21f6bc", "kaa":"9254b5", "kj":"2a1f48", "k9":"df95ff"}

Cookie Data

5=1; ay=b; bc=1; ae=d; j=2a1f48; a=Hack; 18=1; aa=9254b5; 7=261d49; 9=df95ff; 8=1bccfd; 21=2a1f48; x=21f6bc


Pale grey and dusty pastel

Screenshot - Nord

Try it Out!

Color Palette: #2e3440, #404855, #81a1c1, #87c0d0, #b28ead


{"kae":"d", "k5":"1", "kay":"b", "kbc":"1", "kax":"v261-7", "kx":"b28ead", "kaa":"87c0d0", "kj":"404855", "k9":"#81a1c1", "k18":"1", "ka":"Courier New", "k8":"#81a1c1", "k21":"#81a1c1", "k7":"2e3440", "kt":"h"}

Cookie Data

5=1; ay=b; bc=1; ae=d; ax=v261-7; a=Courier%20New; 7=2e3440; 18=1; 9=81a1c1; 8=81a1c1; aa=87c0d0; x=b28ead; 21=81a1c1; j=404855; t=h


There are three different methods of applying themes: Using cookies, URL parameters or DDG's cloud store

For cookies, settings can be applied programmatically with JavaScript directly through the browser console (or using a dev tool or third-party extension). Settings are specified as individual cookies, with a single string identifier and a corresponding value. The following is a quick script to apply settings easily, just replace ddg_cookie_input with your desired data (or use one of the examples above). Note that you must be on the DuckDuckGo domain for this to work.

// Converts DDG cookie string into formatted JSON
const makeCookieData = (ddg_cookie_input) => {
    let ddg_json = {};
  const items = ddg_cookie_input.split(/[ ,]+/);
    let parts = item.split('=');
    ddg_json[parts[0]] = parts[1];
  return ddg_json;

// Iterates over JSON, and adds to browser cookie store
const setCookies = (ddg_json) => {
  Object.keys(ddg_json).forEach(function(key) {

// Paste your cookie data here
const ddg_cookie_input = `5=1; ay=b; bc=1; ae=d; ax=v261-7; 18=1; aa=0a7355; x=a8d3ff; 8=d3d5e5; 9=00af87; j=080813; 7=0b1021; 21=080813; a=Hack; t=v`;

// Call set cookies, passing in formated cookie data

// All done, reload page for changes to take effect :)

This is handy, because once you've got DDG setup just how you like, you can make note of these values, and then easily apply them to any other system or browser with a single command.

If you would rather not set cookies, then you can use URL GET parameters (but note that the identifiers are different, see the full list of options here). You can find pre-formatted URL under Settings --> Appearance --> Show Bookmarklet and Settings Data. Here you can also enable cloud save, where you pick a password which is encoded into a URL so that you can access your setup on a different browser/ device.

Alternatively, if you're already using TamperMonkey, then you can manage this with JavaScript. Similarly if you're comfortable with CSS, then you have a lot more flexibility, and extensions like Stylish can make it easy to manage CSS overrides (here are some examples). - But the great thing about DDG, is that no extensions of hacks are required. (Also note that browser extensions can be pretty bad for privacy- they make your fingerprint much more unique, and occasionally are plain malicious)

My Server Setup โš™๏ธ

This article outlines the steps I take on any new server, to configure it for security, consistency and convenience. It is written specifically for Debian, but will also directly apply to derivatives (such as Ubuntu), and will likely be very similar for for other distros.

I am in the process of writing automation scripts to cover all of these steps, in the form of Ansible Playbooks.

This guide is split into 10 sections:

  1. System Update - Upgrade the OS and enable automated security updates
  2. System Setup - Specify hostname, add users, configure server time etc
  3. Configure SSH - Setup keys, configure sshd_config and set permissions
  4. Install Essential Software - Including git, vim, zsh, tmux, ranger etc
  5. Enable Firewall - Manage allowed inbound/ outbound connections with UFW
  6. Setup Intrusion Prevention - Protect from brute force attacks with Fail2Ban
  7. Configure Malicious Traffic Detection - Flag malicious packets with MalTrail
  8. Implement Security Auditing and Scanning - With ClamAV, Lynis and RKhunter
  9. Fetch Dotfiles for Vim, ZSH, Tmux etc to make SSH sessions more comfortable
  10. Automated Backups - Using Borg for incremental, off-site, encrypted backups
  11. Final Steps - Optional items (Go, Rust, Node, Python, Docker, NGINX etc..)

System Update

Update the System and Packages

  • apt update - Update system packages
  • apt -y upgrade - Upgrade OS
  • apt autoremove and apt clean - Remove locally downloaded deb packages and apt-get caches

Enable Unattended Upgrades

  • apt install unattended-upgrades - Install package (if not already installed)
  • dpkg-reconfigure --priority=high unattended-upgrades - Enable automatic upgrades
  • vi /etc/apt/apt.conf.d/50unattended-upgrades to update the configuration

System Setup

Specify Host Name

  • sudo hostnamectl set-hostname [new-host-name] - Set the machines host name
  • Add [hostname] into /etc/hosts - Add host name to the hosts file

Add New Users

  • useradd -m [username] -c "[user full name]" - Create a new user (-c Allows an associated name or comment)
  • passwd [username] - Specify a password for new user
  • sudo usermod -a -G sudo [username] - Gives the user root privileges (only apply if needed)

Set the Server Time

  • sudo timedatectl set-timezone Europe/London
  • sudo vi /etc/systemd/timesyncd.conf and add the address of the local NTP server
  • sudo systemctl restart systemd-timesyncd.service - Restart the time sync service

Configure SSH

Setup SSH Keys for Authentication

  • sudo apt install openssh-server - Install OpenSSH Server on remote host
  • ssh-keygen -t rsa -b 4096 - On the local system. Generates a new SSH key pair (enter a strong passphrase when prompted)
  • ssh-copy-id root@[] - Uploads to the remote server, and update the hosts file
  • chmod go-w ~ ~/.ssh ~/.ssh/authorized_keys - On the remote host, updated permissions
  • sudo ufw allow ssh - If UFW is enabled, then allow SSH access

Next we're going configure a couple of SSH security essentials

  • vim /etc/ssh/sshd_config - To open the SSH daemon's config file , and update:
    • Protocol 2 # Only use SSH 2 Protocol
    • PermitRootLogin no # Disable root SSH login
    • PasswordAuthentication no # Disable password-based SSH login
    • Compression delayed # Compression could be dangerous, only allow it once authenticated
    • MaxAuthTries 5 # Limit the maximum authentication attempts
    • PrintLastLog yes # Display last login date for an extra check (should be default)
    • PermitEmptyPasswords no # Disallow empty passwords (Not relevant for SSH Keys, but still good to have)
    • IgnoreRhosts yes # Disallow access via rhosts, which is rarely used anymore
    • IgnoreUserKnownHosts yes # Only trust the global known hosts list
    • HostBasedAuthentication no # Similar to rhosts, this is rarely used
    • Port 2200 # Set SSH access to a non-standard port
    • StrictModes yes # Prevent users from accidentally leaving their directories/ files as writable
    • UsePrivilegeSeparation sandbox # Prevent privilege escalation
    • PubkeyAuthentication yes # Public key authentication should be preferred (should be default)
    • GSSAPIAuthentication no # If you are not using GSSAPI authentication, this should be disabled
    • KerberosAuthentication no # If you are not using Kerberos authentication, this again should be disabled
    • Ciphers aes128-ctr,aes192-ctr,aes256-ctr # Use FIPS 140-2 compliant ciphers, to avoid weak encryption algorithms
    • MACs hmac-sha2-256,hmac-sha2-512 # Use FIPS 140-2 Compliant MACs, to avoid weak cryptographic hashes

The SSH daemon must be restarted, in order for these config changes to take effect: sudo systemctl restart ssh

Protect SSH Host Keys

  • sudo chmod 0600 /etc/ssh/*key - Set permissions for private keys
  • sudo chmod 0644 /etc/ssh/*pub - Set permissions for public keys

If your system stores keys in a different directory, you can find them with grep -i hostkey /etc/ssh/sshd_config. You can list the permissions of keys with ls -l /etc/ssh/*key (or *pub for public keys)

Optionally, configure an SSH tarpit, to lock up the bots hammering port 22, with Endlessh

Install Essential Software

Install Packages

  • sudo apt update - Ensure the package list is up-to-date
  • sudo apt install -y git vim tmux zsh ranger - Install essentials: vim, git, tmux, ZSH and ranger
  • sudo apt install -y make curl - Install utilities
  • sudo apt install -y fzf exa - Install command line improvements
  • sudo apt install -y ctags xsel glances fonts-powerline - Install visual improvements
  • sudo apt install -y clamav rkhunter lynis - Install security audit tools
  • sudo apt install -y neofetch figlet lolcat - Optionally, install fun stuff


  • If needed, install Docker
  • If needed, install Go Lang
  • If needed, install Rust and Cargo, with sudo curl -sSf | sh (check the script first!)
  • If needed, install Python and PIP, with sudo apt install python3 python3-pip
  • If needed, install Node.js and NPM, with sudo apt install nodejs npm
    • Or use NodeSource's PPA: curl -fsSL | bash -

Configure Firewall with UFW

  • sudo apt install ufw - Install UFW
  • sudo vi /etc/default/ufw and set IPV6=yes to use IPv6
  • sudo ufw default deny incoming and sudo ufw default allow outgoing to deny all incoming traffic, and allow outgoing
  • sudo ufw allow 2200/tcp to for example, allow incoming SSH traffic on port 2200
  • sudo ufw disable and sudo ufw enable (or systemctl restart ufw) to restart UFW
  • sudo ufw status - Check the current status

Whenever a new application is configured, UFW needs to be updated to allow incoming traffic to that port and protocol.

Intrusion Prevention with Fail2Ban

  • sudo apt install fail2ban - Install Fail2ban
  • sudo cp /etc/fail2ban/jail.{conf,local} - Copy jail.conf to jail.local
  • sudo vi /etc/fail2ban/jail.local - To edit the local config file, and add:
    • ignoreip = ::1 - with local IP addresses
    • bantime = 1d - Increase the ban time to 1 day
    • findtime = 10m - Time between each attempt
    • maxretry = 7 - Number of failures before IP is banned
  • sudo systemctl restart fail2ban - Restart Fail2ban, for changes to take effect
  • sudo systemctl status fail2ban - Show the current status

The fail2ban-client can also be used to interact with the Fail2ban service from the CLI

Malicious Traffic Detection with MalTrail

For systems that have services exposed to the internet, or for a firewall device that protects internal devices, then MalTrail can be really useful for flagging anything out of the ordinary.

Install dependencies and get the MalTrail source

  • sudo apt install schedtool python-pcapy git - SchedTool for better CPU scheduling, and Python for MalTrail
  • git clone - Get the MalTrail code
  • cd maltrail - Navigate into the directoru

Run MalTrail. There are two components, a sensor and a server.

  • sudo python & - Start the sensor (& will run it in the background)
  • python & - Start the server, in order to log results and allow access through a GUI

Access the GUI

  • Navigate to http://[ip]:8338 and enter username: admin and password: changeme!
  • To test things are working correctly, try ping -c 1 or, for DNS capturing nslookup
    • Results for both should display on the dashboard and in the logs: /var/log/maltrail/
    • To view today's logs, run cat /var/log/maltrail/$(date +"%Y-%m-%d").log

Configure MalTrail's Settings

  • echo -n '[your-desired-password]' | sha256sum | cut -d " " -f 1 - Choose a strong password and hash it
  • sudo vim /home/tech/maltrail/maltrail.conf - Open the configuration file
  • Under USERS section, replace the current Admin:05a181f00c15... with Admin:[your-hashed-password]
  • From within the maltrail.conf you can configure other settings for the server component
  • pkill -f && python & - Restart MalTrail
  • Under normal circumstances the logs are fairly sparse, so it is possible to use a system like entr to monitor them for changes and notify you using a channel of your choice.

Security Scanning with ClamAV, Lynis and RKhunter

For security monitoring, I am using Lynis to audit general system config, ClamAV for detecting malware and rkhunter for checking for root kits.

Install Packages

  • sudo apt install -y clamav rkhunter lynis - Install security audit tools
  • sudo rkhunter --propupd - Update rkhunter's file properties database

Run a System Audit

  • sudo lynis audit system - Run a full security audit
  • sudo clamscan / -r - Scan for malware
  • sudo rkhunter -c --sk --rwo - Check for rootkits (c for check, sk for skip keypress and rwo for report wanrings only)

These commands can also be put into an .sh file, and run periodically as a scheduled cron job, sending results to your email.

Setup Dotfiles

  • git clone --recursive - Download my dotfiles
  • cd ./dotfiles - Navigate to directory
  • ./ - Run the install script

Automated Backups

Borg is a deduplicating archiver with compression and encryption, it's space efficient, secure and easy. BorgBase provides affordable, performant, simple and safe Borg repositories (10 GB free or 100 GB for $24/year). I am also using for monitoring backup status.

Will Browning has written an excellent tutorial for setting up Borg Backups.

  • sudo apt install borgbackup python3-pip python3-setuptools - Install Borg backup, and Python (if you don't already have it)
  • pip3 install wheel and pip3 install --user --upgrade borgmatic - Install borgmatic for the user, and it's dependency, wheel
  • export PATH="$HOME/.local/bin:$PATH" - Add this to zsh/ bashrc to include the above commands in your path
  • sudo env "PATH=$PATH" generate-borgmatic-config - Generate a bormatic config

Final Steps

Setup Welcome Banner

  • sudo cp ~/dotfiles/utils/ /etc/profile.d/ - Copy welcome banner from utils to system
  • sudo chmod +x /etc/profile.d/ - Update permissions for all users

Install NetData, for web-based resource monitoring

  • bash <(curl -Ss --stable-channel --disable-telemetry - Install NetData
  • You will need to allow firewall access, sudo ufw allow from [your-static-ip] to any port 19999
  • If using a cloud platform (like AWS, Azure, GCP) then you may need specify an inbound port rule to allow access

Setup Glances

  • Install: sudo apt install glances
  • To enable Glances to start up at boot time, run it as a service with systemd. See docs for more info
  • If you need to access Glances remotely, either VPN into your server (recommended), or setup a reverse proxy to the Glances web page, as per docs

Install Bpytop

  • sudo pip3 install bpytop --upgrade

If needed, use Smartmontool to monitor the status of you're disks.

  • sudo apt install smartmontools - Install smartmontool, which includes smartctl
  • sudo fdisk -l - Find the disk(s) you wish to ceck
  • sudo smartctl -t short /dev/sdX - Run a quick check, where X is you're drive number
  • For more info regarding the output, see this post

Optionally, setup Bash Hub for indexed and searchable terminal history in the cloud

  • curl -OL && zsh setup - Check the installation script first, then install
  • When prompted, log into your account. Restart your session, and run bh to access the help menu
  • Add an environmental variable, indicating which commands should not be saved, e.g. export BH_FILTER="(scp|ssh|--password)"
  • Precede any command that contains sensitive information with #ignore to prevent it being saved
  • See usage docs:

Additional Tasks:

Spelling Auto-Correct System โœ๏ธโŒ

TDLR; Auto-correct is a lot more efficient than manually correcting misspelled words. Espanso is awesome.
Beyond that, this isn't too interesting - I just documented this so I can refer back to it in the future.
If you're just looking for a generic word list, see this post, which contains 4,200 common misspellings.

  1. Intro
  2. Word List
  3. Converter
  4. Usage


I am terrible at spelling. About 15% of what I've typed will be underlined in red. It's usually the same couple hundred words that I forget how to write. The best solution would probably be to learn how to spell, but I've instead I use a system to auto-correct my mistakes.

I use Espanso to implement this.

There are of course standalone applications that do exactly this (like client9/misspell, streetsidesoftware/cspell and sedm0784/vim-you-autocorrect), but I have other Espanso scripts for various tasks, so it made sense to bundle it all into one simple, cross-platform solution. I've previously used Auto-Hot-Key which is also very good, but only available for Windows systems. Esprano's matching system makes it an extremely powerful tool, this is a very trivial task compared to all the other awesome stuff you can use it for.

My Auto-Correction List

These are just the 220-ish words that I most often miss type/ spell, along with their correct spellings. It is written as an AHK script (because it's easier to maintain), but I made a quick utility to convert AHK into YAML for use with Espanso.

For a more comprehensive list of 4,200 crowd source common misspellings, see here:

; This is my personal list of words I commonly misspelled plus auto-corrections
; Licensed under MIT - (C) Alicia Sykes, 2021 <>
; Format: '::[Incorrect Word]::[Correct Word]'

::ourself::our self

For more words, see:

Source code for Converter Script on


For Espanso, first convert your source into YAML, then run espanso path to find your config file location, drop the script into that directory, and restart Espanso, it should now be running.

For the Auto Hot Key script, once you have AHK installed, then just download the above script (save it with the .ahk extension), double click on it and it will be running.

Top 25 Raspberry Pi Projects ๐Ÿฅง


Ever since the first version was released in 2012, the Raspberry Pi has been a staple piece of kit for professionals, hobbyists, educators and everyone in between. And for good reason, it's small, low power, affordable but extremely versatile. There are of course other single board computers on the market, but the Pi has a strong community behind it and provides a good balance between capabilities, form factor and price.

Raspberry Pi Projects

Here is a curated list of projects that I have used, enjoyed and would recommend for anyone looking to put their Pi to use. For links to each projects GitHub repo, see:

There's nothing too complicated here, so this should also provide a good starting point for beginners. Everything here is fully open source and backed by strong communities with large user bases.

  • Pi-Hole - Network-wide DNS-based ad blocker
  • RetroPi - Retro game emulator
  • Magic Mirror - Easy modular smart mirror project
  • Pi-KVM - Easy and fully-featured KVM-over-IP
  • OctoPrint - 3D printer remote controller interface
  • PiMusicBox - An all-in-one streaming device with Spotify support (see also PiCorePlayer or Balena Sound)
  • SonicPi - Code-based music creation tool
  • Kiwix Hotspot - An off-grid WiFi hotspot serving up all the world's knowledge (Wikipedia, StackExchange, TED, the Gutenburg Library and more)
  • RaspAP - Fully-features wireless access point, with GUI user interface. Supports ad-blocking, VPN, data usage, and more
  • PiVPN - Super simple OpenVPN or WireGuard server
  • P4wnP1_aloa - All-in-one highly customizable Pi Zero attack platform (see also PwnPi, a pen test drop box)
  • MotionEye - Easy multi-camera CCTV surveillance system (see also Rpisurv)
  • RaspiBlitz - Run a BTC lightning node (see also Umbrel or RaspNode and this tutorial)
  • NextCloudPi - Self-host a local file server, with tons of useful plugins available
  • Audio-Reactive-LED-Strip - Create lighting effects in time with music
  • BrewPi - Fully automated brewery controller
  • RaspberryShake - Earthquake monitoring with seismographs and infrasound
  • Sensor.Community - Host a Pi environment sensor, using the Enviro Air Quality pHAT
  • RPiPlay - Turn a Raspberry Pi into an Airplay server to enable screen mirroring on TVs, monitors and projectors
  • RpiITX - RF transmitter 5 KHz to 1500 MHz
  • PiAware - ADS-B aircraft tracking from FlightAware (see also Startus ADS-B)
  • Pirate Box - Anonymous offline mobile file-sharing and communications system
  • MoBro - An external resource monitor screen for your PC
  • Mycroft AI - Privacy-respecting AI voice assistant similar to Alexa. You can also build a full enclosure out of the developer kit. (Alternatively, build a Google Home, using Voice Kit or an Alexa)

There is almost no limit to what you can do with a Pi, this list is just intended to serve as an example and a provide a starting point.

Operating Systems

Raspberry Pi can also be used as a normal computer; either a desktop, mini handheld or headless as a server. You're not just limited to Raspberry Pi OS, it also works very well with Debian, FreeBSD, Arch, Kali, Slackware and Ubuntu to name a few.

For more specific use cases, there's also Diet Pi (super light-weight OS specifically for single-board computers), OpenElec (lightweight system for running a Kodi media center), Windows IoT Core, OCMC (media center), Emteria Android (for Android) and Chromium OS (similar to open source alternative to Chrome OS), Nems (for network monitoring) and many more

Self-Hosted Applications

Once running an OS of your choice, the Pi is also perfect for self-hosting Linux applications. For example;

... and tons more

If you're interested in self-hosting multiple apps, or using your Pi as a little home server, then check out Home Lab OS by Nick Busey, it makes correctly configuring a complex lab as easy as running a single command.

Tools for Flashing SD Card/ USB

For flashing an OS to you're Pi's SD card or USB: Official Pi Imager, Etcher or use the dd (CLI utility on Unix systems). Rufus and Win32 Disk Imager are also good utilities, but only available on Windows.

To backup you're Pi's SD card of USB, you can also use dd (the same as cloning, but in reverse). For example:

  • Backup: sudo dd bs=4M if=/dev/sdb of=PiOS.img
  • Restore: sudo dd bs=4M if=PiOS.img of=/dev/sdb

For more information, see this tutorial. Alternatively, on Windows systems, you can use Win32 Disk Imager to clone the SD card.

More Project Ideas & Tutorials

The following projects are a bit more hands-on

  • Truly WiFi extender - A very performant and inexpensive WiFi repeater solution: via Instructables by @mrtejas99
  • Print Server - Turn any old printer into an internet-connected WiFi printer: via by Christian Cawley
  • YouTube Streaming Bird Box: via Instructables by @buestad
  • Smart Glasses with a treansparent OLED display: via Instructables by @Bradley_Campbell
  • 3D-Printed Mini Macintosh PC: via Instructables
  • Mini Desktop PC with the Pi 4: via Instructables by @thediylife
  • Internet Radio Player - Stream content from Pandora's Radio: via Instructables by @Ayy
  • Raspberry Pi Zero Cherry MX Split Mechanical Keyboard: via Instructables by Gosse Adema
  • Step-by-step Pi NAS with OpenMediaVault: via Instructables by @araymbox
  • Distraction-Free Writing Machine: via Instructables by @CameronCoward